diff --git a/examples/api/lib/painting/gradient/linear_gradient.0.dart b/examples/api/lib/painting/gradient/linear_gradient.0.dart index 5662e6df3e359..6d5f9027579a7 100644 --- a/examples/api/lib/painting/gradient/linear_gradient.0.dart +++ b/examples/api/lib/painting/gradient/linear_gradient.0.dart @@ -11,33 +11,41 @@ void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); - static const String _title = 'Flutter Code Sample'; - @override Widget build(BuildContext context) { - return const MaterialApp( - title: _title, - home: MyStatelessWidget(), - ); + return const MaterialApp(home: MoodyGradient()); } } -class MyStatelessWidget extends StatelessWidget { - const MyStatelessWidget({Key? key}) : super(key: key); +class MoodyGradient extends StatelessWidget { + const MoodyGradient({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return Container( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: - Alignment(0.8, 0.0), // 10% of the width, so there are ten blinds. - colors: [ - Color(0xffee0000), - Color(0xffeeee00) - ], // red to yellow - tileMode: TileMode.repeated, // repeats the gradient over the canvas + return Material( + child: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment(0.8, 1), + colors: [ + Color(0xff1f005c), + Color(0xff5b0060), + Color(0xff870160), + Color(0xffac255e), + Color(0xffca485c), + Color(0xffe16b5c), + Color(0xfff39060), + Color(0xffffb56b), + ], // Gradient from https://learnui.design/tools/gradient-generator.html + tileMode: TileMode.mirror, + ), + ), + child: const Center( + child: Text( + 'From Night to Day', + style: TextStyle(fontSize: 24, color: Colors.white), + ), ), ), ); diff --git a/examples/api/pubspec.yaml b/examples/api/pubspec.yaml index 5c9a2ecdd6bfe..cdb927ac65c6e 100644 --- a/examples/api/pubspec.yaml +++ b/examples/api/pubspec.yaml @@ -25,6 +25,8 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter + flutter_goldens: + sdk: flutter flutter_test: sdk: flutter test: 1.20.1 diff --git a/examples/api/test/flutter_test_config.dart b/examples/api/test/flutter_test_config.dart new file mode 100644 index 0000000000000..2028d46c83f60 --- /dev/null +++ b/examples/api/test/flutter_test_config.dart @@ -0,0 +1,14 @@ +// 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 'package:flutter_test/flutter_test.dart'; + +import 'goldens_io.dart' if (dart.library.html) 'goldens_web.dart' as flutter_goldens; + +Future testExecutable(FutureOr Function() testMain) { + // Enable golden file testing using Skia Gold. + return flutter_goldens.testExecutable(testMain, namePrefix: 'api'); +} diff --git a/examples/api/test/goldens_io.dart b/examples/api/test/goldens_io.dart new file mode 100644 index 0000000000000..d552235d6903e --- /dev/null +++ b/examples/api/test/goldens_io.dart @@ -0,0 +1,5 @@ +// 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. + +export 'package:flutter_goldens/flutter_goldens.dart' show testExecutable; diff --git a/examples/api/test/goldens_web.dart b/examples/api/test/goldens_web.dart new file mode 100644 index 0000000000000..57c0fbad759ff --- /dev/null +++ b/examples/api/test/goldens_web.dart @@ -0,0 +1,8 @@ +// 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'; + +// package:flutter_goldens is not used as part of the test process for web. +Future testExecutable(FutureOr Function() testMain) async => testMain(); diff --git a/examples/api/test/painting/linear_gradient.0_test.dart b/examples/api/test/painting/linear_gradient.0_test.dart new file mode 100644 index 0000000000000..0ba1105f64751 --- /dev/null +++ b/examples/api/test/painting/linear_gradient.0_test.dart @@ -0,0 +1,39 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_api_samples/painting/gradient/linear_gradient.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('finds a gradient', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: example.MoodyGradient(), + ), + ); + + expect(find.byType(example.MoodyGradient), findsOneWidget); + }); + + testWidgets('gradient matches golden', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: SizedBox( + width: 800, + height: 600, + child: RepaintBoundary( + child: example.MoodyGradient(), + ), + ), + ), + ); + await expectLater( + find.byType(example.MoodyGradient), + matchesGoldenFile('linear_gradient.0_test.png'), + ); + }); +} diff --git a/packages/flutter/lib/src/painting/gradient.dart b/packages/flutter/lib/src/painting/gradient.dart index 471a6b99e2481..045e0dba77bba 100644 --- a/packages/flutter/lib/src/painting/gradient.dart +++ b/packages/flutter/lib/src/painting/gradient.dart @@ -350,8 +350,9 @@ abstract class Gradient { /// To use a [LinearGradient] to paint on a canvas directly, see [createShader]. /// /// {@tool dartpad} -/// This sample draws a picture that looks like vertical window shades by having -/// a [Container] display a [BoxDecoration] with a [LinearGradient]. +/// This sample draws a picture with a gradient sweeping through different +/// colors, by having a [Container] display a [BoxDecoration] with a +/// [LinearGradient]. /// /// ** See code in examples/api/lib/painting/gradient/linear_gradient.0.dart ** /// {@end-tool} diff --git a/packages/flutter_goldens/lib/flutter_goldens.dart b/packages/flutter_goldens/lib/flutter_goldens.dart index e0b5bdfcc2255..04c9a6279ebc5 100644 --- a/packages/flutter_goldens/lib/flutter_goldens.dart +++ b/packages/flutter_goldens/lib/flutter_goldens.dart @@ -25,15 +25,18 @@ const String _kFlutterRootKey = 'FLUTTER_ROOT'; /// [goldenFileComparator] to an instance of [FlutterGoldenFileComparator] that /// works for the current test. _Which_ FlutterGoldenFileComparator is /// instantiated is based on the current testing environment. -Future testExecutable(FutureOr Function() testMain) async { +/// +/// When set, the `namePrefix` is prepended to the names of all gold images. +Future testExecutable(FutureOr Function() testMain, {String? namePrefix}) async { const Platform platform = LocalPlatform(); if (FlutterPostSubmitFileComparator.isAvailableForEnvironment(platform)) { - goldenFileComparator = await FlutterPostSubmitFileComparator.fromDefaultComparator(platform); + goldenFileComparator = await FlutterPostSubmitFileComparator.fromDefaultComparator(platform, namePrefix: namePrefix); } else if (FlutterPreSubmitFileComparator.isAvailableForEnvironment(platform)) { - goldenFileComparator = await FlutterPreSubmitFileComparator.fromDefaultComparator(platform); + goldenFileComparator = await FlutterPreSubmitFileComparator.fromDefaultComparator(platform, namePrefix: namePrefix); } else if (FlutterSkippingFileComparator.isAvailableForEnvironment(platform)) { goldenFileComparator = FlutterSkippingFileComparator.fromDefaultComparator( - 'Golden file testing is not executed on Cirrus, or LUCI environments outside of flutter/flutter.' + 'Golden file testing is not executed on Cirrus, or LUCI environments outside of flutter/flutter.', + namePrefix: namePrefix ); } else { goldenFileComparator = await FlutterLocalFileComparator.fromDefaultComparator(platform); @@ -91,6 +94,7 @@ abstract class FlutterGoldenFileComparator extends GoldenFileComparator { this.skiaClient, { this.fs = const LocalFileSystem(), this.platform = const LocalPlatform(), + this.namePrefix, }); /// The directory to which golden file URIs will be resolved in [compare] and @@ -109,6 +113,9 @@ abstract class FlutterGoldenFileComparator extends GoldenFileComparator { @visibleForTesting final Platform platform; + /// The prefix that is added to all golden names. + final String? namePrefix; + @override Future update(Uri golden, Uint8List imageBytes) async { final File goldenFile = getGoldenFile(golden); @@ -173,8 +180,12 @@ abstract class FlutterGoldenFileComparator extends GoldenFileComparator { 'Golden files in the Flutter framework must end with the file extension ' '.png.' ); - final String prefix = basedir.pathSegments[basedir.pathSegments.length - 2]; - return Uri.parse('$prefix.$golden'); + return Uri.parse([ + if (namePrefix != null) + namePrefix!, + basedir.pathSegments[basedir.pathSegments.length - 2], + golden.toString(), + ].join('.')); } } @@ -205,11 +216,13 @@ class FlutterPostSubmitFileComparator extends FlutterGoldenFileComparator { final SkiaGoldClient skiaClient, { final FileSystem fs = const LocalFileSystem(), final Platform platform = const LocalPlatform(), + String? namePrefix, }) : super( basedir, skiaClient, fs: fs, platform: platform, + namePrefix: namePrefix, ); /// Creates a new [FlutterPostSubmitFileComparator] that mirrors the relative @@ -221,6 +234,7 @@ class FlutterPostSubmitFileComparator extends FlutterGoldenFileComparator { final Platform platform, { SkiaGoldClient? goldens, LocalFileComparator? defaultComparator, + String? namePrefix, }) async { defaultComparator ??= goldenFileComparator as LocalFileComparator; @@ -233,7 +247,7 @@ class FlutterPostSubmitFileComparator extends FlutterGoldenFileComparator { goldens ??= SkiaGoldClient(baseDirectory); await goldens.auth(); - return FlutterPostSubmitFileComparator(baseDirectory.uri, goldens); + return FlutterPostSubmitFileComparator(baseDirectory.uri, goldens, namePrefix: namePrefix); } @override @@ -283,11 +297,13 @@ class FlutterPreSubmitFileComparator extends FlutterGoldenFileComparator { final SkiaGoldClient skiaClient, { final FileSystem fs = const LocalFileSystem(), final Platform platform = const LocalPlatform(), + final String? namePrefix, }) : super( basedir, skiaClient, fs: fs, platform: platform, + namePrefix: namePrefix, ); /// Creates a new [FlutterPreSubmitFileComparator] that mirrors the @@ -300,6 +316,7 @@ class FlutterPreSubmitFileComparator extends FlutterGoldenFileComparator { SkiaGoldClient? goldens, LocalFileComparator? defaultComparator, Directory? testBasedir, + String? namePrefix, }) async { defaultComparator ??= goldenFileComparator as LocalFileComparator; @@ -318,6 +335,7 @@ class FlutterPreSubmitFileComparator extends FlutterGoldenFileComparator { return FlutterPreSubmitFileComparator( baseDirectory.uri, goldens, platform: platform, + namePrefix: namePrefix, ); } @@ -367,8 +385,9 @@ class FlutterSkippingFileComparator extends FlutterGoldenFileComparator { FlutterSkippingFileComparator( final Uri basedir, final SkiaGoldClient skiaClient, - this.reason, - ) : super(basedir, skiaClient); + this.reason, { + String? namePrefix, + }) : super(basedir, skiaClient, namePrefix: namePrefix); /// Describes the reason for using the [FlutterSkippingFileComparator]. /// @@ -380,12 +399,13 @@ class FlutterSkippingFileComparator extends FlutterGoldenFileComparator { static FlutterSkippingFileComparator fromDefaultComparator( String reason, { LocalFileComparator? defaultComparator, + String? namePrefix, }) { defaultComparator ??= goldenFileComparator as LocalFileComparator; const FileSystem fs = LocalFileSystem(); final Uri basedir = defaultComparator.basedir; final SkiaGoldClient skiaClient = SkiaGoldClient(fs.directory(basedir)); - return FlutterSkippingFileComparator(basedir, skiaClient, reason); + return FlutterSkippingFileComparator(basedir, skiaClient, reason, namePrefix: namePrefix); } @override diff --git a/packages/flutter_goldens/test/flutter_goldens_test.dart b/packages/flutter_goldens/test/flutter_goldens_test.dart index 494f582cfe01f..feb3ef75fd5a2 100644 --- a/packages/flutter_goldens/test/flutter_goldens_test.dart +++ b/packages/flutter_goldens/test/flutter_goldens_test.dart @@ -466,6 +466,27 @@ void main() { expect(key, Uri.parse('foo.png')); }); + test('adds namePrefix', () async { + const String libraryName = 'sidedishes'; + const String namePrefix = 'tomatosalad'; + const String fileName = 'lettuce.png'; + final FakeSkiaGoldClient fakeSkiaClient = FakeSkiaGoldClient(); + final Directory basedir = fs.directory('flutter/test/$libraryName/') + ..createSync(recursive: true); + final FlutterGoldenFileComparator comparator = FlutterPostSubmitFileComparator( + basedir.uri, + fakeSkiaClient, + fs: fs, + platform: platform, + namePrefix: namePrefix, + ); + await comparator.compare( + Uint8List.fromList(_kTestPngBytes), + Uri.parse(fileName), + ); + expect(fakeSkiaClient.testNames.single, '$namePrefix.$libraryName.$fileName'); + }); + group('Post-Submit', () { late FakeSkiaGoldClient fakeSkiaClient; @@ -926,11 +947,16 @@ class FakeSkiaGoldClient extends Fake implements SkiaGoldClient { @override Future auth() async {} + final List testNames = []; + int initCalls = 0; @override Future imgtestInit() async => initCalls += 1; @override - Future imgtestAdd(String testName, File goldenFile) async => true; + Future imgtestAdd(String testName, File goldenFile) async { + testNames.add(testName); + return true; + } int tryInitCalls = 0; @override