Kate Lovett 9d96df2364
Modernize framework lints (#179089)
WIP

Commits separated as follows:
- Update lints in analysis_options files
- Run `dart fix --apply`
- Clean up leftover analysis issues 
- Run `dart format .` in the right places.

Local analysis and testing passes. Checking CI now.

Part of https://github.com/flutter/flutter/issues/178827
- Adoption of flutter_lints in examples/api coming in a separate change
(cc @loic-sharma)

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-11-26 01:10:39 +00:00

592 lines
16 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'test_widgets.dart';
class TestInherited extends InheritedWidget {
const TestInherited({super.key, required super.child, this.shouldNotify = true});
final bool shouldNotify;
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return shouldNotify;
}
}
class ValueInherited extends InheritedWidget {
const ValueInherited({super.key, required super.child, required this.value});
final int value;
@override
bool updateShouldNotify(ValueInherited oldWidget) => value != oldWidget.value;
}
class ExpectFail extends StatefulWidget {
const ExpectFail(this.onError, {super.key});
final VoidCallback onError;
@override
ExpectFailState createState() => ExpectFailState();
}
class ExpectFailState extends State<ExpectFail> {
@override
void initState() {
super.initState();
try {
context.dependOnInheritedWidgetOfExactType<TestInherited>(); // should fail
} catch (e) {
widget.onError();
}
}
@override
Widget build(BuildContext context) => Container();
}
class ChangeNotifierInherited extends InheritedNotifier<ChangeNotifier> {
const ChangeNotifierInherited({super.key, required super.child, super.notifier});
}
class ThemedCard extends SingleChildRenderObjectWidget {
const ThemedCard({super.key}) : super(child: const SizedBox.expand());
@override
RenderPhysicalShape createRenderObject(BuildContext context) {
final CardThemeData cardTheme = CardTheme.of(context);
return RenderPhysicalShape(
clipper: ShapeBorderClipper(shape: cardTheme.shape ?? const RoundedRectangleBorder()),
clipBehavior: cardTheme.clipBehavior ?? Clip.antiAlias,
color: cardTheme.color ?? Colors.white,
elevation: cardTheme.elevation ?? 0.0,
shadowColor: cardTheme.shadowColor ?? Colors.black,
);
}
@override
void updateRenderObject(BuildContext context, RenderPhysicalShape renderObject) {
final CardThemeData cardTheme = CardTheme.of(context);
renderObject
..clipper = ShapeBorderClipper(shape: cardTheme.shape ?? const RoundedRectangleBorder())
..clipBehavior = cardTheme.clipBehavior ?? Clip.antiAlias
..color = cardTheme.color ?? Colors.white
..elevation = cardTheme.elevation ?? 0.0
..shadowColor = cardTheme.shadowColor ?? Colors.black;
}
}
void main() {
testWidgets('Inherited notifies dependents', (WidgetTester tester) async {
final log = <TestInherited>[];
final builder = Builder(
builder: (BuildContext context) {
log.add(context.dependOnInheritedWidgetOfExactType<TestInherited>()!);
return Container();
},
);
final first = TestInherited(child: builder);
await tester.pumpWidget(first);
expect(log, equals(<TestInherited>[first]));
final second = TestInherited(shouldNotify: false, child: builder);
await tester.pumpWidget(second);
expect(log, equals(<TestInherited>[first]));
final third = TestInherited(child: builder);
await tester.pumpWidget(third);
expect(log, equals(<TestInherited>[first, third]));
});
testWidgets('Update inherited when reparenting state', (WidgetTester tester) async {
final GlobalKey globalKey = GlobalKey();
final log = <TestInherited>[];
TestInherited build() {
return TestInherited(
key: UniqueKey(),
child: Container(
key: globalKey,
child: Builder(
builder: (BuildContext context) {
log.add(context.dependOnInheritedWidgetOfExactType<TestInherited>()!);
return Container();
},
),
),
);
}
final TestInherited first = build();
await tester.pumpWidget(first);
expect(log, equals(<TestInherited>[first]));
final TestInherited second = build();
await tester.pumpWidget(second);
expect(log, equals(<TestInherited>[first, second]));
});
testWidgets('Update inherited when removing node', (WidgetTester tester) async {
final log = <String>[];
await tester.pumpWidget(
ValueInherited(
value: 1,
child: FlipWidget(
left: ValueInherited(
value: 2,
child: ValueInherited(
value: 3,
child: Builder(
builder: (BuildContext context) {
final ValueInherited v = context
.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
log.add('a: ${v.value}');
return const Text('', textDirection: TextDirection.ltr);
},
),
),
),
right: ValueInherited(
value: 2,
child: Builder(
builder: (BuildContext context) {
final ValueInherited v = context
.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
log.add('b: ${v.value}');
return const Text('', textDirection: TextDirection.ltr);
},
),
),
),
),
);
expect(log, equals(<String>['a: 3']));
log.clear();
await tester.pump();
expect(log, equals(<String>[]));
log.clear();
flipStatefulWidget(tester);
await tester.pump();
expect(log, equals(<String>['b: 2']));
log.clear();
flipStatefulWidget(tester);
await tester.pump();
expect(log, equals(<String>['a: 3']));
log.clear();
});
testWidgets('Update inherited when removing node and child has global key', (
WidgetTester tester,
) async {
final log = <String>[];
final Key key = GlobalKey();
await tester.pumpWidget(
ValueInherited(
value: 1,
child: FlipWidget(
left: ValueInherited(
value: 2,
child: ValueInherited(
value: 3,
child: Container(
key: key,
child: Builder(
builder: (BuildContext context) {
final ValueInherited v = context
.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
log.add('a: ${v.value}');
return const Text('', textDirection: TextDirection.ltr);
},
),
),
),
),
right: ValueInherited(
value: 2,
child: Container(
key: key,
child: Builder(
builder: (BuildContext context) {
final ValueInherited v = context
.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
log.add('b: ${v.value}');
return const Text('', textDirection: TextDirection.ltr);
},
),
),
),
),
),
);
expect(log, equals(<String>['a: 3']));
log.clear();
await tester.pump();
expect(log, equals(<String>[]));
log.clear();
flipStatefulWidget(tester);
await tester.pump();
expect(log, equals(<String>['b: 2']));
log.clear();
flipStatefulWidget(tester);
await tester.pump();
expect(log, equals(<String>['a: 3']));
log.clear();
});
testWidgets('Update inherited when removing node and child has global key with constant child', (
WidgetTester tester,
) async {
final log = <int>[];
final Key key = GlobalKey();
final Widget child = Builder(
builder: (BuildContext context) {
final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
log.add(v.value);
return const Text('', textDirection: TextDirection.ltr);
},
);
await tester.pumpWidget(
ValueInherited(
value: 1,
child: FlipWidget(
left: ValueInherited(
value: 2,
child: ValueInherited(
value: 3,
child: Container(key: key, child: child),
),
),
right: ValueInherited(
value: 2,
child: Container(key: key, child: child),
),
),
),
);
expect(log, equals(<int>[3]));
log.clear();
await tester.pump();
expect(log, equals(<int>[]));
log.clear();
flipStatefulWidget(tester);
await tester.pump();
expect(log, equals(<int>[2]));
log.clear();
flipStatefulWidget(tester);
await tester.pump();
expect(log, equals(<int>[3]));
log.clear();
});
testWidgets(
'Update inherited when removing node and child has global key with constant child, minimised',
(WidgetTester tester) async {
final log = <int>[];
final Widget child = Builder(
key: GlobalKey(),
builder: (BuildContext context) {
final ValueInherited v = context.dependOnInheritedWidgetOfExactType<ValueInherited>()!;
log.add(v.value);
return const Text('', textDirection: TextDirection.ltr);
},
);
await tester.pumpWidget(
ValueInherited(
value: 2,
child: FlipWidget(
left: ValueInherited(value: 3, child: child),
right: child,
),
),
);
expect(log, equals(<int>[3]));
log.clear();
await tester.pump();
expect(log, equals(<int>[]));
log.clear();
flipStatefulWidget(tester);
await tester.pump();
expect(log, equals(<int>[2]));
log.clear();
flipStatefulWidget(tester);
await tester.pump();
expect(log, equals(<int>[3]));
log.clear();
},
);
testWidgets(
'Inherited widget notifies descendants when descendant previously failed to find a match',
(WidgetTester tester) async {
int? inheritedValue = -1;
final Widget inner = Container(
key: GlobalKey(),
child: Builder(
builder: (BuildContext context) {
final ValueInherited? widget = context
.dependOnInheritedWidgetOfExactType<ValueInherited>();
inheritedValue = widget?.value;
return Container();
},
),
);
await tester.pumpWidget(inner);
expect(inheritedValue, isNull);
inheritedValue = -2;
await tester.pumpWidget(ValueInherited(value: 3, child: inner));
expect(inheritedValue, equals(3));
},
);
testWidgets(
"Inherited widget doesn't notify descendants when descendant did not previously fail to find a match and had no dependencies",
(WidgetTester tester) async {
var buildCount = 0;
final Widget inner = Container(
key: GlobalKey(),
child: Builder(
builder: (BuildContext context) {
buildCount += 1;
return Container();
},
),
);
await tester.pumpWidget(inner);
expect(buildCount, equals(1));
await tester.pumpWidget(ValueInherited(value: 3, child: inner));
expect(buildCount, equals(1));
},
);
testWidgets(
'Inherited widget does notify descendants when descendant did not previously fail to find a match but did have other dependencies',
(WidgetTester tester) async {
var buildCount = 0;
final Widget inner = Container(
key: GlobalKey(),
child: TestInherited(
shouldNotify: false,
child: Builder(
builder: (BuildContext context) {
context.dependOnInheritedWidgetOfExactType<TestInherited>();
buildCount += 1;
return Container();
},
),
),
);
await tester.pumpWidget(inner);
expect(buildCount, equals(1));
await tester.pumpWidget(ValueInherited(value: 3, child: inner));
expect(buildCount, equals(2));
},
);
testWidgets("BuildContext.getInheritedWidgetOfExactType doesn't create a dependency", (
WidgetTester tester,
) async {
var buildCount = 0;
final GlobalKey<void> inheritedKey = GlobalKey();
final notifier = ChangeNotifier();
addTearDown(notifier.dispose);
final Widget builder = Builder(
builder: (BuildContext context) {
expect(
context.getInheritedWidgetOfExactType<ChangeNotifierInherited>(),
equals(inheritedKey.currentWidget),
);
buildCount += 1;
return Container();
},
);
final Widget inner = ChangeNotifierInherited(
key: inheritedKey,
notifier: notifier,
child: builder,
);
await tester.pumpWidget(inner);
expect(buildCount, equals(1));
notifier.notifyListeners();
await tester.pumpWidget(inner);
expect(buildCount, equals(1));
});
testWidgets('initState() dependency on Inherited asserts', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/5491
var exceptionCaught = false;
final parent = TestInherited(
child: ExpectFail(() {
exceptionCaught = true;
}),
);
await tester.pumpWidget(parent);
expect(exceptionCaught, isTrue);
});
testWidgets('InheritedNotifier', (WidgetTester tester) async {
var buildCount = 0;
final notifier = ChangeNotifier();
addTearDown(notifier.dispose);
final Widget builder = Builder(
builder: (BuildContext context) {
context.dependOnInheritedWidgetOfExactType<ChangeNotifierInherited>();
buildCount += 1;
return Container();
},
);
final Widget inner = ChangeNotifierInherited(notifier: notifier, child: builder);
await tester.pumpWidget(inner);
expect(buildCount, equals(1));
await tester.pumpWidget(inner);
expect(buildCount, equals(1));
await tester.pump();
expect(buildCount, equals(1));
notifier.notifyListeners();
await tester.pump();
expect(buildCount, equals(2));
await tester.pumpWidget(inner);
expect(buildCount, equals(2));
await tester.pumpWidget(ChangeNotifierInherited(child: builder));
expect(buildCount, equals(3));
});
testWidgets('InheritedWidgets can trigger RenderObject updates', (WidgetTester tester) async {
var cardThemeData = const CardThemeData(color: Colors.white);
late StateSetter setState;
// Verifies that the "themed card" is rendered
// with the appropriate inherited theme data.
void expectCardToMatchTheme() {
final RenderPhysicalShape renderShape = tester.renderObject(find.byType(ThemedCard));
if (cardThemeData.color != null) {
expect(renderShape.color, cardThemeData.color);
}
if (cardThemeData.elevation != null) {
expect(renderShape.elevation, cardThemeData.elevation);
}
if (cardThemeData.shadowColor != null) {
expect(renderShape.shadowColor, cardThemeData.shadowColor);
}
if (cardThemeData.shape != null) {
final CustomClipper<Path>? clipper = renderShape.clipper;
expect(clipper, isA<ShapeBorderClipper>());
expect((clipper! as ShapeBorderClipper).shape, cardThemeData.shape);
}
if (cardThemeData.clipBehavior != null) {
expect(renderShape.clipBehavior, cardThemeData.clipBehavior);
}
}
await tester.pumpWidget(
StatefulBuilder(
builder: (BuildContext context, StateSetter stateSetter) {
setState = stateSetter;
return Theme(
data: ThemeData(cardTheme: cardThemeData),
child: const ThemedCard(),
);
},
),
);
expectCardToMatchTheme();
setState(() {
cardThemeData = const CardThemeData(
shape: BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))),
);
});
await tester.pump();
expectCardToMatchTheme();
setState(() {
cardThemeData = const CardThemeData(clipBehavior: Clip.hardEdge);
});
await tester.pump();
expectCardToMatchTheme();
setState(() {
cardThemeData = const CardThemeData(
elevation: 5.0,
shadowColor: Colors.blueGrey,
shape: ContinuousRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))),
clipBehavior: Clip.antiAliasWithSaveLayer,
);
});
await tester.pump();
expectCardToMatchTheme();
});
}