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

1969 lines
65 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 'dart:async';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
tearDown(() {
LicenseRegistry.reset();
});
testWidgets('Material3 has sentence case labels', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: Builder(
builder: (BuildContext context) => ElevatedButton(
onPressed: () {
showAboutDialog(context: context, useRootNavigator: false, applicationName: 'A');
},
child: const Text('Show About Dialog'),
),
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
expect(find.text('Close'), findsOneWidget);
expect(find.text('View licenses'), findsOneWidget);
});
testWidgets('Material2 - AboutListTile control test', (WidgetTester tester) async {
const logo = FlutterLogo();
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
title: 'Pirate app',
home: Scaffold(
appBar: AppBar(title: const Text('Home')),
drawer: Drawer(
child: ListView(
children: const <Widget>[
AboutListTile(
applicationVersion: '0.1.2',
applicationIcon: logo,
applicationLegalese: 'I am the very model of a modern major general.',
aboutBoxChildren: <Widget>[Text('About box')],
),
],
),
),
),
),
);
expect(find.text('About Pirate app'), findsNothing);
expect(find.text('0.1.2'), findsNothing);
expect(find.byWidget(logo), findsNothing);
expect(find.text('I am the very model of a modern major general.'), findsNothing);
expect(find.text('About box'), findsNothing);
await tester.tap(find.byType(IconButton));
await tester.pumpAndSettle();
expect(find.text('About Pirate app'), findsOneWidget);
expect(find.text('0.1.2'), findsNothing);
expect(find.byWidget(logo), findsNothing);
expect(find.text('I am the very model of a modern major general.'), findsNothing);
expect(find.text('About box'), findsNothing);
await tester.tap(find.text('About Pirate app'));
await tester.pumpAndSettle();
expect(find.text('About Pirate app'), findsOneWidget);
expect(find.text('0.1.2'), findsOneWidget);
expect(find.byWidget(logo), findsOneWidget);
expect(find.text('I am the very model of a modern major general.'), findsOneWidget);
expect(find.text('About box'), findsOneWidget);
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['Pirate package '], 'Pirate license'),
]);
});
await tester.tap(find.text('VIEW LICENSES'));
await tester.pumpAndSettle();
expect(find.text('Pirate app'), findsOneWidget);
expect(find.text('0.1.2'), findsOneWidget);
expect(find.byWidget(logo), findsOneWidget);
expect(find.text('I am the very model of a modern major general.'), findsOneWidget);
await tester.tap(find.text('Pirate package '));
await tester.pumpAndSettle();
expect(find.text('Pirate license'), findsOneWidget);
});
testWidgets('Material3 - AboutListTile control test', (WidgetTester tester) async {
const logo = FlutterLogo();
await tester.pumpWidget(
MaterialApp(
title: 'Pirate app',
home: Scaffold(
appBar: AppBar(title: const Text('Home')),
drawer: Drawer(
child: ListView(
children: const <Widget>[
AboutListTile(
applicationVersion: '0.1.2',
applicationIcon: logo,
applicationLegalese: 'I am the very model of a modern major general.',
aboutBoxChildren: <Widget>[Text('About box')],
),
],
),
),
),
),
);
expect(find.text('About Pirate app'), findsNothing);
expect(find.text('0.1.2'), findsNothing);
expect(find.byWidget(logo), findsNothing);
expect(find.text('I am the very model of a modern major general.'), findsNothing);
expect(find.text('About box'), findsNothing);
await tester.tap(find.byType(IconButton));
await tester.pumpAndSettle();
expect(find.text('About Pirate app'), findsOneWidget);
expect(find.text('0.1.2'), findsNothing);
expect(find.byWidget(logo), findsNothing);
expect(find.text('I am the very model of a modern major general.'), findsNothing);
expect(find.text('About box'), findsNothing);
await tester.tap(find.text('About Pirate app'));
await tester.pumpAndSettle();
expect(find.text('About Pirate app'), findsOneWidget);
expect(find.text('0.1.2'), findsOneWidget);
expect(find.byWidget(logo), findsOneWidget);
expect(find.text('I am the very model of a modern major general.'), findsOneWidget);
expect(find.text('About box'), findsOneWidget);
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['Pirate package '], 'Pirate license'),
]);
});
await tester.tap(find.text('View licenses'));
await tester.pumpAndSettle();
expect(find.text('Pirate app'), findsOneWidget);
expect(find.text('0.1.2'), findsOneWidget);
expect(find.byWidget(logo), findsOneWidget);
expect(find.text('I am the very model of a modern major general.'), findsOneWidget);
await tester.tap(find.text('Pirate package '));
await tester.pumpAndSettle();
expect(find.text('Pirate license'), findsOneWidget);
});
testWidgets('About box logic defaults to executable name for app name', (
WidgetTester tester,
) async {
await tester.pumpWidget(
const MaterialApp(
title: 'flutter_tester',
home: Material(child: AboutListTile()),
),
);
expect(find.text('About flutter_tester'), findsOneWidget);
});
testWidgets('LicensePage control test', (WidgetTester tester) async {
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['AAA'], 'BBB'),
]);
});
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['Another package'], 'Another license'),
]);
});
await tester.pumpWidget(const MaterialApp(home: Center(child: LicensePage())));
expect(find.text('AAA'), findsNothing);
expect(find.text('BBB'), findsNothing);
expect(find.text('Another package'), findsNothing);
expect(find.text('Another license'), findsNothing);
await tester.pumpAndSettle();
// Check for packages.
expect(find.text('AAA'), findsOneWidget);
expect(find.text('Another package'), findsOneWidget);
// Check license is displayed after entering into license page for 'AAA'.
await tester.tap(find.text('AAA'));
await tester.pumpAndSettle();
expect(find.text('BBB'), findsOneWidget);
/// Go back to list of packages.
await tester.pageBack();
await tester.pumpAndSettle();
/// Check license is displayed after entering into license page for
/// 'Another package'.
await tester.tap(find.text('Another package'));
await tester.pumpAndSettle();
expect(find.text('Another license'), findsOneWidget);
});
testWidgets('LicensePage control test with all properties', (WidgetTester tester) async {
const logo = FlutterLogo();
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['AAA'], 'BBB'),
]);
});
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['Another package'], 'Another license'),
]);
});
await tester.pumpWidget(
const MaterialApp(
title: 'Pirate app',
home: Center(
child: LicensePage(
applicationName: 'LicensePage test app',
applicationVersion: '0.1.2',
applicationIcon: logo,
applicationLegalese: 'I am the very model of a modern major general.',
),
),
),
);
expect(find.text('Pirate app'), findsNothing);
expect(find.text('LicensePage test app'), findsOneWidget);
expect(find.text('0.1.2'), findsOneWidget);
expect(find.byWidget(logo), findsOneWidget);
expect(find.text('I am the very model of a modern major general.'), findsOneWidget);
expect(find.text('AAA'), findsNothing);
expect(find.text('BBB'), findsNothing);
expect(find.text('Another package'), findsNothing);
expect(find.text('Another license'), findsNothing);
await tester.pumpAndSettle();
expect(find.text('Pirate app'), findsNothing);
expect(find.text('LicensePage test app'), findsOneWidget);
expect(find.text('0.1.2'), findsOneWidget);
expect(find.byWidget(logo), findsOneWidget);
expect(find.text('I am the very model of a modern major general.'), findsOneWidget);
// Check for packages.
expect(find.text('AAA'), findsOneWidget);
expect(find.text('Another package'), findsOneWidget);
// Check license is displayed after entering into license page for 'AAA'.
await tester.tap(find.text('AAA'));
await tester.pumpAndSettle();
expect(find.text('BBB'), findsOneWidget);
/// Go back to list of packages.
await tester.pageBack();
await tester.pumpAndSettle();
/// Check license is displayed after entering into license page for
/// 'Another package'.
await tester.tap(find.text('Another package'));
await tester.pumpAndSettle();
expect(find.text('Another license'), findsOneWidget);
});
testWidgets('Material2 - _PackageLicensePage title style without AppBarTheme', (
WidgetTester tester,
) async {
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['AAA'], 'BBB'),
]);
});
const titleTextStyle = TextStyle(fontSize: 20, color: Colors.black, inherit: false);
const subtitleTextStyle = TextStyle(fontSize: 15, color: Colors.red, inherit: false);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
useMaterial3: false,
primaryTextTheme: const TextTheme(
titleLarge: titleTextStyle,
titleSmall: subtitleTextStyle,
),
),
home: const Center(child: LicensePage()),
),
);
await tester.pumpAndSettle();
// Check for packages.
expect(find.text('AAA'), findsOneWidget);
// Check license is displayed after entering into license page for 'AAA'.
await tester.tap(find.text('AAA'));
await tester.pumpAndSettle();
// Check for titles style.
final Text title = tester.widget(find.text('AAA'));
expect(title.style, titleTextStyle);
final Text subtitle = tester.widget(find.text('1 license.'));
expect(subtitle.style, subtitleTextStyle);
});
testWidgets('Material3 - _PackageLicensePage title style without AppBarTheme', (
WidgetTester tester,
) async {
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['AAA'], 'BBB'),
]);
});
const titleTextStyle = TextStyle(fontSize: 20, color: Colors.black, inherit: false);
const subtitleTextStyle = TextStyle(fontSize: 15, color: Colors.red, inherit: false);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
textTheme: const TextTheme(titleLarge: titleTextStyle, titleSmall: subtitleTextStyle),
),
home: const Center(child: LicensePage()),
),
);
await tester.pumpAndSettle();
// Check for packages.
expect(find.text('AAA'), findsOneWidget);
// Check license is displayed after entering into license page for 'AAA'.
await tester.tap(find.text('AAA'));
await tester.pumpAndSettle();
// Check for titles style.
final Text title = tester.widget(find.text('AAA'));
expect(title.style, titleTextStyle);
final Text subtitle = tester.widget(find.text('1 license.'));
expect(subtitle.style, subtitleTextStyle);
});
testWidgets('_PackageLicensePage title style with AppBarTheme', (WidgetTester tester) async {
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['AAA'], 'BBB'),
]);
});
const titleTextStyle = TextStyle(fontSize: 20, color: Colors.indigo);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
// Not used because appBarTheme is prioritized.
primaryTextTheme: const TextTheme(
titleLarge: TextStyle(fontSize: 12, color: Colors.grey),
titleSmall: TextStyle(fontSize: 10, color: Colors.grey),
),
appBarTheme: const AppBarTheme(
titleTextStyle: titleTextStyle,
foregroundColor: Colors.indigo,
),
),
home: const Center(child: LicensePage()),
),
);
await tester.pumpAndSettle();
// Check for packages.
expect(find.text('AAA'), findsOneWidget);
// Check license is displayed after entering into license page for 'AAA'.
await tester.tap(find.text('AAA'));
await tester.pumpAndSettle();
// Check for titles style.
final Text title = tester.widget(find.text('AAA'));
expect(title.style, titleTextStyle);
});
testWidgets('Material2 - LicensePage respects the notch', (WidgetTester tester) async {
const safeareaPadding = 27.0;
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['ABC'], 'DEF'),
]);
});
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: const MediaQuery(
data: MediaQueryData(padding: EdgeInsets.all(safeareaPadding)),
child: LicensePage(),
),
),
);
await tester.pumpAndSettle();
// The position of the top left of app bar title should indicate whether
// the safe area is sufficiently respected.
expect(
tester.getTopLeft(find.text('Licenses')),
const Offset(16.0 + safeareaPadding, 18.0 + safeareaPadding),
);
});
testWidgets('Material3 - LicensePage respects the notch', (WidgetTester tester) async {
const safeareaPadding = 27.0;
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['ABC'], 'DEF'),
]);
});
await tester.pumpWidget(
const MaterialApp(
home: MediaQuery(
data: MediaQueryData(padding: EdgeInsets.all(safeareaPadding)),
child: LicensePage(),
),
),
);
await tester.pumpAndSettle();
// The position of the top left of app bar title should indicate whether
// the safe area is sufficiently respected.
expect(
tester.getTopLeft(find.text('Licenses')),
const Offset(16.0 + safeareaPadding, 14.0 + safeareaPadding),
);
});
testWidgets('LicensePage returns early if unmounted', (WidgetTester tester) async {
final licenseCompleter = Completer<LicenseEntry>();
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromFuture(licenseCompleter.future);
});
await tester.pumpWidget(const MaterialApp(home: LicensePage()));
await tester.pump();
await tester.pumpWidget(const MaterialApp(home: Placeholder()));
await tester.pumpAndSettle();
final licenseEntry = FakeLicenseEntry();
licenseCompleter.complete(licenseEntry);
expect(licenseEntry.packagesCalled, false);
});
testWidgets('LicensePage returns late if unmounted', (WidgetTester tester) async {
final licenseCompleter = Completer<LicenseEntry>();
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromFuture(licenseCompleter.future);
});
await tester.pumpWidget(const MaterialApp(home: LicensePage()));
await tester.pump();
final licenseEntry = FakeLicenseEntry();
licenseCompleter.complete(licenseEntry);
await tester.pumpWidget(const MaterialApp(home: Placeholder()));
await tester.pumpAndSettle();
expect(licenseEntry.packagesCalled, true);
});
testWidgets('LicensePage logic defaults to executable name for app name', (
WidgetTester tester,
) async {
await tester.pumpWidget(
const MaterialApp(
title: 'flutter_tester',
home: Material(child: LicensePage()),
),
);
expect(find.text('flutter_tester'), findsOneWidget);
});
testWidgets('AboutListTile dense property is applied', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(child: Center(child: AboutListTile())),
),
);
Rect tileRect = tester.getRect(find.byType(AboutListTile));
expect(tileRect.height, 56.0);
await tester.pumpWidget(
const MaterialApp(
home: Material(child: Center(child: AboutListTile(dense: false))),
),
);
tileRect = tester.getRect(find.byType(AboutListTile));
expect(tileRect.height, 56.0);
await tester.pumpWidget(
const MaterialApp(
home: Material(child: Center(child: AboutListTile(dense: true))),
),
);
tileRect = tester.getRect(find.byType(AboutListTile));
expect(tileRect.height, 48.0);
});
testWidgets('showLicensePage uses nested navigator by default', (WidgetTester tester) async {
final rootObserver = LicensePageObserver();
final nestedObserver = LicensePageObserver();
await tester.pumpWidget(
MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
initialRoute: '/',
onGenerateRoute: (_) {
return PageRouteBuilder<dynamic>(
pageBuilder: (_, _, _) => Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, _, _) {
return ElevatedButton(
onPressed: () {
showLicensePage(context: context, applicationName: 'A');
},
child: const Text('Show License Page'),
);
},
);
},
),
);
},
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
expect(rootObserver.licensePageCount, 0);
expect(nestedObserver.licensePageCount, 1);
});
testWidgets('showLicensePage uses root navigator if useRootNavigator is true', (
WidgetTester tester,
) async {
final rootObserver = LicensePageObserver();
final nestedObserver = LicensePageObserver();
await tester.pumpWidget(
MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
initialRoute: '/',
onGenerateRoute: (_) {
return PageRouteBuilder<dynamic>(
pageBuilder: (_, _, _) => Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return PageRouteBuilder<dynamic>(
pageBuilder: (BuildContext context, _, _) {
return ElevatedButton(
onPressed: () {
showLicensePage(
context: context,
useRootNavigator: true,
applicationName: 'A',
);
},
child: const Text('Show License Page'),
);
},
);
},
),
);
},
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
expect(rootObserver.licensePageCount, 1);
expect(nestedObserver.licensePageCount, 0);
});
group('Barrier dismissible', () {
late AboutDialogObserver rootObserver;
setUp(() {
rootObserver = AboutDialogObserver();
});
testWidgets('Barrier is dismissible with default parameter', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ElevatedButton(
child: const Text('X'),
onPressed: () => showAboutDialog(context: context),
);
},
),
),
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
expect(rootObserver.dialogCount, 1);
// Tap on the barrier.
await tester.tapAt(const Offset(10.0, 10.0));
await tester.pumpAndSettle();
expect(rootObserver.dialogCount, 0);
});
testWidgets('Barrier is not dismissible with barrierDismissible is false', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ElevatedButton(
child: const Text('X'),
onPressed: () => showAboutDialog(context: context, barrierDismissible: false),
);
},
),
),
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
expect(rootObserver.dialogCount, 1);
// Tap on the barrier, which shouldn't do anything this time.
await tester.tapAt(const Offset(10.0, 10.0));
await tester.pumpAndSettle();
expect(rootObserver.dialogCount, 1);
});
});
testWidgets('Barrier color', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ElevatedButton(
child: const Text('X'),
onPressed: () => showAboutDialog(context: context),
);
},
),
),
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.black54);
// Dismiss the dialog.
await tester.tapAt(const Offset(10.0, 10.0));
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ElevatedButton(
child: const Text('X'),
onPressed: () => showAboutDialog(context: context, barrierColor: Colors.pink),
);
},
),
),
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, Colors.pink);
});
testWidgets('Barrier Label', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ElevatedButton(
child: const Text('X'),
onPressed: () => showAboutDialog(context: context, barrierLabel: 'Custom Label'),
);
},
),
),
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
expect(
tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).semanticsLabel,
'Custom Label',
);
});
testWidgets('showAboutDialog uses root navigator by default', (WidgetTester tester) async {
final rootObserver = AboutDialogObserver();
final nestedObserver = AboutDialogObserver();
await tester.pumpWidget(
MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return ElevatedButton(
onPressed: () {
showAboutDialog(context: context, applicationName: 'A');
},
child: const Text('Show About Dialog'),
);
},
);
},
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
expect(rootObserver.dialogCount, 1);
expect(nestedObserver.dialogCount, 0);
});
testWidgets('showAboutDialog uses nested navigator if useRootNavigator is false', (
WidgetTester tester,
) async {
final rootObserver = AboutDialogObserver();
final nestedObserver = AboutDialogObserver();
await tester.pumpWidget(
MaterialApp(
navigatorObservers: <NavigatorObserver>[rootObserver],
home: Navigator(
observers: <NavigatorObserver>[nestedObserver],
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return ElevatedButton(
onPressed: () {
showAboutDialog(
context: context,
useRootNavigator: false,
applicationName: 'A',
);
},
child: const Text('Show About Dialog'),
);
},
);
},
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
expect(rootObserver.dialogCount, 0);
expect(nestedObserver.dialogCount, 1);
});
group('showAboutDialog avoids overlapping display features', () {
testWidgets('default positioning', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: Builder(
builder: (BuildContext context) => ElevatedButton(
onPressed: () {
showAboutDialog(context: context, useRootNavigator: false, applicationName: 'A');
},
child: const Text('Show About Dialog'),
),
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
// By default it should place the dialog on the left screen
expect(tester.getTopLeft(find.byType(AboutDialog)), Offset.zero);
expect(tester.getBottomRight(find.byType(AboutDialog)), const Offset(390.0, 600.0));
});
testWidgets('positioning using anchorPoint', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: child!,
);
},
home: Builder(
builder: (BuildContext context) => ElevatedButton(
onPressed: () {
showAboutDialog(
context: context,
useRootNavigator: false,
applicationName: 'A',
anchorPoint: const Offset(1000, 0),
);
},
child: const Text('Show About Dialog'),
),
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
// The anchorPoint hits the right side of the display
expect(tester.getTopLeft(find.byType(AboutDialog)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(AboutDialog)), const Offset(800.0, 600.0));
});
testWidgets('positioning using Directionality', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
builder: (BuildContext context, Widget? child) {
return MediaQuery(
// Display has a vertical hinge down the middle
data: const MediaQueryData(
size: Size(800, 600),
displayFeatures: <DisplayFeature>[
DisplayFeature(
bounds: Rect.fromLTRB(390, 0, 410, 600),
type: DisplayFeatureType.hinge,
state: DisplayFeatureState.unknown,
),
],
),
child: Directionality(textDirection: TextDirection.rtl, child: child!),
);
},
home: Builder(
builder: (BuildContext context) => ElevatedButton(
onPressed: () {
showAboutDialog(context: context, useRootNavigator: false, applicationName: 'A');
},
child: const Text('Show About Dialog'),
),
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
// Since this is rtl, the first screen is the on the right
expect(tester.getTopLeft(find.byType(AboutDialog)), const Offset(410.0, 0.0));
expect(tester.getBottomRight(find.byType(AboutDialog)), const Offset(800.0, 600.0));
});
});
testWidgets("AboutListTile's child should not be offset when the icon is not specified.", (
WidgetTester tester,
) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(body: AboutListTile(child: Text('About'))),
),
);
expect(
find.descendant(of: find.byType(AboutListTile), matching: find.byType(Icon)),
findsNothing,
);
});
testWidgets("AboutDialog's contents are scrollable", (WidgetTester tester) async {
final Key contentKey = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<dynamic>(
builder: (BuildContext context) {
return ElevatedButton(
onPressed: () {
showAboutDialog(
context: context,
useRootNavigator: false,
applicationName: 'A',
children: <Widget>[
Container(key: contentKey, color: Colors.orange, height: 500),
],
);
},
child: const Text('Show About Dialog'),
);
},
);
},
),
),
);
await tester.tap(find.text('Show About Dialog'));
await tester.pumpAndSettle();
// Try dragging by the [AboutDialog]'s title.
RenderBox box = tester.renderObject(find.text('A'));
Offset originalOffset = box.localToGlobal(Offset.zero);
await tester.drag(find.byKey(contentKey), const Offset(0.0, -20.0));
expect(box.localToGlobal(Offset.zero), equals(originalOffset.translate(0.0, -20.0)));
// Try dragging by the additional children in contents.
box = tester.renderObject(find.byKey(contentKey));
originalOffset = box.localToGlobal(Offset.zero);
await tester.drag(find.byKey(contentKey), const Offset(0.0, -20.0));
expect(box.localToGlobal(Offset.zero), equals(originalOffset.translate(0.0, -20.0)));
});
testWidgets("Material2 - LicensePage's color must be same whether loading or done", (
WidgetTester tester,
) async {
const scaffoldColor = Color(0xFF123456);
const cardColor = Color(0xFF654321);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.light(
useMaterial3: false,
).copyWith(scaffoldBackgroundColor: scaffoldColor, cardColor: cardColor),
home: Scaffold(
body: Center(
child: Builder(
builder: (BuildContext context) => GestureDetector(
child: const Text('Show licenses'),
onTap: () {
showLicensePage(
context: context,
applicationName: 'MyApp',
applicationVersion: '1.0.0',
);
},
),
),
),
),
),
);
await tester.tap(find.text('Show licenses'));
await tester.pump();
await tester.pump();
// Check color when loading.
final List<Material> materialLoadings = tester
.widgetList<Material>(find.byType(Material))
.toList();
expect(materialLoadings.length, equals(4));
expect(materialLoadings[1].color, scaffoldColor);
expect(materialLoadings[2].color, cardColor);
await tester.pumpAndSettle();
// Check color when done.
expect(find.byKey(const ValueKey<ConnectionState>(ConnectionState.done)), findsOneWidget);
final List<Material> materialDones = tester
.widgetList<Material>(find.byType(Material))
.toList();
expect(materialDones.length, equals(3));
expect(materialDones[0].color, scaffoldColor);
expect(materialDones[1].color, cardColor);
});
testWidgets("Material3 - LicensePage's color must be same whether loading or done", (
WidgetTester tester,
) async {
const scaffoldColor = Color(0xFF123456);
const cardColor = Color(0xFF654321);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(scaffoldBackgroundColor: scaffoldColor, cardColor: cardColor),
home: Scaffold(
body: Center(
child: Builder(
builder: (BuildContext context) => GestureDetector(
child: const Text('Show licenses'),
onTap: () {
showLicensePage(
context: context,
applicationName: 'MyApp',
applicationVersion: '1.0.0',
);
},
),
),
),
),
),
);
await tester.tap(find.text('Show licenses'));
await tester.pump();
await tester.pump();
// Check color when loading.
final List<Material> materialLoadings = tester
.widgetList<Material>(find.byType(Material))
.toList();
expect(materialLoadings.length, equals(5));
expect(materialLoadings[1].color, scaffoldColor);
expect(materialLoadings[2].color, cardColor);
await tester.pumpAndSettle();
// Check color when done.
expect(find.byKey(const ValueKey<ConnectionState>(ConnectionState.done)), findsOneWidget);
final List<Material> materialDones = tester
.widgetList<Material>(find.byType(Material))
.toList();
expect(materialDones.length, equals(4));
expect(materialDones[0].color, scaffoldColor);
expect(materialDones[1].color, cardColor);
});
testWidgets(
'Conflicting scrollbars are not applied by ScrollBehavior to _PackageLicensePage',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/83819
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['AAA'], 'BBB'),
]);
});
await tester.pumpWidget(const MaterialApp(home: Center(child: LicensePage())));
await tester.pumpAndSettle();
// Check for packages.
expect(find.text('AAA'), findsOneWidget);
// Check license is displayed after entering into license page for 'AAA'.
await tester.tap(find.text('AAA'));
await tester.pumpAndSettle();
// The inherited ScrollBehavior should not apply Scrollbars since they are
// already built in to the widget.
switch (debugDefaultTargetPlatformOverride) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
expect(find.byType(CupertinoScrollbar), findsNothing);
case TargetPlatform.iOS:
expect(find.byType(CupertinoScrollbar), findsOneWidget);
case null:
break;
}
expect(find.byType(Scrollbar), findsOneWidget);
expect(find.byType(RawScrollbar), findsNothing);
},
variant: TargetPlatformVariant.all(),
);
testWidgets('ListView of license entries is primary', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/120710
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
LicenseEntryWithLineBreaks(
<String>['AAA'],
// Add enough content to scroll
List<String>.generate(500, (int index) => 'BBBB').join('\n'),
),
]);
});
await tester.pumpWidget(
MaterialApp(
title: 'Flutter Code Sample',
home: Scaffold(
body: Builder(
builder: (BuildContext context) => TextButton(
child: const Text('Show License Page'),
onPressed: () {
showLicensePage(context: context);
},
),
),
),
),
);
await tester.pumpAndSettle();
expect(find.text('Show License Page'), findsOneWidget);
await tester.tap(find.text('Show License Page'));
await tester.pumpAndSettle();
// Check for packages.
expect(find.text('AAA'), findsOneWidget);
// Check license is displayed after entering into license page for 'AAA'.
await tester.tap(find.text('AAA'));
await tester.pumpAndSettle();
// The inherited ScrollBehavior should not apply Scrollbars since they are
// already built in to the widget.
switch (debugDefaultTargetPlatformOverride) {
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
expect(find.byType(CupertinoScrollbar), findsNothing);
case TargetPlatform.iOS:
expect(find.byType(CupertinoScrollbar), findsOneWidget);
case null:
break;
}
expect(find.byType(Scrollbar), findsOneWidget);
expect(find.byType(RawScrollbar), findsNothing);
await tester.drag(find.byType(ListView), const Offset(0.0, 20.0));
await tester.pumpAndSettle(); // No exception triggered.
}, variant: TargetPlatformVariant.all());
testWidgets('LicensePage padding', (WidgetTester tester) async {
const logo = FlutterLogo();
await tester.pumpWidget(
const MaterialApp(
title: 'Pirate app',
home: Center(
child: LicensePage(
applicationName: 'LicensePage test app',
applicationIcon: logo,
applicationVersion: '0.1.2',
applicationLegalese: 'I am the very model of a modern major general.',
),
),
),
);
final Finder appName = find.text('LicensePage test app');
final Finder appIcon = find.byType(FlutterLogo);
final Finder appVersion = find.text('0.1.2');
final Finder appLegalese = find.text('I am the very model of a modern major general.');
final Finder appPowered = find.text('Powered by Flutter');
expect(appName, findsOneWidget);
expect(appIcon, findsOneWidget);
expect(appVersion, findsOneWidget);
expect(appLegalese, findsOneWidget);
expect(appPowered, findsOneWidget);
// Bottom padding is applied to the app version and app legalese text.
final double appNameBottomPadding =
tester.getTopLeft(appIcon).dy - tester.getBottomLeft(appName).dy;
expect(appNameBottomPadding, 0.0);
final double appIconBottomPadding =
tester.getTopLeft(appVersion).dy - tester.getBottomLeft(appIcon).dy;
expect(appIconBottomPadding, 0.0);
final double appVersionBottomPadding =
tester.getTopLeft(appLegalese).dy - tester.getBottomLeft(appVersion).dy;
expect(appVersionBottomPadding, 18.0);
final double appLegaleseBottomPadding =
tester.getTopLeft(appPowered).dy - tester.getBottomLeft(appLegalese).dy;
expect(appLegaleseBottomPadding, 18.0);
});
testWidgets('LicensePage has no extra padding between app icon and app powered text', (
WidgetTester tester,
) async {
// This is a regression test for https://github.com/flutter/flutter/issues/99559
const logo = FlutterLogo();
await tester.pumpWidget(
const MaterialApp(
title: 'Pirate app',
home: Center(child: LicensePage(applicationIcon: logo)),
),
);
final Finder appName = find.text('LicensePage test app');
final Finder appIcon = find.byType(FlutterLogo);
final Finder appVersion = find.text('0.1.2');
final Finder appLegalese = find.text('I am the very model of a modern major general.');
final Finder appPowered = find.text('Powered by Flutter');
expect(appName, findsNothing);
expect(appIcon, findsOneWidget);
expect(appVersion, findsNothing);
expect(appLegalese, findsNothing);
expect(appPowered, findsOneWidget);
// Padding between app icon and app powered text.
final double appIconBottomPadding =
tester.getTopLeft(appPowered).dy - tester.getBottomLeft(appIcon).dy;
expect(appIconBottomPadding, 18.0);
});
testWidgets('Material2 - Error handling test', (WidgetTester tester) async {
LicenseRegistry.addLicense(() => Stream<LicenseEntry>.error(Exception('Injected failure')));
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: const Material(child: AboutListTile()),
),
);
await tester.tap(find.byType(ListTile));
await tester.pump();
await tester.pump(const Duration(seconds: 2));
await tester.tap(find.text('VIEW LICENSES'));
await tester.pump();
await tester.pump(const Duration(seconds: 2));
final Finder finder = find.byWidgetPredicate(
(Widget widget) => widget.runtimeType.toString() == '_PackagesView',
);
// force the stream to complete (has to be done in a runAsync block since it's areal async process)
await tester.runAsync(() => (tester.firstState(finder) as dynamic).licenses as Future<dynamic>);
expect(tester.takeException().toString(), 'Exception: Injected failure');
await tester.pumpAndSettle();
expect(tester.takeException().toString(), 'Exception: Injected failure');
expect(find.text('Exception: Injected failure'), findsOneWidget);
});
testWidgets('Material3 - Error handling test', (WidgetTester tester) async {
LicenseRegistry.addLicense(() => Stream<LicenseEntry>.error(Exception('Injected failure')));
await tester.pumpWidget(const MaterialApp(home: Material(child: AboutListTile())));
await tester.tap(find.byType(ListTile));
await tester.pump();
await tester.pump(const Duration(seconds: 2));
await tester.tap(find.text('View licenses'));
await tester.pump();
await tester.pump(const Duration(seconds: 2));
final Finder finder = find.byWidgetPredicate(
(Widget widget) => widget.runtimeType.toString() == '_PackagesView',
);
// force the stream to complete (has to be done in a runAsync block since it's areal async process)
await tester.runAsync(() => (tester.firstState(finder) as dynamic).licenses as Future<dynamic>);
expect(tester.takeException().toString(), 'Exception: Injected failure');
await tester.pumpAndSettle();
expect(tester.takeException().toString(), 'Exception: Injected failure');
expect(find.text('Exception: Injected failure'), findsOneWidget);
});
testWidgets('Material2 - LicensePage master view layout position - ltr', (
WidgetTester tester,
) async {
const TextDirection textDirection = TextDirection.ltr;
const defaultSize = Size(800.0, 600.0);
const wideSize = Size(1200.0, 600.0);
const title = 'License ABC';
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['ABC'], 'DEF'),
]);
});
addTearDown(() async {
await tester.binding.setSurfaceSize(null);
});
// Configure to show the default layout.
await tester.binding.setSurfaceSize(defaultSize);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
title: title,
home: const Scaffold(
body: Directionality(textDirection: textDirection, child: LicensePage()),
),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
// If the layout width is less than 840.0 pixels, nested layout is
// used which positions license page title at the top center.
Offset titleOffset = tester.getCenter(find.text(title));
expect(titleOffset, Offset(defaultSize.width / 2, 92.0));
expect(tester.getCenter(find.byType(ListView)), Offset(defaultSize.width / 2, 328.0));
// Configure a wide window to show the lateral UI.
await tester.binding.setSurfaceSize(wideSize);
await tester.pumpWidget(
const MaterialApp(
title: title,
home: Scaffold(
body: Directionality(textDirection: textDirection, child: LicensePage()),
),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
// If the layout width is greater than 840.0 pixels, lateral UI layout
// is used which positions license page title and packageList
// at the top left.
titleOffset = tester.getTopRight(find.text(title));
expect(titleOffset, const Offset(292.0, 136.0));
expect(titleOffset.dx, lessThan(wideSize.width - 320)); // Default master view width is 320.0.
expect(tester.getCenter(find.byType(ListView)), const Offset(160, 356));
});
testWidgets('Material3 - LicensePage master view layout position - ltr', (
WidgetTester tester,
) async {
const TextDirection textDirection = TextDirection.ltr;
const defaultSize = Size(800.0, 600.0);
const wideSize = Size(1200.0, 600.0);
const title = 'License ABC';
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['ABC'], 'DEF'),
]);
});
addTearDown(() async {
await tester.binding.setSurfaceSize(null);
});
// Configure to show the default layout.
await tester.binding.setSurfaceSize(defaultSize);
await tester.pumpWidget(
const MaterialApp(
title: title,
home: Scaffold(
body: Directionality(textDirection: textDirection, child: LicensePage()),
),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
// If the layout width is less than 840.0 pixels, nested layout is
// used which positions license page title at the top center.
Offset titleOffset = tester.getCenter(find.text(title));
expect(titleOffset, Offset(defaultSize.width / 2, 96.0));
expect(tester.getCenter(find.byType(ListView)), Offset(defaultSize.width / 2, 328.0));
// Configure a wide window to show the lateral UI.
await tester.binding.setSurfaceSize(wideSize);
await tester.pumpWidget(
const MaterialApp(
title: title,
home: Scaffold(
body: Directionality(textDirection: textDirection, child: LicensePage()),
),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
// If the layout width is greater than 840.0 pixels, lateral UI layout
// is used which positions license page title and packageList
// at the top left.
titleOffset = tester.getTopRight(find.text(title));
expect(titleOffset, const Offset(292.0, 136.0));
expect(titleOffset.dx, lessThan(wideSize.width - 320)); // Default master view width is 320.0.
expect(tester.getCenter(find.byType(ListView)), const Offset(160, 356));
});
testWidgets('Material2 - LicensePage master view layout position - rtl', (
WidgetTester tester,
) async {
const TextDirection textDirection = TextDirection.rtl;
const defaultSize = Size(800.0, 600.0);
const wideSize = Size(1200.0, 600.0);
const title = 'License ABC';
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['ABC'], 'DEF'),
]);
});
addTearDown(() async {
await tester.binding.setSurfaceSize(null);
});
// Configure to show the default layout.
await tester.binding.setSurfaceSize(defaultSize);
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
title: title,
home: const Scaffold(
body: Directionality(textDirection: textDirection, child: LicensePage()),
),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
// If the layout width is less than 840.0 pixels, nested layout is
// used which positions license page title at the top center.
Offset titleOffset = tester.getCenter(find.text(title));
expect(titleOffset, Offset(defaultSize.width / 2, 92.0));
expect(tester.getCenter(find.byType(ListView)), Offset(defaultSize.width / 2, 328.0));
// Configure a wide window to show the lateral UI.
await tester.binding.setSurfaceSize(wideSize);
await tester.pumpWidget(
const MaterialApp(
title: title,
home: Scaffold(
body: Directionality(textDirection: textDirection, child: LicensePage()),
),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
// If the layout width is greater than 840.0 pixels, lateral UI layout
// is used which positions license page title and packageList
// at the top right.
titleOffset = tester.getTopLeft(find.text(title));
expect(titleOffset, const Offset(908.0, 136.0));
expect(
titleOffset.dx,
greaterThan(wideSize.width - 320),
); // Default master view width is 320.0.
expect(tester.getCenter(find.byType(ListView)), const Offset(1040.0, 356.0));
});
testWidgets('Material3 - LicensePage master view layout position - rtl', (
WidgetTester tester,
) async {
const TextDirection textDirection = TextDirection.rtl;
const defaultSize = Size(800.0, 600.0);
const wideSize = Size(1200.0, 600.0);
const title = 'License ABC';
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['ABC'], 'DEF'),
]);
});
addTearDown(() async {
await tester.binding.setSurfaceSize(null);
});
// Configure to show the default layout.
await tester.binding.setSurfaceSize(defaultSize);
await tester.pumpWidget(
const MaterialApp(
title: title,
home: Scaffold(
body: Directionality(textDirection: textDirection, child: LicensePage()),
),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
// If the layout width is less than 840.0 pixels, nested layout is
// used which positions license page title at the top center.
Offset titleOffset = tester.getCenter(find.text(title));
expect(titleOffset, Offset(defaultSize.width / 2, 96.0));
expect(tester.getCenter(find.byType(ListView)), Offset(defaultSize.width / 2, 328.0));
// Configure a wide window to show the lateral UI.
await tester.binding.setSurfaceSize(wideSize);
await tester.pumpWidget(
const MaterialApp(
title: title,
home: Scaffold(
body: Directionality(textDirection: textDirection, child: LicensePage()),
),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
// If the layout width is greater than 840.0 pixels, lateral UI layout
// is used which positions license page title and packageList
// at the top right.
titleOffset = tester.getTopLeft(find.text(title));
expect(titleOffset, const Offset(908.0, 136.0));
expect(
titleOffset.dx,
greaterThan(wideSize.width - 320),
); // Default master view width is 320.0.
expect(tester.getCenter(find.byType(ListView)), const Offset(1040.0, 356.0));
});
testWidgets('License page title in lateral UI does not use AppBarTheme.foregroundColor', (
WidgetTester tester,
) async {
// This is a regression test for https://github.com/flutter/flutter/issues/108991
final theme = ThemeData(appBarTheme: const AppBarTheme(foregroundColor: Color(0xFFFFFFFF)));
const title = 'License ABC';
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['ABC'], 'DEF'),
]);
});
addTearDown(() async {
await tester.binding.setSurfaceSize(null);
});
// Configure a wide window to show the lateral UI.
await tester.binding.setSurfaceSize(const Size(1200.0, 600.0));
await tester.pumpWidget(
MaterialApp(
title: title,
theme: theme,
home: const Scaffold(body: LicensePage()),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
final renderParagraph = tester.renderObject(find.text('ABC').last) as RenderParagraph;
// License page title should not use AppBarTheme's foregroundColor.
expect(renderParagraph.text.style!.color, isNot(theme.appBarTheme.foregroundColor));
// License page title in the lateral UI uses default text style color.
expect(renderParagraph.text.style!.color, theme.textTheme.titleLarge!.color);
});
testWidgets('License page default title text color in the nested UI', (
WidgetTester tester,
) async {
// This is a regression test for https://github.com/flutter/flutter/issues/108991
final theme = ThemeData();
const title = 'License ABC';
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['ABC'], 'DEF'),
]);
});
await tester.pumpWidget(
MaterialApp(
title: title,
theme: theme,
home: const Scaffold(body: LicensePage()),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
// Currently in the master view.
expect(find.text('License ABC'), findsOneWidget);
// Navigate to the license page.
await tester.tap(find.text('ABC'));
await tester.pumpAndSettle();
// Master view is no longer visible.
expect(find.text('License ABC'), findsNothing);
final renderParagraph = tester.renderObject(find.text('ABC').first) as RenderParagraph;
expect(renderParagraph.text.style!.color, theme.textTheme.titleLarge!.color);
});
group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests
// can be deleted.
testWidgets('License page default title text color in the nested UI', (
WidgetTester tester,
) async {
// This is a regression test for https://github.com/flutter/flutter/issues/108991
final theme = ThemeData(useMaterial3: false);
const title = 'License ABC';
LicenseRegistry.addLicense(() {
return Stream<LicenseEntry>.fromIterable(<LicenseEntry>[
const LicenseEntryWithLineBreaks(<String>['ABC'], 'DEF'),
]);
});
await tester.pumpWidget(
MaterialApp(
title: title,
theme: theme,
home: const Scaffold(body: LicensePage()),
),
);
await tester.pumpAndSettle(); // Finish rendering the page.
// Currently in the master view.
expect(find.text('License ABC'), findsOneWidget);
// Navigate to the license page.
await tester.tap(find.text('ABC'));
await tester.pumpAndSettle();
// Master view is no longer visible.
expect(find.text('License ABC'), findsNothing);
final renderParagraph = tester.renderObject(find.text('ABC').first) as RenderParagraph;
expect(renderParagraph.text.style!.color, theme.primaryTextTheme.titleLarge!.color);
});
});
testWidgets('Adaptive AboutDialog shows correct widget on each platform', (
WidgetTester tester,
) async {
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: platform),
home: const Material(
child: Center(child: ElevatedButton(onPressed: null, child: Text('Go'))),
),
),
);
final BuildContext context = tester.element(find.text('Go'));
showAdaptiveAboutDialog(
context: context,
applicationIcon: const Icon(Icons.abc),
applicationName: 'Test',
applicationVersion: '1.0.0',
applicationLegalese: 'Application Legalese',
children: <Widget>[const Text('Test1')],
);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.byType(CupertinoDialogAction), findsWidgets);
}
for (final platform in <TargetPlatform>[
TargetPlatform.android,
TargetPlatform.fuchsia,
TargetPlatform.linux,
TargetPlatform.windows,
]) {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: platform),
home: const Material(
child: Center(child: ElevatedButton(onPressed: null, child: Text('Go'))),
),
),
);
final BuildContext context = tester.element(find.text('Go'));
showAboutDialog(
context: context,
applicationIcon: const Icon(Icons.abc),
applicationName: 'Test',
applicationVersion: '1.0.0',
);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.byType(CupertinoDialogAction), findsNothing);
}
});
testWidgets('Adaptive AboutDialog closes correctly on each platform', (
WidgetTester tester,
) async {
for (final platform in <TargetPlatform>[TargetPlatform.iOS, TargetPlatform.macOS]) {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: platform),
home: const Material(
child: Center(child: ElevatedButton(onPressed: null, child: Text('Go'))),
),
),
);
final BuildContext context = tester.element(find.text('Go'));
showAdaptiveAboutDialog(
context: context,
applicationName: 'Test',
applicationVersion: '1.0.0',
);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.byType(CupertinoDialogAction), findsWidgets);
await tester.tap(find.text('Close'));
await tester.pumpAndSettle();
expect(find.byType(CupertinoAlertDialog), findsNothing);
}
for (final platform in <TargetPlatform>[
TargetPlatform.android,
TargetPlatform.fuchsia,
TargetPlatform.linux,
TargetPlatform.windows,
]) {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(platform: platform),
home: const Material(
child: Center(child: ElevatedButton(onPressed: null, child: Text('Go'))),
),
),
);
final BuildContext context = tester.element(find.text('Go'));
showAdaptiveAboutDialog(
context: context,
applicationName: 'Test',
applicationVersion: '1.0.0',
);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.byType(TextButton), findsWidgets);
await tester.tap(find.text('Close'));
await tester.pumpAndSettle();
expect(find.byType(AlertDialog), findsNothing);
}
});
testWidgets('showLicensePage inherits ambient Theme', (WidgetTester tester) async {
final theme = ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: const Color(0XFFFF0000)));
await tester.pumpWidget(
MaterialApp(
home: Theme(
data: theme,
child: Builder(
builder: (BuildContext context) => ElevatedButton(
onPressed: () {
showAboutDialog(
context: context,
applicationName: 'Sample Test',
applicationVersion: 'v1.0.0', // Version of the app
);
},
child: const Text('Show About Dialog'),
),
),
),
),
);
// Open the dialog.
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
await tester.tap(find.text('View licenses'));
await tester.pumpAndSettle();
final ThemeData licensePageTheme = Theme.of(tester.element(find.text('Powered by Flutter')));
expect(theme.colorScheme.primary, licensePageTheme.colorScheme.primary);
});
testWidgets('AboutDialog renders at zero area', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Center(
child: SizedBox.shrink(
child: Scaffold(body: AboutDialog(children: <Widget>[Text('X')])),
),
),
),
);
final Finder xText = find.text('X');
expect(tester.getSize(xText).isEmpty, isTrue);
});
testWidgets('AboutListTile renders at zero area', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Center(
child: SizedBox.shrink(
child: Scaffold(body: AboutListTile(child: Text('X'))),
),
),
),
);
final Finder xText = find.text('X');
expect(tester.getSize(xText).isEmpty, isTrue);
});
testWidgets('LicensePage renders at zero area', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Center(
child: SizedBox.shrink(
child: Scaffold(body: LicensePage(applicationName: 'X')),
),
),
),
);
final Finder xText = find.text('X');
expect(tester.getSize(xText).isEmpty, isTrue);
});
}
class FakeLicenseEntry extends LicenseEntry {
FakeLicenseEntry();
bool get packagesCalled => _packagesCalled;
bool _packagesCalled = false;
@override
Iterable<LicenseParagraph> paragraphs = <LicenseParagraph>[];
@override
Iterable<String> get packages {
_packagesCalled = true;
return <String>[];
}
}
class LicensePageObserver extends NavigatorObserver {
int licensePageCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
if (route is MaterialPageRoute<dynamic>) {
licensePageCount++;
}
super.didPush(route, previousRoute);
}
}
class AboutDialogObserver extends NavigatorObserver {
int dialogCount = 0;
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
if (route is DialogRoute) {
dialogCount++;
}
super.didPush(route, previousRoute);
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
if (route is DialogRoute) {
dialogCount--;
}
super.didPop(route, previousRoute);
}
}