From c1ee381b183005c8b6e76e6a447a7f6022951b95 Mon Sep 17 00:00:00 2001 From: Valentin Vignal <32538273+ValentinVignal@users.noreply.github.com> Date: Thu, 14 Nov 2024 16:14:17 +0800 Subject: [PATCH] Reland Add test for dynamic_content_color.0.dart (#158547) Fixes https://github.com/flutter/flutter/issues/130459 Follow up of the reverted https://github.com/flutter/flutter/pull/158309 It adds a test for - `examples/api/lib/material/color_scheme/dynamic_content_color.0.dart` --- dev/bots/check_code_samples.dart | 42 +--- .../color_scheme/dynamic_content_color.0.dart | 50 +++-- .../dynamic_content_color.0_test.dart | 181 ++++++++++++++++++ 3 files changed, 220 insertions(+), 53 deletions(-) create mode 100644 examples/api/test/material/color_scheme/dynamic_content_color.0_test.dart diff --git a/dev/bots/check_code_samples.dart b/dev/bots/check_code_samples.dart index f365dc22ba..ae2cbe9174 100644 --- a/dev/bots/check_code_samples.dart +++ b/dev/bots/check_code_samples.dart @@ -142,29 +142,18 @@ class SampleChecker { // Get a list of the filenames that were not found in the source files. final List missingFilenames = checkForMissingLinks(exampleFilenames, exampleLinks); - // Get a list of any tests that are missing, as well as any that used to be - // missing, but have been implemented. - final (List missingTests, List noLongerMissing) = checkForMissingTests(exampleFilenames); + // Get a list of any tests that are missing. + final List missingTests = checkForMissingTests(exampleFilenames); // Remove any that we know are exceptions (examples that aren't expected to be // linked into any source files). These are typically template files used to // generate new examples. missingFilenames.removeWhere((String file) => _knownUnlinkedExamples.contains(file)); - if (missingFilenames.isEmpty && missingTests.isEmpty && noLongerMissing.isEmpty && malformedLinks.isEmpty) { + if (missingFilenames.isEmpty && missingTests.isEmpty && malformedLinks.isEmpty) { return true; } - if (noLongerMissing.isNotEmpty) { - final StringBuffer buffer = StringBuffer('The following tests have been implemented! Huzzah!:\n'); - for (final File name in noLongerMissing) { - buffer.writeln(' ${getRelativePath(name)}'); - } - buffer.writeln('However, they now need to be removed from the _knownMissingTests'); - buffer.write('list in the script $_scriptLocation.'); - foundError(buffer.toString().split('\n')); - } - if (missingTests.isNotEmpty) { final StringBuffer buffer = StringBuffer('The following example test files are missing:\n'); for (final File name in missingTests) { @@ -279,35 +268,14 @@ class SampleChecker { return '${path.join(testPath, path.basenameWithoutExtension(example.path))}_test.dart'; } - (List, List) checkForMissingTests(List exampleFilenames) { + List checkForMissingTests(List exampleFilenames) { final List missingTests = []; - final List noLongerMissingTests = []; for (final File example in exampleFilenames) { final File testFile = filesystem.file(getTestNameForExample(example, examples)); - final String name = path.relative(testFile.absolute.path, from: flutterRoot.absolute.path); if (!testFile.existsSync()) { missingTests.add(testFile); - } else if (_knownMissingTests.contains(name.replaceAll(r'\', '/'))) { - noLongerMissingTests.add(testFile); } } - // Skip any that we know are missing. - missingTests.removeWhere( - (File test) { - final String name = path.relative(test.absolute.path, from: flutterRoot.absolute.path).replaceAll(r'\', '/'); - return _knownMissingTests.contains(name); - }, - ); - return (missingTests, noLongerMissingTests); + return missingTests; } } - -// These tests are known to be missing. They should all eventually be -// implemented, but until they are we allow them, so that we can catch any new -// examples that are added without tests. -// -// TODO(gspencergoog): implement the missing tests. -// See https://github.com/flutter/flutter/issues/130459 -final Set _knownMissingTests = { - 'examples/api/test/material/color_scheme/dynamic_content_color.0_test.dart', -}; diff --git a/examples/api/lib/material/color_scheme/dynamic_content_color.0.dart b/examples/api/lib/material/color_scheme/dynamic_content_color.0.dart index 2dde166aba..d88896a746 100644 --- a/examples/api/lib/material/color_scheme/dynamic_content_color.0.dart +++ b/examples/api/lib/material/color_scheme/dynamic_content_color.0.dart @@ -9,26 +9,34 @@ import 'package:flutter/material.dart'; const Widget divider = SizedBox(height: 10); const double narrowScreenWidthThreshold = 400; -void main() => runApp(DynamicColorExample()); +void main() => runApp(const DynamicColorExample()); class DynamicColorExample extends StatefulWidget { - DynamicColorExample({super.key}); + const DynamicColorExample({ + this.loadColorScheme, + super.key, + }); - final List images = [ - const NetworkImage( + static const List images = [ + NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_1.png'), - const NetworkImage( + NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_2.png'), - const NetworkImage( + NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_3.png'), - const NetworkImage( + NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_4.png'), - const NetworkImage( + NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_5.png'), - const NetworkImage( + NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_6.png'), ]; + final Future Function( + ImageProvider provider, + Brightness brightness, + )? loadColorScheme; + @override State createState() => _DynamicColorExampleState(); } @@ -48,7 +56,7 @@ class _DynamicColorExampleState extends State { isLoading = true; currentColorScheme = const ColorScheme.light(); WidgetsBinding.instance.addPostFrameCallback((_) { - _updateImage(widget.images[selectedImage]); + _updateImage(DynamicColorExample.images[selectedImage]); isLoading = false; }); } @@ -105,7 +113,7 @@ class _DynamicColorExampleState extends State { onChanged: (bool value) { setState(() { isLight = value; - _updateImage(widget.images[selectedImage]); + _updateImage(DynamicColorExample.images[selectedImage]); }); }) ], @@ -120,7 +128,7 @@ class _DynamicColorExampleState extends State { divider, _imagesRow( context, - widget.images, + DynamicColorExample.images, colorScheme, ), divider, @@ -186,13 +194,23 @@ class _DynamicColorExampleState extends State { } Future _updateImage(ImageProvider provider) async { - final ColorScheme newColorScheme = await ColorScheme.fromImageProvider( - provider: provider, brightness: isLight ? Brightness.light : Brightness.dark); + final ColorScheme newColorScheme; + if (widget.loadColorScheme != null) { + newColorScheme = await widget.loadColorScheme!( + provider, + isLight ? Brightness.light : Brightness.dark, + ); + } else { + newColorScheme = await ColorScheme.fromImageProvider( + provider: provider, + brightness: isLight ? Brightness.light : Brightness.dark, + ); + } if (!mounted) { return; } setState(() { - selectedImage = widget.images.indexOf(provider); + selectedImage = DynamicColorExample.images.indexOf(provider); currentColorScheme = newColorScheme; }); } @@ -227,7 +245,7 @@ class _DynamicColorExampleState extends State { child: GestureDetector( onTap: () => _updateImage(image), child: Card( - color: widget.images.indexOf(image) == selectedImage + color: DynamicColorExample.images.indexOf(image) == selectedImage ? colorScheme.primaryContainer : colorScheme.surface, child: Padding( diff --git a/examples/api/test/material/color_scheme/dynamic_content_color.0_test.dart b/examples/api/test/material/color_scheme/dynamic_content_color.0_test.dart new file mode 100644 index 0000000000..e6d700cbfc --- /dev/null +++ b/examples/api/test/material/color_scheme/dynamic_content_color.0_test.dart @@ -0,0 +1,181 @@ +// 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:io'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/color_scheme/dynamic_content_color.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + final List<(ImageProvider, Brightness)> loadColorSchemeCalls = <(ImageProvider, Brightness)>[]; + + Future fakeColorSchemeLoader(ImageProvider provider, Brightness brightness) async { + loadColorSchemeCalls.add((provider, brightness)); + final int index = example.DynamicColorExample.images.indexOf(provider); + final int seedColor = 0xf * pow(0x10, index).toInt(); + return ColorScheme.fromSeed(seedColor: Color(seedColor), brightness: brightness); + } + + setUp(() { + loadColorSchemeCalls.clear(); + }); + + // The app being tested loads images via HTTP which the test + // framework defeats by default. + setUpAll(() { + HttpOverrides.global = null; + }); + + testWidgets('The content is visible', (WidgetTester tester) async { + await tester.pumpWidget( + example.DynamicColorExample( + loadColorScheme: fakeColorSchemeLoader, + ), + ); + await tester.pump(); + + expect( + find.widgetWithText(AppBar, 'Content Based Dynamic Color'), + findsOne, + ); + expect(find.byType(Switch), findsOne); + expect(find.byIcon(Icons.light_mode), findsOne); + + expect(find.text('Light ColorScheme'), findsOne); + expect(find.text('Dark ColorScheme'), findsOne); + expect(find.text('primary'), findsExactly(2)); + expect(find.text('onPrimary'), findsExactly(2)); + expect(find.text('primaryContainer'), findsExactly(2)); + expect(find.text('onPrimaryContainer'), findsExactly(2)); + expect(find.text('secondary'), findsExactly(2)); + expect(find.text('onSecondary'), findsExactly(2)); + expect(find.text('secondaryContainer'), findsExactly(2)); + expect(find.text('onSecondaryContainer'), findsExactly(2)); + expect(find.text('tertiary'), findsExactly(2)); + expect(find.text('onTertiary'), findsExactly(2)); + expect(find.text('tertiaryContainer'), findsExactly(2)); + expect(find.text('onTertiaryContainer'), findsExactly(2)); + expect(find.text('error'), findsExactly(2)); + expect(find.text('onError'), findsExactly(2)); + expect(find.text('errorContainer'), findsExactly(2)); + expect(find.text('onErrorContainer'), findsExactly(2)); + expect(find.text('surface'), findsExactly(2)); + expect(find.text('onSurface'), findsExactly(2)); + expect(find.text('onSurfaceVariant'), findsExactly(2)); + expect(find.text('outline'), findsExactly(2)); + expect(find.text('shadow'), findsExactly(2)); + expect(find.text('inverseSurface'), findsExactly(2)); + expect(find.text('onInverseSurface'), findsExactly(2)); + expect(find.text('inversePrimary'), findsExactly(2)); + + expect(loadColorSchemeCalls, hasLength(1)); + expect( + loadColorSchemeCalls.single.$1, + isA() + .having( + (NetworkImage provider) => provider.url, + 'url', + 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_1.png', + ), + ); + expect( + loadColorSchemeCalls.single.$2, + Brightness.light, + ); + + await tester.pumpAndSettle(); // Clears the timers from image loading. + }); + + testWidgets('The brightness can be changed', (WidgetTester tester) async { + await tester.pumpWidget( + example.DynamicColorExample( + loadColorScheme: fakeColorSchemeLoader, + ), + ); + await tester.pump(); + + expect(loadColorSchemeCalls, hasLength(1)); + expect( + loadColorSchemeCalls.single.$1, + isA() + .having( + (NetworkImage provider) => provider.url, + 'url', + 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_1.png', + ), + ); + expect( + loadColorSchemeCalls.single.$2, + Brightness.light, + ); + await tester.pump(); + await tester.pump(kThemeChangeDuration); + + ThemeData themeData = Theme.of(tester.element(find.byType(Scaffold))); + + expect(themeData.colorScheme.primary, const Color(0xff565992)); + expect(themeData.colorScheme.secondary, const Color(0xff5c5d72)); + + await tester.tap(find.byType(Switch)); + await tester.pump(); + + expect(loadColorSchemeCalls, hasLength(2)); + expect( + loadColorSchemeCalls.last.$1, + isA() + .having( + (NetworkImage provider) => provider.url, + 'url', + 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_1.png', + ), + ); + expect( + loadColorSchemeCalls.last.$2, + Brightness.dark, + ); + + await tester.pump(kThemeChangeDuration); + + themeData = Theme.of(tester.element(find.byType(Scaffold))); + + expect(themeData.colorScheme.primary, const Color(0xffbfc2ff)); + expect(themeData.colorScheme.secondary, const Color(0xffc5c4dd)); + }); + + testWidgets('Tapping an image loads a new color scheme', (WidgetTester tester) async { + await tester.pumpWidget( + example.DynamicColorExample( + loadColorScheme: fakeColorSchemeLoader, + ), + ); + await tester.pump(); + + await tester.tapAt(tester.getCenter(find.byType(Image).at(3))); + await tester.pump(); + + expect(loadColorSchemeCalls, hasLength(2)); + expect( + loadColorSchemeCalls.last.$1, + isA() + .having( + (NetworkImage provider) => provider.url, + 'url', + 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_4.png', + ), + ); + expect( + loadColorSchemeCalls.last.$2, + Brightness.light, + ); + + await tester.pump(kThemeChangeDuration); + + final ThemeData themeData = Theme.of(tester.element(find.byType(Scaffold))); + + expect(themeData.colorScheme.primary, const Color(0xff406836)); + expect(themeData.colorScheme.secondary, const Color(0xff54634d)); + }); +}