diff --git a/dev/bots/check_code_samples.dart b/dev/bots/check_code_samples.dart index ae2cbe9174..f365dc22ba 100644 --- a/dev/bots/check_code_samples.dart +++ b/dev/bots/check_code_samples.dart @@ -142,18 +142,29 @@ 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. - final List missingTests = checkForMissingTests(exampleFilenames); + // 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); // 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 && malformedLinks.isEmpty) { + if (missingFilenames.isEmpty && missingTests.isEmpty && noLongerMissing.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) { @@ -268,14 +279,35 @@ class SampleChecker { return '${path.join(testPath, path.basenameWithoutExtension(example.path))}_test.dart'; } - List checkForMissingTests(List exampleFilenames) { + (List, 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); } } - return missingTests; + // 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); } } + +// 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 f8f0e1644d..2dde166aba 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,23 +9,23 @@ import 'package:flutter/material.dart'; const Widget divider = SizedBox(height: 10); const double narrowScreenWidthThreshold = 400; -void main() => runApp(const DynamicColorExample()); +void main() => runApp(DynamicColorExample()); class DynamicColorExample extends StatefulWidget { - const DynamicColorExample({super.key}); + DynamicColorExample({super.key}); - static const List images = [ - NetworkImage( + final List images = [ + const NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_1.png'), - NetworkImage( + const NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_2.png'), - NetworkImage( + const NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_3.png'), - NetworkImage( + const NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_4.png'), - NetworkImage( + const NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_5.png'), - NetworkImage( + const NetworkImage( 'https://flutter.github.io/assets-for-api-docs/assets/material/content_based_color_scheme_6.png'), ]; @@ -48,7 +48,7 @@ class _DynamicColorExampleState extends State { isLoading = true; currentColorScheme = const ColorScheme.light(); WidgetsBinding.instance.addPostFrameCallback((_) { - _updateImage(DynamicColorExample.images[selectedImage]); + _updateImage(widget.images[selectedImage]); isLoading = false; }); } @@ -105,7 +105,7 @@ class _DynamicColorExampleState extends State { onChanged: (bool value) { setState(() { isLight = value; - _updateImage(DynamicColorExample.images[selectedImage]); + _updateImage(widget.images[selectedImage]); }); }) ], @@ -120,7 +120,7 @@ class _DynamicColorExampleState extends State { divider, _imagesRow( context, - DynamicColorExample.images, + widget.images, colorScheme, ), divider, @@ -192,7 +192,7 @@ class _DynamicColorExampleState extends State { return; } setState(() { - selectedImage = DynamicColorExample.images.indexOf(provider); + selectedImage = widget.images.indexOf(provider); currentColorScheme = newColorScheme; }); } @@ -227,7 +227,7 @@ class _DynamicColorExampleState extends State { child: GestureDetector( onTap: () => _updateImage(image), child: Card( - color: DynamicColorExample.images.indexOf(image) == selectedImage + color: widget.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 deleted file mode 100644 index 6287d5cc08..0000000000 --- a/examples/api/test/material/color_scheme/dynamic_content_color.0_test.dart +++ /dev/null @@ -1,163 +0,0 @@ -// 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:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter/services.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() { - - testWidgets('The theme colors are created dynamically from the first image', (WidgetTester tester) async { - await HttpOverrides.runZoned( - () async { - await tester.pumpWidget( - const example.DynamicColorExample(), - ); - - expect( - find.widgetWithText(AppBar, 'Content Based Dynamic Color'), - findsOne, - ); - expect(find.byType(Switch), findsOne); - expect(find.byIcon(Icons.light_mode), findsOne); - - // Loads the images. - // Using runAsync forces the streams to complete. This is needed because - // loading the fake image is a real async task. - await tester.pump(); - await tester.runAsync(() => Future.delayed(Duration.zero)); - await tester.runAsync(() => Future.delayed(Duration.zero)); - await tester.runAsync(() => Future.delayed(Duration.zero)); - await tester.runAsync(() => Future.delayed(Duration.zero)); - await tester.runAsync(() => Future.delayed(Duration.zero)); - await tester.pump(); - await tester.pump(kThemeChangeDuration); - - expect(find.byType(Image), findsExactly(6)); - - 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)); - - ThemeData themeData = Theme.of( - tester.element(find.byType(Scaffold)), - ); - - expect(themeData.colorScheme.primary, const Color(0xff575992)); - expect(themeData.colorScheme.secondary, const Color(0xff5d5c72)); - - await tester.tap(find.byType(Switch)); - await tester.pump(); - - // Loads the images. - // Using runAsync forces the streams to complete. This is needed because - // loading the fake image is a real async task. - await tester.runAsync(() => Future.delayed(Duration.zero)); - await tester.runAsync(() => Future.delayed(Duration.zero)); - await tester.pump(); - await tester.pump(kThemeChangeDuration); - - themeData = Theme.of(tester.element(find.byType(Scaffold))); - - expect(themeData.primaryColor, const Color(0xff131318)); - expect(themeData.colorScheme.secondary, const Color(0xffc6c4dd)); - }, - createHttpClient: (SecurityContext? securityContext) => _FakeClient(), - ); - }); -} - -class _FakeClient extends Fake implements HttpClient { - @override - Future getUrl(Uri url) async { - return _FakeHttpClientRequest(); - } - - @override - bool autoUncompress = true; -} - -class _FakeHttpClientRequest extends Fake implements HttpClientRequest { - @override - HttpHeaders get headers => _FakeHttpHeaders(); - - @override - Future close() async { - return _FakeHttpClientResponse(); - } -} - -class _FakeHttpHeaders extends Fake implements HttpHeaders {} - -class _FakeHttpClientResponse extends Fake implements HttpClientResponse { - @override - HttpClientResponseCompressionState get compressionState => HttpClientResponseCompressionState.notCompressed; - - @override - int get contentLength => _blueSquarePng.length; - - @override - int get statusCode => 200; - - @override - Future drain([E? futureValue]) { - return Future.value(futureValue as E); - } - - @override - StreamSubscription> listen( - void Function(List event)? onData, { - Function? onError, - void Function()? onDone, - bool? cancelOnError, - }) { - return Stream>.value(_blueSquarePng).listen( - onData, - onDone: onDone, - onError: onError, - cancelOnError: cancelOnError, - ); - } -} - -/// A 50x50 blue square png. -final Uint8List _blueSquarePng = Uint8List.fromList(const [ - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, - 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x32, 0x08, 0x06, - 0x00, 0x00, 0x00, 0x1e, 0x3f, 0x88, 0xb1, 0x00, 0x00, 0x00, 0x48, 0x49, 0x44, - 0x41, 0x54, 0x78, 0xda, 0xed, 0xcf, 0x31, 0x0d, 0x00, 0x30, 0x08, 0x00, 0xb0, - 0x61, 0x63, 0x2f, 0xfe, 0x2d, 0x61, 0x05, 0x34, 0xf0, 0x92, 0xd6, 0x41, 0x23, - 0x7f, 0xf5, 0x3b, 0x20, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, - 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, - 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, - 0x44, 0x44, 0x44, 0x36, 0x06, 0x03, 0x6e, 0x69, 0x47, 0x12, 0x8e, 0xea, 0xaa, - 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, -]);