Skip to content

Commit 9f7f6b5

Browse files
[Feature] - Added a SingleShot golden scene for less verbose tests with a single golden image (Resolves #27) (#33)
1 parent b3b9679 commit 9f7f6b5

File tree

10 files changed

+240
-38
lines changed

10 files changed

+240
-38
lines changed

analysis_options.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ include: package:flutter_lints/flutter.yaml
22

33
# Additional information about this file can be found at
44
# https://dart.dev/guides/language/analysis-options
5+
6+
linter:
7+
rules:
8+
- always_use_package_imports

lib/flutter_test_goldens.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ export 'src/scenes/film_strip.dart';
1010
export 'src/scenes/gallery.dart';
1111
export 'src/scenes/golden_scene.dart';
1212
export 'src/scenes/scene_layout.dart';
13+
export 'src/scenes/single_shot.dart';
1314
export 'src/logging.dart';
1415
export 'src/test_runners.dart';

lib/src/scenes/gallery.dart

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import 'dart:math';
44
import 'dart:typed_data';
55
import 'dart:ui' as ui;
66

7-
import 'package:flutter/material.dart' hide Image;
87
import 'package:flutter/material.dart' as m;
8+
import 'package:flutter/material.dart' hide Image;
99
import 'package:flutter_test/flutter_test.dart';
10+
import 'package:flutter_test_goldens/golden_bricks.dart';
1011
import 'package:flutter_test_goldens/src/flutter/flutter_test_extensions.dart';
1112
import 'package:flutter_test_goldens/src/goldens/golden_camera.dart';
1213
import 'package:flutter_test_goldens/src/goldens/golden_collections.dart';
@@ -15,9 +16,11 @@ import 'package:flutter_test_goldens/src/goldens/golden_rendering.dart';
1516
import 'package:flutter_test_goldens/src/goldens/golden_scenes.dart';
1617
import 'package:flutter_test_goldens/src/goldens/pixel_comparisons.dart';
1718
import 'package:flutter_test_goldens/src/logging.dart';
19+
import 'package:flutter_test_goldens/src/scenes/golden_files.dart';
1820
import 'package:flutter_test_goldens/src/scenes/golden_scene.dart';
1921
import 'package:flutter_test_goldens/src/scenes/scene_layout.dart';
2022
import 'package:image/image.dart';
23+
import 'package:path/path.dart';
2124
import 'package:qr_bar_code/code/code.dart';
2225

2326
/// A golden builder that builds independent widget tree UIs and then either
@@ -26,20 +29,25 @@ import 'package:qr_bar_code/code/code.dart';
2629
class Gallery {
2730
Gallery(
2831
this._tester, {
29-
required String sceneName,
32+
Directory? directory,
33+
required String fileName,
34+
required String sceneDescription,
3035
required SceneLayout layout,
3136
GalleryItemScaffold itemScaffold = defaultGalleryItemScaffold,
3237
GalleryItemDecorator? itemDecorator,
3338
Widget? goldenBackground,
3439
m.Color qrCodeColor = m.Colors.black,
3540
m.Color qrCodeBackgroundColor = m.Colors.white,
36-
}) : _sceneName = sceneName,
41+
}) : _fileName = fileName,
42+
_sceneDescription = sceneDescription,
3743
_layout = layout,
3844
_itemScaffold = itemScaffold,
3945
_itemDecorator = itemDecorator,
4046
_goldenBackground = goldenBackground,
4147
_qrCodeColor = qrCodeColor,
42-
_qrCodeBackgroundColor = qrCodeBackgroundColor;
48+
_qrCodeBackgroundColor = qrCodeBackgroundColor {
49+
_directory = directory ?? defaultGoldenDirectory;
50+
}
4351

4452
final WidgetTester _tester;
4553

