1
1
import 'dart:async' ;
2
2
import 'dart:math' ;
3
- import 'dart:typed_data' ;
4
3
import 'dart:ui' as ui;
5
4
6
5
import 'package:flutter/material.dart' as material;
@@ -88,8 +87,9 @@ Future<Image> paintGoldenMismatchImages(GoldenMismatch mismatch) async {
88
87
}
89
88
90
89
/// Given a [report] , generates that shows all the mismatches found in the report.
91
- Future <(Image , FailureSceneMetadata )> paintFailureScene (WidgetTester tester, GoldenSceneReport report) async {
92
- final photos = < GoldenFailurePhoto > [];
90
+ Future <(Image , FailureSceneMetadata )> paintFailureScene (
91
+ WidgetTester tester, GoldenSceneReport report, SceneLayout layout) async {
92
+ final photos = < GoldenSceneScreenshot > [];
93
93
94
94
for (final item in report.items) {
95
95
final mismatch = item.mismatch;
@@ -110,73 +110,95 @@ Future<(Image, FailureSceneMetadata)> paintFailureScene(WidgetTester tester, Gol
110
110
absoluteDiff: absoluteDiff,
111
111
relativeDiff: relativeDiff,
112
112
);
113
+ final image = await _convertImagePackageToUiImage (reportImage);
114
+ final pixels = (await image.toByteData (format: ui.ImageByteFormat .png))! .buffer.asUint8List ();
113
115
114
- String description = item.metadata.id ;
116
+ String description = item.metadata.metadata.description ;
115
117
if (mismatch is PixelGoldenMismatch ) {
116
118
description += " (${mismatch .mismatchPixelCount .toInt ()}px, ${(mismatch .percent * 100 ).toStringAsFixed (2 )}%)" ;
117
119
} else if (mismatch is WrongSizeGoldenMismatch ) {
118
120
description += " (wrong size)" ;
119
121
}
122
+
120
123
photos.add (
121
- GoldenFailurePhoto (
122
- description: description,
123
- pixels: reportImage,
124
+ GoldenSceneScreenshot (
125
+ item.metadata.id,
126
+ item.metadata.metadata.copyWith (description: description),
127
+ reportImage,
128
+ pixels,
124
129
),
125
130
);
126
131
}
127
132
128
133
for (final missingCandidate in report.missingCandidates) {
134
+ // TODO: Figure out why using missingCandidate.golden!.pngBytes causes an "Invalid image data" error.
135
+ final image = await _convertImagePackageToUiImage (missingCandidate.golden! .image);
136
+ final pixels = (await image.toByteData (format: ui.ImageByteFormat .png))! .buffer.asUint8List ();
129
137
photos.add (
130
- GoldenFailurePhoto (
131
- description: "${missingCandidate .golden !.id } (missing candidate)" ,
132
- pixels: missingCandidate.golden! .image,
138
+ GoldenSceneScreenshot (
139
+ missingCandidate.golden! .id,
140
+ missingCandidate.golden! .metadata.copyWith (
141
+ description: "${missingCandidate .golden !.metadata .description } (missing candidate)" ,
142
+ ),
143
+ missingCandidate.golden! .image,
144
+ pixels,
133
145
),
134
146
);
135
147
}
136
148
137
149
for (final extraCandidate in report.extraCandidates) {
138
150
photos.add (
139
- GoldenFailurePhoto (
140
- description: "${extraCandidate .screenshot !.id } (extra candidate)" ,
141
- pixels: extraCandidate.screenshot! .image,
151
+ GoldenSceneScreenshot (
152
+ extraCandidate.screenshot! .id,
153
+ extraCandidate.screenshot! .metadata.copyWith (
154
+ description: "${extraCandidate .screenshot !.metadata .description } (extra candidate)" ,
155
+ ),
156
+ extraCandidate.screenshot! .image,
157
+ extraCandidate.screenshot! .pngBytes,
142
158
),
143
159
);
144
160
}
145
161
146
- return _layoutFailureScene (tester, report, photos);
162
+ return _layoutFailureScene (tester, report, photos, layout );
147
163
}
148
164
149
165
/// Generates a single image that shows all the golden failures.
150
166
Future <(Image , FailureSceneMetadata )> _layoutFailureScene (
151
- WidgetTester tester, GoldenSceneReport report, List <GoldenFailurePhoto > images) async {
152
- final renderablePhotos = < GoldenFailurePhoto , (Uint8List , GlobalKey )> {};
167
+ WidgetTester tester,
168
+ GoldenSceneReport report,
169
+ List <GoldenSceneScreenshot > images,
170
+ SceneLayout layout,
171
+ ) async {
172
+ final renderablePhotos = < GoldenSceneScreenshot , GlobalKey > {};
153
173
for (final photo in images) {
154
- final image = await _convertImagePackageToUiImage (photo.pixels);
155
- final pixels = (await image.toByteData (format: ui.ImageByteFormat .png))! .buffer.asUint8List ();
156
- renderablePhotos[photo] = (pixels, GlobalKey ());
174
+ renderablePhotos[photo] = GlobalKey ();
157
175
}
158
176
159
177
final sceneKey = GlobalKey ();
160
178
final scene = GoldenSceneBounds (
161
179
child: IntrinsicWidth (
162
180
child: IntrinsicHeight (
163
- child: GoldenFailureScene (
164
- key: sceneKey,
165
- direction: Axis .vertical,
166
- renderablePhotos: renderablePhotos,
167
- background: null ,
168
- ),
181
+ child: material.Builder (
182
+ key: sceneKey,
183
+ builder: (context) {
184
+ return layout.build (
185
+ tester,
186
+ context,
187
+ renderablePhotos,
188
+ );
189
+ }),
169
190
),
170
191
),
171
192
);
172
193
await tester.pumpWidgetAndAdjustWindow (scene);
173
194
174
195
for (final entry in renderablePhotos.entries) {
175
196
await precacheImage (
176
- MemoryImage (entry.value.$1 ),
177
- tester.element (find.byKey (entry.value.$2 )),
197
+ MemoryImage (entry.key.pngBytes ),
198
+ tester.element (find.byKey (entry.value)),
178
199
);
179
200
}
201
+
180
202
await tester.pumpAndSettle ();
181
203
182
204
final uiImage = await captureImage (find.byKey (sceneKey).evaluate ().single);
@@ -195,10 +217,10 @@ Future<(Image, FailureSceneMetadata)> _layoutFailureScene(
195
217
images: [
196
218
for (final golden in renderablePhotos.keys)
197
219
FailureImageMetadata (
198
- id: golden.description ,
220
+ id: golden.id ,
199
221
topLeft:
200
- (renderablePhotos[golden]! .$2. currentContext! .findRenderObject () as RenderBox ).localToGlobal (Offset .zero),
201
- size: renderablePhotos[golden]! .$2. currentContext! .size! ,
222
+ (renderablePhotos[golden]! .currentContext! .findRenderObject () as RenderBox ).localToGlobal (Offset .zero),
223
+ size: renderablePhotos[golden]! .currentContext! .size! ,
202
224
),
203
225
],
204
226
);
@@ -215,11 +237,22 @@ Image _layoutGoldenFailure({
215
237
required Image absoluteDiff,
216
238
required Image relativeDiff,
217
239
}) {
240
+ final maxWidth = max (golden.width, candidate.width);
241
+ final maxHeight = max (golden.height, candidate.height);
242
+ const gap = 4 ;
243
+
218
244
final image = Image (
219
- width: golden.width + candidate.width ,
220
- height: golden.height + candidate.height ,
245
+ width: maxWidth * 2 + gap ,
246
+ height: maxHeight * 2 + gap ,
221
247
);
222
248
249
+ final white = ColorUint32 .rgb (255 , 255 , 255 );
250
+ for (int x = 0 ; x < image.width; x += 1 ) {
251
+ for (int y = 0 ; y < image.height; y += 1 ) {
252
+ image.setPixel (x, y, white);
253
+ }
254
+ }
255
+
223
256
// Copy golden to top left corner.
224
257
_drawImage (
225
258
source: golden,
@@ -232,7 +265,7 @@ Image _layoutGoldenFailure({
232
265
_drawImage (
233
266
source: candidate,
234
267
destination: image,
235
- x: golden.width ,
268
+ x: maxWidth + gap ,
236
269
y: 0 ,
237
270
);
238
271
@@ -241,15 +274,15 @@ Image _layoutGoldenFailure({
241
274
source: absoluteDiff,
242
275
destination: image,
243
276
x: 0 ,
244
- y: golden.height ,
277
+ y: maxHeight + gap ,
245
278
);
246
279
247
280
// Copy relative diff to bottom right corner.
248
281
_drawImage (
249
282
source: relativeDiff,
250
283
destination: image,
251
- x: golden.width ,
252
- y: golden.height ,
284
+ x: maxWidth + gap ,
285
+ y: maxHeight + gap ,
253
286
);
254
287
255
288
return image;
@@ -258,8 +291,8 @@ Image _layoutGoldenFailure({
258
291
/// Generates an image that shows the absolute differences between the golden
259
292
/// and the candidate images.
260
293
Image _generateAbsoluteDiff (
261
- GoldenImage golden,
262
- GoldenImage candidate,
294
+ GoldenSceneScreenshot golden,
295
+ GoldenSceneScreenshot candidate,
263
296
GoldenMismatch mismatch,
264
297
) {
265
298
final maxWidth = max (golden.image.width, candidate.image.width);
@@ -283,8 +316,8 @@ void _paintAbsoluteDiff({
283
316
required Image destination,
284
317
required int originX,
285
318
required int originY,
286
- required GoldenImage golden,
287
- required GoldenImage candidate,
319
+ required GoldenSceneScreenshot golden,
320
+ required GoldenSceneScreenshot candidate,
288
321
}) {
289
322
final maxWidth = max (golden.image.width, candidate.image.width);
290
323
final maxHeight = max (golden.image.height, candidate.image.height);
@@ -324,8 +357,8 @@ void _paintAbsoluteDiff({
324
357
/// Generates an image that shows the relative differences between the golden
325
358
/// and the candidate images.
326
359
Image _generateRelativeDiff (
327
- GoldenImage golden,
328
- GoldenImage candidate,
360
+ GoldenSceneScreenshot golden,
361
+ GoldenSceneScreenshot candidate,
329
362
GoldenMismatch mismatch,
330
363
) {
331
364
final maxWidth = max (golden.image.width, candidate.image.width);
@@ -349,8 +382,8 @@ void _paintRelativeDiff({
349
382
required Image destination,
350
383
required int originX,
351
384
required int originY,
352
- required GoldenImage golden,
353
- required GoldenImage candidate,
385
+ required GoldenSceneScreenshot golden,
386
+ required GoldenSceneScreenshot candidate,
354
387
}) {
355
388
final maxWidth = max (golden.image.width, candidate.image.width);
356
389
final maxHeight = max (golden.image.height, candidate.image.height);
@@ -421,82 +454,6 @@ Future<ui.Image> _convertImagePackageToUiImage(Image image) async {
421
454
return completer.future;
422
455
}
423
456
424
- class GoldenFailureScene extends StatelessWidget {
425
- const GoldenFailureScene ({
426
- super .key,
427
- required this .direction,
428
- required this .renderablePhotos,
429
- this .background,
430
- });
431
-
432
- final Axis direction;
433
- final Map <GoldenFailurePhoto , (Uint8List , GlobalKey )> renderablePhotos;
434
- final Widget ? background;
435
-
436
- @override
437
- Widget build (BuildContext context) {
438
- return ColoredBox (
439
- color: const material.Color (0xFF666666 ),
440
- child: Stack (
441
- children: [
442
- if (background != null ) //
443
- Positioned .fill (
444
- child: ColoredBox (color: material.Colors .green),
445
- ),
446
- if (background != null ) //
447
- Positioned .fill (
448
- child: background! ,
449
- ),
450
- Padding (
451
- padding: const EdgeInsets .all (48 ),
452
- child: Flex (
453
- direction: direction,
454
- mainAxisSize: MainAxisSize .min,
455
- spacing: 48 ,
456
- children: [
457
- for (final entry in renderablePhotos.entries) //
458
- SizedBox (
459
- width: entry.key.pixels.width.toDouble (),
460
- child: Column (
461
- mainAxisSize: MainAxisSize .min,
462
- crossAxisAlignment: CrossAxisAlignment .stretch,
463
- children: [
464
- ColoredBox (
465
- color: material.Colors .white,
466
- child: material.Image .memory (
467
- key: entry.value.$2,
468
- entry.value.$1,
469
- width: entry.key.pixels.width.toDouble (),
470
- height: entry.key.pixels.height.toDouble (),
471
- ),
472
- ),
473
- Container (
474
- color: material.Colors .white,
475
- padding: const EdgeInsets .all (16 ),
476
- child: FittedBox (
477
- fit: BoxFit .scaleDown,
478
- child: Text (
479
- entry.key.description,
480
- softWrap: false ,
481
- style: TextStyle (
482
- color: material.Colors .black,
483
- fontFamily: "packages/flutter_test_goldens/OpenSans" ,
484
- ),
485
- ),
486
- ),
487
- ),
488
- ],
489
- ),
490
- ),
491
- ],
492
- ),
493
- ),
494
- ],
495
- ),
496
- );
497
- }
498
- }
499
-
500
457
class GoldenFailurePhoto {
501
458
const GoldenFailurePhoto ({
502
459
required this .description,
0 commit comments