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

429 lines
15 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/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'navigator_utils.dart';
void main() {
bool? lastFrameworkHandlesBack;
setUp(() async {
lastFrameworkHandlesBack = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
SystemChannels.platform,
(MethodCall methodCall) async {
if (methodCall.method == 'SystemNavigator.setFrameworkHandlesBack') {
expect(methodCall.arguments, isA<bool>());
lastFrameworkHandlesBack = methodCall.arguments as bool;
}
return;
},
);
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
'flutter/lifecycle',
const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()),
(ByteData? data) {},
);
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
SystemChannels.platform,
null,
);
});
testWidgets('toggling canPop on root route allows/prevents backs', (WidgetTester tester) async {
var canPop = false;
late StateSetter setState;
late BuildContext context;
await tester.pumpWidget(
MaterialApp(
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext buildContext) => Scaffold(
body: StatefulBuilder(
builder: (BuildContext buildContext, StateSetter stateSetter) {
context = buildContext;
setState = stateSetter;
return PopScope<Object?>(
canPop: canPop,
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text('Home/PopScope Page')],
),
),
);
},
),
),
},
),
);
expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop);
setState(() {
canPop = true;
});
await tester.pump();
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.bubble);
}, variant: TargetPlatformVariant.all());
testWidgets('pop scope can receive result', (WidgetTester tester) async {
Object? receivedResult;
final poppedResult = Object();
final nav = GlobalKey<NavigatorState>();
await tester.pumpWidget(
MaterialApp(
initialRoute: '/',
navigatorKey: nav,
home: Scaffold(
body: PopScope<Object?>(
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) {
receivedResult = result;
},
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text('Home/PopScope Page')],
),
),
),
),
),
);
nav.currentState!.maybePop(poppedResult);
await tester.pumpAndSettle();
expect(receivedResult, poppedResult);
}, variant: TargetPlatformVariant.all());
testWidgets(
'pop scope can have Object? generic type while route has stricter generic type',
(WidgetTester tester) async {
Object? receivedResult;
const poppedResult = 13;
final nav = GlobalKey<NavigatorState>();
await tester.pumpWidget(
MaterialApp(
initialRoute: '/',
navigatorKey: nav,
home: Scaffold(
body: PopScope<Object?>(
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) {
receivedResult = result;
},
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text('Home/PopScope Page')],
),
),
),
),
),
);
nav.currentState!.push(
MaterialPageRoute<int>(
builder: (BuildContext context) {
return Scaffold(
body: PopScope<Object?>(
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) {
receivedResult = result;
},
child: const Center(child: Text('new page')),
),
);
},
),
);
await tester.pumpAndSettle();
expect(find.text('new page'), findsOneWidget);
nav.currentState!.maybePop(poppedResult);
await tester.pumpAndSettle();
expect(receivedResult, poppedResult);
},
variant: TargetPlatformVariant.all(),
);
testWidgets('toggling canPop on secondary route allows/prevents backs', (
WidgetTester tester,
) async {
final nav = GlobalKey<NavigatorState>();
var canPop = true;
late StateSetter setState;
late BuildContext homeContext;
late BuildContext oneContext;
late bool lastPopSuccess;
await tester.pumpWidget(
MaterialApp(
navigatorKey: nav,
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
homeContext = context;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Home Page'),
TextButton(
onPressed: () {
Navigator.of(context).pushNamed('/one');
},
child: const Text('Next'),
),
],
),
),
);
},
'/one': (BuildContext context) => Scaffold(
body: StatefulBuilder(
builder: (BuildContext context, StateSetter stateSetter) {
oneContext = context;
setState = stateSetter;
return PopScope<Object?>(
canPop: canPop,
onPopInvokedWithResult: (bool didPop, Object? result) {
lastPopSuccess = didPop;
},
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text('PopScope Page')],
),
),
);
},
),
),
},
),
);
expect(find.text('Home Page'), findsOneWidget);
expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble);
await tester.tap(find.text('Next'));
await tester.pumpAndSettle();
expect(find.text('PopScope Page'), findsOneWidget);
expect(find.text('Home Page'), findsNothing);
expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
// When canPop is true, can use pop to go back.
nav.currentState!.maybePop();
await tester.pumpAndSettle();
expect(lastPopSuccess, true);
expect(find.text('Home Page'), findsOneWidget);
expect(find.text('PopScope Page'), findsNothing);
expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
await tester.tap(find.text('Next'));
await tester.pumpAndSettle();
expect(find.text('PopScope Page'), findsOneWidget);
expect(find.text('Home Page'), findsNothing);
expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
// When canPop is true, can use system back to go back.
await simulateSystemBack();
await tester.pumpAndSettle();
expect(lastPopSuccess, true);
expect(find.text('Home Page'), findsOneWidget);
expect(find.text('PopScope Page'), findsNothing);
expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
await tester.tap(find.text('Next'));
await tester.pumpAndSettle();
expect(find.text('PopScope Page'), findsOneWidget);
expect(find.text('Home Page'), findsNothing);
expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
setState(() {
canPop = false;
});
await tester.pump();
// When canPop is false, can't use pop to go back.
nav.currentState!.maybePop();
await tester.pumpAndSettle();
expect(lastPopSuccess, false);
expect(find.text('PopScope Page'), findsOneWidget);
expect(find.text('Home Page'), findsNothing);
expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.doNotPop);
// When canPop is false, can't use system back to go back.
await simulateSystemBack();
await tester.pumpAndSettle();
expect(lastPopSuccess, false);
expect(find.text('PopScope Page'), findsOneWidget);
expect(find.text('Home Page'), findsNothing);
expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.doNotPop);
// Toggle canPop back to true and back works again.
setState(() {
canPop = true;
});
await tester.pump();
nav.currentState!.maybePop();
await tester.pumpAndSettle();
expect(lastPopSuccess, true);
expect(find.text('Home Page'), findsOneWidget);
expect(find.text('PopScope Page'), findsNothing);
expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
await tester.tap(find.text('Next'));
await tester.pumpAndSettle();
expect(find.text('PopScope Page'), findsOneWidget);
expect(find.text('Home Page'), findsNothing);
expect(ModalRoute.of(oneContext)!.popDisposition, RoutePopDisposition.pop);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
await simulateSystemBack();
await tester.pumpAndSettle();
expect(lastPopSuccess, true);
expect(find.text('Home Page'), findsOneWidget);
expect(find.text('PopScope Page'), findsNothing);
expect(ModalRoute.of(homeContext)!.popDisposition, RoutePopDisposition.bubble);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
}, variant: TargetPlatformVariant.all());
testWidgets(
'removing PopScope from the tree removes its effect on navigation',
(WidgetTester tester) async {
var usePopScope = true;
late StateSetter setState;
late BuildContext context;
await tester.pumpWidget(
MaterialApp(
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext buildContext) => Scaffold(
body: StatefulBuilder(
builder: (BuildContext buildContext, StateSetter stateSetter) {
context = buildContext;
setState = stateSetter;
const Widget child = Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[Text('Home/PopScope Page')],
),
);
if (!usePopScope) {
return child;
}
return const PopScope<Object?>(canPop: false, child: child);
},
),
),
},
),
);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop);
setState(() {
usePopScope = false;
});
await tester.pump();
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.bubble);
},
variant: TargetPlatformVariant.all(),
);
testWidgets('identical PopScopes', (WidgetTester tester) async {
var usePopScope1 = true;
var usePopScope2 = true;
late StateSetter setState;
late BuildContext context;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: StatefulBuilder(
builder: (BuildContext buildContext, StateSetter stateSetter) {
context = buildContext;
setState = stateSetter;
return Column(
children: <Widget>[
if (usePopScope1) const PopScope<Object?>(canPop: false, child: Text('hello')),
if (usePopScope2) const PopScope<Object?>(canPop: false, child: Text('hello')),
],
);
},
),
),
),
);
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop);
// Despite being in the widget tree twice, the ModalRoute has only ever
// registered one PopScopeInterface for it. Removing one makes it think that
// both have been removed.
setState(() {
usePopScope1 = false;
});
await tester.pump();
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isTrue);
}
expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.doNotPop);
setState(() {
usePopScope2 = false;
});
await tester.pump();
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
expect(lastFrameworkHandlesBack, isFalse);
}
expect(ModalRoute.of(context)!.popDisposition, RoutePopDisposition.bubble);
}, variant: TargetPlatformVariant.all());
}