diff --git a/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/lib/main.dart b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/lib/main.dart index 97834ef244d..0cd24a06ba5 100644 --- a/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/lib/main.dart +++ b/add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view/lib/main.dart @@ -33,20 +33,24 @@ class _ResizeAppState extends State { mainAxisSize: MainAxisSize.min, children: [ for (int i = 0; i < _listSize; i++) - Container(color: HSVColor.fromAHSV(1, (10.0 * i), 1, 1).toColor(), height: 50, width: 200, + Container( + color: HSVColor.fromAHSV(1, (10.0 * i), 1, 1).toColor(), + height: 50, + width: 200, child: Center( child: Text( 'Flutter Widget $i', style: const TextStyle(fontSize: 16, color: Colors.black), ), - )), + ), + ), TextButton( onPressed: _addToList, child: Text('Listception!'), - ) + ), ], ), ), ); } -} \ No newline at end of file +} diff --git a/add_to_app/fullscreen/flutter_module_fullscreen/test/widget_test.dart b/add_to_app/fullscreen/flutter_module_fullscreen/test/widget_test.dart index af8421e5b83..8bd801b4ed4 100644 --- a/add_to_app/fullscreen/flutter_module_fullscreen/test/widget_test.dart +++ b/add_to_app/fullscreen/flutter_module_fullscreen/test/widget_test.dart @@ -21,27 +21,30 @@ class MockCounterModel extends ChangeNotifier implements CounterModel { } void main() { - testWidgets('MiniView smoke test', (tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget( - MaterialApp( - home: ChangeNotifierProvider.value( - value: MockCounterModel(), - child: const Contents(), + testWidgets( + 'MiniView smoke test', + (tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget( + MaterialApp( + home: ChangeNotifierProvider.value( + value: MockCounterModel(), + child: const Contents(), + ), ), - ), - ); + ); - // Verify that our counter starts at 0. - expect(find.text('Taps: 0'), findsOneWidget); - expect(find.text('Taps: 1'), findsNothing); + // Verify that our counter starts at 0. + expect(find.text('Taps: 0'), findsOneWidget); + expect(find.text('Taps: 1'), findsNothing); - // Tap the '+' icon and trigger a frame. - await tester.tap(find.text('Tap me!')); - await tester.pump(); + // Tap the '+' icon and trigger a frame. + await tester.tap(find.text('Tap me!')); + await tester.pump(); - // Verify that our counter has incremented. - expect(find.text('Taps: 0'), findsNothing); - expect(find.text('Taps: 1'), findsOneWidget); - }); + // Verify that our counter has incremented. + expect(find.text('Taps: 0'), findsNothing); + expect(find.text('Taps: 1'), findsOneWidget); + }, + ); } diff --git a/asset_transformation/grayscale_transformer/pubspec.yaml b/asset_transformation/grayscale_transformer/pubspec.yaml index 27153b02e16..ed59b389eb7 100644 --- a/asset_transformation/grayscale_transformer/pubspec.yaml +++ b/asset_transformation/grayscale_transformer/pubspec.yaml @@ -1,6 +1,7 @@ name: grayscale_transformer description: A sample command-line application. version: 1.0.0 +resolution: workspace environment: sdk: ^3.9.0-0 @@ -10,5 +11,5 @@ dependencies: image: ^4.1.7 dev_dependencies: - lints: ^5.0.0 + lints: ^6.0.0 test: ^1.24.0 diff --git a/dynamic_theme/lib/widgets/message_widget.dart b/dynamic_theme/lib/widgets/message_widget.dart index 74b45df1a8c..59603ca07b6 100644 --- a/dynamic_theme/lib/widgets/message_widget.dart +++ b/dynamic_theme/lib/widgets/message_widget.dart @@ -48,7 +48,7 @@ class MessageWidget extends StatelessWidget { child: Column( children: [ if (text case final text?) MarkdownBody(data: text), - if (image case final image?) image, + ?image, ], ), ), diff --git a/gemini_tasks/lib/widgets/message_widget.dart b/gemini_tasks/lib/widgets/message_widget.dart index 74b45df1a8c..59603ca07b6 100644 --- a/gemini_tasks/lib/widgets/message_widget.dart +++ b/gemini_tasks/lib/widgets/message_widget.dart @@ -48,7 +48,7 @@ class MessageWidget extends StatelessWidget { child: Column( children: [ if (text case final text?) MarkdownBody(data: text), - if (image case final image?) image, + ?image, ], ), ), diff --git a/material_3_demo/test/component_screen_test.dart b/material_3_demo/test/component_screen_test.dart index 7f088a7a7a1..16169f511c2 100644 --- a/material_3_demo/test/component_screen_test.dart +++ b/material_3_demo/test/component_screen_test.dart @@ -261,205 +261,6 @@ void main() { expect(box.localToGlobal(Offset.zero), const Offset(0.0, 3080.0)); }, ); - - testWidgets( - 'Material version switches between Material3 and Material2 when ' - 'the version icon is clicked', - (tester) async { - widgetSetup(tester, 450, windowHeight: 7000); - await tester.pumpWidget(const App()); - BuildContext defaultElevatedButton = tester.firstElement( - find.byType(ElevatedButton), - ); - BuildContext defaultIconButton = tester.firstElement( - find.byType(IconButton), - ); - BuildContext defaultFAB = tester.firstElement( - find.byType(FloatingActionButton), - ); - BuildContext defaultCard = tester.firstElement( - find.widgetWithText(Card, 'Elevated'), - ); - BuildContext defaultChip = tester.firstElement( - find.widgetWithText(ActionChip, 'Assist'), - ); - Finder dialog = find.text('Show dialog'); - await tester.tap(dialog); - await tester.pumpAndSettle(const Duration(microseconds: 500)); - BuildContext defaultAlertDialog = tester.element( - find.byType(AlertDialog), - ); - expect(Theme.of(defaultAlertDialog).useMaterial3, true); - Finder dismiss = find.text('Okay'); - await tester.tap(dismiss); - await tester.pumpAndSettle(const Duration(microseconds: 500)); - - expect(find.widgetWithIcon(AppBar, Icons.filter_2), findsOneWidget); - expect(find.widgetWithIcon(AppBar, Icons.filter_3), findsNothing); - expect(find.text('Material 3'), findsOneWidget); - expect(Theme.of(defaultElevatedButton).useMaterial3, true); - expect(Theme.of(defaultIconButton).useMaterial3, true); - expect(Theme.of(defaultFAB).useMaterial3, true); - expect(Theme.of(defaultCard).useMaterial3, true); - expect(Theme.of(defaultChip).useMaterial3, true); - - Finder appbarM3Icon = find.descendant( - of: find.byType(AppBar), - matching: find.widgetWithIcon(IconButton, Icons.filter_2), - ); - await tester.tap(appbarM3Icon); - await tester.pumpAndSettle(const Duration(microseconds: 500)); - BuildContext updatedElevatedButton = tester.firstElement( - find.byType(ElevatedButton), - ); - BuildContext updatedIconButton = tester.firstElement( - find.byType(IconButton), - ); - BuildContext updatedFAB = tester.firstElement( - find.byType(FloatingActionButton), - ); - BuildContext updatedCard = tester.firstElement(find.byType(Card)); - BuildContext updatedChip = tester.firstElement( - find.widgetWithText(ActionChip, 'Assist'), - ); - Finder updatedDialog = find.text('Show dialog'); - await tester.tap(updatedDialog); - await tester.pumpAndSettle(const Duration(microseconds: 500)); - BuildContext updatedAlertDialog = tester.firstElement( - find.byType(AlertDialog), - ); - expect(Theme.of(updatedAlertDialog).useMaterial3, false); - Finder updatedDismiss = find.text('Dismiss'); - await tester.tap(updatedDismiss); - await tester.pumpAndSettle(const Duration(microseconds: 500)); - - expect(find.widgetWithIcon(AppBar, Icons.filter_3), findsOneWidget); - expect(find.widgetWithIcon(AppBar, Icons.filter_2), findsNothing); - expect(find.text('Material 2'), findsOneWidget); - expect(Theme.of(updatedElevatedButton).useMaterial3, false); - expect(Theme.of(updatedIconButton).useMaterial3, false); - expect(Theme.of(updatedFAB).useMaterial3, false); - expect(Theme.of(updatedCard).useMaterial3, false); - expect(Theme.of(updatedChip).useMaterial3, false); - }, - ); - - testWidgets( - 'Other screens become Material2 mode after changing mode from ' - 'main screen', - (tester) async { - await tester.pumpWidget(const App()); - Finder appbarM2Icon = find.descendant( - of: find.byType(AppBar), - matching: find.widgetWithIcon(IconButton, Icons.filter_2), - ); - await tester.tap(appbarM2Icon); - Finder secondScreenIcon = find.descendant( - of: find.byType(NavigationBar), - matching: find.widgetWithIcon( - NavigationDestination, - Icons.format_paint_outlined, - ), - ); - await tester.tap(secondScreenIcon); - await tester.pumpAndSettle(const Duration(microseconds: 500)); - BuildContext lightThemeText = tester.element( - find.text('Light ColorScheme'), - ); - expect(Theme.of(lightThemeText).useMaterial3, false); - Finder thirdScreenIcon = find.descendant( - of: find.byType(NavigationBar), - matching: find.widgetWithIcon( - NavigationDestination, - Icons.text_snippet_outlined, - ), - ); - await tester.tap(thirdScreenIcon); - await tester.pumpAndSettle(const Duration(microseconds: 500)); - BuildContext displayLargeText = tester.element( - find.text('Display Large'), - ); - expect(Theme.of(displayLargeText).useMaterial3, false); - Finder fourthScreenIcon = find.descendant( - of: find.byType(NavigationBar), - matching: find.widgetWithIcon( - NavigationDestination, - Icons.invert_colors_on_outlined, - ), - ); - await tester.tap(fourthScreenIcon); - await tester.pumpAndSettle(const Duration(microseconds: 500)); - BuildContext material = tester.firstElement(find.byType(Material)); - expect(Theme.of(material).useMaterial3, false); - }, - ); - - testWidgets('Brightness mode switches between dark and light when' - 'the brightness icon is clicked', (tester) async { - await tester.pumpWidget(const App()); - Finder lightIcon = find.descendant( - of: find.byType(AppBar), - matching: find.widgetWithIcon(IconButton, Icons.light_mode_outlined), - ); - Finder darkIcon = find.descendant( - of: find.byType(AppBar), - matching: find.widgetWithIcon(IconButton, Icons.dark_mode_outlined), - ); - BuildContext appBar = tester.element(find.byType(AppBar).first); - BuildContext body = tester.firstElement(find.byType(Scaffold).first); - BuildContext navigationRail = tester.element( - find.widgetWithIcon(NavigationRail, Icons.format_paint_outlined), - ); - expect(darkIcon, findsOneWidget); - expect(lightIcon, findsNothing); - expect(Theme.of(appBar).brightness, Brightness.light); - expect(Theme.of(body).brightness, Brightness.light); - expect(Theme.of(navigationRail).brightness, Brightness.light); - await tester.tap(darkIcon); - await tester.pumpAndSettle(const Duration(microseconds: 500)); - - BuildContext appBar2 = tester.element(find.byType(AppBar).first); - BuildContext body2 = tester.element(find.byType(Scaffold).first); - BuildContext navigationRail2 = tester.element( - find.widgetWithIcon(NavigationRail, Icons.format_paint_outlined), - ); - - expect(darkIcon, findsNothing); - expect(lightIcon, findsOneWidget); - expect(Theme.of(appBar2).brightness, Brightness.dark); - expect(Theme.of(body2).brightness, Brightness.dark); - expect(Theme.of(navigationRail2).brightness, Brightness.dark); - }); - - testWidgets('Color theme changes when a color is selected from menu', ( - tester, - ) async { - Color m3BaseColor = const Color(0xff65558f); - await tester.pumpWidget(Container()); - await tester.pumpWidget(const App()); - await tester.pump(); - Finder menuIcon = find.descendant( - of: find.byType(AppBar), - matching: find.widgetWithIcon(IconButton, Icons.palette_outlined), - ); - BuildContext appBar = tester.element( - find.widgetWithIcon(AppBar, Icons.palette_outlined).first, - ); - BuildContext body = tester.element(find.byType(Scaffold).first); - - expect(Theme.of(appBar).primaryColor, m3BaseColor); - expect(Theme.of(body).primaryColor, m3BaseColor); - await tester.tap(menuIcon); - await tester.pumpAndSettle(); - await tester.tap(find.text('Blue').last); - await tester.pumpAndSettle(); - - BuildContext appBar2 = tester.element(find.byType(AppBar).first); - BuildContext body2 = tester.element(find.byType(Scaffold).first); - ThemeData expectedTheme = ThemeData(colorSchemeSeed: Colors.blue); - expect(Theme.of(appBar2).primaryColor, expectedTheme.primaryColor); - expect(Theme.of(body2).primaryColor, expectedTheme.primaryColor); - }); } void widgetSetup( diff --git a/pubspec.yaml b/pubspec.yaml index d61189cc580..ef0e64881cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ workspace: - android_splash_screen - animations - asset_transformation + - asset_transformation/grayscale_transformer - background_isolate_channels - code_sharing/client - code_sharing/server @@ -52,3 +53,12 @@ workspace: - tool - web_embedding/element_embedding_demo - web_embedding/ng-flutter/flutter + +skip_ci: + - add_to_app/android_view/flutter_module_using_plugin_android_view + - add_to_app/android_view/flutter_module_using_plugin_content_sizing_android_view + - add_to_app/books/flutter_module_books + - add_to_app/fullscreen/flutter_module_fullscreen + - add_to_app/multiple_flutters/multiple_flutters_module + - add_to_app/plugin/flutter_module_using_plugin + - add_to_app/prebuilt_module/flutter_module diff --git a/tool/ci_script.dart b/tool/ci_script.dart index 3274aba8c22..8aad8972557 100644 --- a/tool/ci_script.dart +++ b/tool/ci_script.dart @@ -14,10 +14,15 @@ Future main() async { exit(1); } + final skipCiList = pubspecYaml['skip_ci'] as YamlList?; + // pub workspace, only run 'get' once await _runCommand('flutter', ['pub', 'get'], workingDirectory: rootDir.path); - final packages = workspace.map((e) => e.toString()).toList(); + final packages = workspace + .where((e) => skipCiList == null || !skipCiList.contains(e)) + .map((e) => e.toString()) + .toList(); for (final package in packages) { final packagePath = path.join(rootDir.path, package); diff --git a/tool/ci_with_channel.dart b/tool/ci_with_channel.dart new file mode 100644 index 00000000000..47a39f16ddd --- /dev/null +++ b/tool/ci_with_channel.dart @@ -0,0 +1,137 @@ +import 'dart:io'; +import 'package:args/args.dart'; +import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; + +/// Do not use this with GitHub Actions. It is a convenience script for running +/// CI locally. +/// +/// Usage: dart tool/ci_with_channel.dart -c stable,beta + +Future main(List args) async { + final parser = ArgParser() + ..addFlag( + 'help', + abbr: 'h', + negatable: false, + help: 'Print this usage information.', + ) + ..addOption( + 'channels', + abbr: 'c', + help: + 'Comma-separated list of channels to run on (e.g., stable,beta,main)', + ); + + final results = parser.parse(args); + + if (results['help'] as bool) { + print(parser.usage); + return; + } + + final channelsArg = results['channels'] as String?; + + List channels; + if (channelsArg != null) { + channels = channelsArg.split(',').map((c) => c.trim()).toList(); + } else { + // Default to current channel if none specified + channels = []; + } + + final rootDir = Directory.current; + final originalChannel = await _getCurrentChannel(); + + try { + if (channels.isEmpty) { + print('Running on current channel: $originalChannel'); + await _runCI(rootDir); + } else { + for (final channel in channels) { + print('\n=== Switching to channel: $channel ==='); + await _runCommand('flutter', ['channel', channel]); + await _runCommand('flutter', ['doctor']); + await _runCI(rootDir); + } + } + } finally { + if (channels.isNotEmpty && originalChannel != null) { + print('\n=== Switching back to original channel: $originalChannel ==='); + await _runCommand('flutter', ['channel', originalChannel]); + } + } +} + +Future _getCurrentChannel() async { + final result = await Process.run('flutter', ['channel']); + if (result.exitCode != 0) { + return null; + } + final output = result.stdout as String; + final match = RegExp(r'\* (\w+)').firstMatch(output); + return match?.group(1); +} + +Future _runCI(Directory rootDir) async { + final pubspecFile = File(path.join(rootDir.path, 'pubspec.yaml')); + final pubspecContent = await pubspecFile.readAsString(); + final pubspecYaml = loadYaml(pubspecContent); + + final workspace = pubspecYaml['workspace'] as YamlList?; + if (workspace == null) { + print('No workspace found in pubspec.yaml'); + exit(1); + } + + // pub workspace, only run 'get' once + await _runCommand('flutter', ['pub', 'get'], workingDirectory: rootDir.path); + + final packages = workspace.map((e) => e.toString()).toList(); + + for (final package in packages) { + final packagePath = path.join(rootDir.path, package); + print('== Testing \'$package\' =='); + await _runCommand('dart', [ + 'analyze', + '--fatal-infos', + '--fatal-warnings', + ], workingDirectory: packagePath); + + await _runCommand('dart', ['format', '.'], workingDirectory: packagePath); + + if (await Directory(path.join(packagePath, 'test')).exists()) { + final packagePubspecFile = File(path.join(packagePath, 'pubspec.yaml')); + final packagePubspecContent = await packagePubspecFile.readAsString(); + if (packagePubspecContent.contains('flutter:')) { + await _runCommand('flutter', [ + 'test', + '--no-pub', + ], workingDirectory: packagePath); + } else { + await _runCommand('dart', ['test'], workingDirectory: packagePath); + } + } + } +} + +Future _runCommand( + String executable, + List arguments, { + String? workingDirectory, +}) async { + final process = await Process.start( + executable, + arguments, + workingDirectory: workingDirectory, + runInShell: true, + mode: ProcessStartMode.inheritStdio, + ); + final exitCode = await process.exitCode; + if (exitCode != 0) { + print( + 'Command "$executable ${arguments.join(' ')}" failed with exit code $exitCode in $workingDirectory', + ); + exit(exitCode); + } +}