@@ -54,9 +62,14 @@ class Gallery {
5462
/// All screenshots within this scene.
5563
final _items = <GalleryItem>[];
5664

57-
/// The name of the overall golden scene, which may includes many individual
58-
/// goldens.
59-
final String _sceneName;
65+
/// The directory where the golden scene file will be saved.
66+
late final Directory _directory;
67+
68+
/// The file name for the golden scene file, which will be saved in [_directory].
69+
final String _fileName;
70+
71+
/// A human readable description of what's in this scene.
72+
final String _sceneDescription;
6073

6174
/// The layout to use to position all the items in this scene.
6275
final SceneLayout _layout;
@@ -78,6 +91,7 @@ class Gallery {
7891
GalleryItem.withWidget(
7992
id: id,
8093
description: description,
94+
boundsFinder: boundsFinder,
8195
child: widget,
8296
),
8397
);
@@ -96,6 +110,7 @@ class Gallery {
96110
GalleryItem.withBuilder(
97111
id: id,
98112
description: description,
113+
boundsFinder: boundsFinder,
99114
builder: builder,
100115
),
101116
);
@@ -130,6 +145,7 @@ class Gallery {
130145
GalleryItem.withPumper(
131146
id: id,
132147
description: description,
148+
boundsFinder: boundsFinder,
133149
pumper: pumper,
134150
),
135151
);
@@ -140,7 +156,7 @@ class Gallery {
140156
/// Either renders a new golden to a scene file, or compares new screenshots against an existing
141157
/// golden scene file.
142158
Future<void> renderOrCompareGolden() async {
143-
FtgLog.pipeline.info("Rendering or comparing golden - $_sceneName");
159+
FtgLog.pipeline.info("Rendering or comparing golden - $_sceneDescription");
144160

145161
// Build each gallery item and screenshot it.
146162
final camera = GoldenCamera();
@@ -155,29 +171,25 @@ class Gallery {
155171
await _tester.pumpWidget(
156172
_itemScaffold(
157173
_tester,
158-
GoldenImageBounds(
159-
child: _itemDecorator != null
160-
? _itemDecorator.call(
161-
_tester,
162-
Builder(builder: item.builder!),
163-
)
164-
: Builder(builder: item.builder!),
165-
),
174+
_itemDecorator != null
175+
? _itemDecorator.call(
176+
_tester,
177+
Builder(builder: item.builder!),
178+
)
179+
: Builder(builder: item.builder!),
166180
),
167181
);
168182
} else {
169183
// Pump this gallery item, deferring to a `Widget` for the content.
170184
await _tester.pumpWidget(
171185
_itemScaffold(
172186
_tester,
173-
GoldenImageBounds(
174-
child: _itemDecorator != null
175-
? _itemDecorator.call(
176-
_tester,
177-
item.child!,
178-
)
179-
: item.child!,
180-
),
187+
_itemDecorator != null
188+
? _itemDecorator.call(
189+
_tester,
190+
item.child!,
191+
)
192+
: item.child!,
181193
),
182194
);
183195
}
@@ -237,15 +249,15 @@ class Gallery {
237249

238250
await _tester.pumpAndSettle();
239251

240-
final goldenFileName = "$_sceneName.png";
252+
final goldenFilePath = File("${_directory.path}$separator$_fileName.png");
241253
if (autoUpdateGoldenFiles) {
242254
// Generate new goldens.
243255
FtgLog.pipeline.finer("Doing golden generation - window height: ${_tester.view.physicalSize.height}");
244-
await expectLater(find.byType(GoldenSceneBounds), matchesGoldenFile(goldenFileName));
256+
await expectLater(find.byType(GoldenSceneBounds), matchesGoldenFile(goldenFilePath.path));
245257
} else {
246258
// Compare to existing goldens.
247259
FtgLog.pipeline.finer("Comparing existing goldens...");
248-
await _compareGoldens(_tester, goldenFileName, find.byType(GoldenSceneBounds));
260+
await _compareGoldens(_tester, goldenFilePath, find.byType(GoldenSceneBounds));
249261
FtgLog.pipeline.finer("Done comparing goldens for gallery");
250262
}
251263

@@ -363,9 +375,9 @@ class Gallery {
363375
);
364376
}
365377

366-
Future<void> _compareGoldens(WidgetTester tester, String existingGoldenFileName, Finder goldenBounds) async {
378+
Future<void> _compareGoldens(WidgetTester tester, File existingGoldenFileName, Finder goldenBounds) async {
367379
final testFileDirectory = (goldenFileComparator as LocalFileComparator).basedir.path;
368-
final goldenFile = File("$testFileDirectory$existingGoldenFileName");
380+
final goldenFile = File("$testFileDirectory${existingGoldenFileName.path}");
369381

370382
if (!goldenFile.existsSync()) {
371383
// TODO: report error in structured way.
@@ -568,10 +580,21 @@ class GalleryItem {
568580
final Widget? child;
569581
}
570582

583+
/// The ancestor widget tree for every item in a gallery, unless overridden by
584+
/// the gallery configuration.
571585
Widget defaultGalleryItemScaffold(WidgetTester tester, Widget content) {
572586
return MaterialApp(
573587
home: Scaffold(
574-
body: content,
588+
body: m.Builder(builder: (context) {
589+
return DefaultTextStyle(
590+
style: DefaultTextStyle.of(context).style.copyWith(
591+
fontFamily: goldenBricks,
592+
),
593+
child: Center(
594+
child: GoldenImageBounds(child: content),
595+
),
596+
);
597+
}),
575598
),
576599
debugShowCheckedModeBanner: false,
577600
);

lib/src/scenes/golden_files.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import 'dart:io';
2+
3+
/// The standard path to where goldens are saved.
4+
///
5+
/// The default path is a `/goldens/` directory, which sits in the same parent directory as
6+
/// the test file that's running the test.
7+
final defaultGoldenDirectory = Directory("./goldens/");

lib/src/scenes/single_shot.dart

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import 'dart:io';
2+
3+
import 'package:flutter/material.dart' hide Image;
4+
import 'package:flutter_test/flutter_test.dart';
5+
import 'package:flutter_test_goldens/src/goldens/golden_rendering.dart';
6+
import 'package:flutter_test_goldens/src/scenes/gallery.dart';
7+
import 'package:flutter_test_goldens/src/scenes/golden_files.dart';
8+
import 'package:flutter_test_goldens/src/scenes/scene_layout.dart';
9+
10+
/// A golden scene with a single golden image.
11+
class SingleShot {
12+
/// Creates a [SingleShot] whose content is rendered by a given [widget].
13+
SingleShot.fromWidget(
14+
this._tester, {
15+
Directory? directory,
16+
required String fileName,
17+
required String description,
18+
GalleryItemScaffold itemScaffold = defaultGalleryItemScaffold,
19+
GalleryItemDecorator? itemDecorator,
20+
Finder? boundsFinder,
21+
required Widget widget,
22+
}) : _fileName = fileName,
23+
_description = description,
24+
_itemScaffold = itemScaffold,
25+
_itemDecorator = itemDecorator,
26+
_boundsFinder = boundsFinder ?? find.byType(GoldenImageBounds),
27+
_widget = widget,
28+
_builder = null,
29+
_pumper = null {
30+
_directory = directory ?? defaultGoldenDirectory;
31+
}
32+
33+
/// Creates a [SingleShot] whose content is rendered by a given [builder].
34+
SingleShot.fromBuilder(
35+
this._tester, {
36+
Directory? directory,
37+
required String fileName,
38+
required String description,
39+
GalleryItemScaffold itemScaffold = defaultGalleryItemScaffold,
40+
GalleryItemDecorator? itemDecorator,
41+
Finder? boundsFinder,
42+
required WidgetBuilder builder,
43+
}) : _fileName = fileName,
44+
_description = description,
45+
_itemScaffold = itemScaffold,
46+
_itemDecorator = itemDecorator,
47+
_boundsFinder = boundsFinder ?? find.byType(GoldenImageBounds),
48+
_builder = builder,
49+
_widget = null,
50+
_pumper = null {
51+
_directory = directory ?? defaultGoldenDirectory;
52+
}
53+
54+
/// Creates a [SingleShot] whose content is rendered by a given [pumper].
55+
///
56+
/// This constructor is useful when a golden scene needs full control over the [WidgetTester],
57+
/// and the call to [WidgetTester.pumpWidget].
58+
SingleShot.fromPumper(
59+
this._tester, {
60+
Directory? directory,
61+
required String fileName,
62+
required String description,
63+
GalleryItemScaffold itemScaffold = defaultGalleryItemScaffold,
64+
GalleryItemDecorator? itemDecorator,
65+
Finder? boundsFinder,
66+
required GalleryItemPumper pumper,
67+
}) : _fileName = fileName,
68+
_description = description,
69+
_itemScaffold = itemScaffold,
70+
_itemDecorator = itemDecorator,
71+
_boundsFinder = boundsFinder ?? find.byType(GoldenImageBounds),
72+
_pumper = pumper,
73+
_widget = null,
74+
_builder = null {
75+
_directory = directory ?? defaultGoldenDirectory;
76+
}
77+
78+
final WidgetTester _tester;
79+
80+
/// The directory where the golden scene file will be saved.
81+
late final Directory _directory;
82+
83+
/// The file name for the golden scene file, which will be saved in [_directory].
84+
final String _fileName;
85+
86+
/// The name of the overall golden scene.
87+
final String _description;
88+
89+
/// A scaffold built around each item in this scene.
90+
///
91+
/// Defaults to [defaultGalleryItemScaffold].
92+
final GalleryItemScaffold _itemScaffold;
93+
94+
/// A decoration applied to each item in this scene.
95+
final GalleryItemDecorator? _itemDecorator;
96+
97+
/// [Finder] that locates the content that will be screenshotted in this golden scene.
98+
final Finder _boundsFinder;
99+
100+
final GalleryItemPumper? _pumper;
101+
102+
final WidgetBuilder? _builder;
103+
104+
final Widget? _widget;
105+
106+
/// Either renders a new golden to a scene file, or compares new screenshots against an existing
107+
/// golden scene file.
108+
Future<void> renderOrCompareGolden() async {
109+
final gallery = Gallery(
110+
_tester,
111+
directory: _directory,
112+
fileName: _fileName,
113+
sceneDescription: _description,
114+
layout: SceneLayout.column,
115+
itemScaffold: _itemScaffold,
116+
itemDecorator: _itemDecorator,
117+
);
118+
119+
if (_widget != null) {
120+
gallery.itemFromWidget(id: "1", description: _description, widget: _widget, boundsFinder: _boundsFinder);
121+
} else if (_builder != null) {
122+
gallery.itemFromBuilder(id: "1", description: _description, builder: _builder, boundsFinder: _boundsFinder);
123+
} else {
124+
gallery.itemFromPumper(id: "1", description: _description, pumper: _pumper!, boundsFinder: _boundsFinder);
125+
}
126+
127+
await gallery.renderOrCompareGolden();
128+
}
129+
}

test_goldens/flutter/buttons/buttons_test.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,11 @@ void main() {
164164

165165
await Gallery(
166166
tester,
167-
sceneName: "button_extended_fab_gallery",
167+
directory: Directory("."),
168+
fileName: "button_extended_fab_gallery",
169+
sceneDescription: "FAB Gallery",
168170
layout: SceneLayout.row,
169-
itemDecorator: (context, child) {
171+
itemScaffold: (context, child) {
170172
return FlutterWidgetScaffold(
171173
child: child,
172174
);

test_goldens/flutter/flutter_widget_scaffold.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ class FlutterWidgetScaffold extends StatelessWidget {
2020
),
2121
home: Scaffold(
2222
backgroundColor: const Color(0xFF222222),
23-
body: GoldenSceneBounds(
24-
key: goldenKey,
25-
child: Padding(
26-
padding: const EdgeInsets.all(48),
27-
child: child,
23+
body: Center(
24+
child: GoldenImageBounds(
25+
key: goldenKey,
26+
child: Padding(
27+
padding: const EdgeInsets.all(48),
28+
child: child,
29+
),
2830
),
2931
),
3032
),

0 commit comments

Comments
 (0)