Skip to content

Commit 7f88407

Browse files
Update failure rendering
1 parent 9b96084 commit 7f88407

File tree

3 files changed

+88
-121
lines changed

3 files changed

+88
-121
lines changed

lib/src/goldens/golden_collections.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,14 @@ class GoldenScreenshotMetadata {
6868
///
6969
/// This is *NOT* the same thing as the platform used to run the golden test suite.
7070
final TargetPlatform simulatedPlatform;
71+
72+
GoldenScreenshotMetadata copyWith({
73+
String? description,
74+
TargetPlatform? simulatedPlatform,
75+
}) {
76+
return GoldenScreenshotMetadata(
77+
description: description ?? this.description,
78+
simulatedPlatform: simulatedPlatform ?? this.simulatedPlatform,
79+
);
80+
}
7181
}

lib/src/scenes/failure_scene.dart

Lines changed: 77 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import 'dart:async';
22
import 'dart:math';
3-
import 'dart:typed_data';
43
import 'dart:ui' as ui;
54

65
import 'package:flutter/material.dart' as material;
@@ -88,8 +87,9 @@ Future<Image> paintGoldenMismatchImages(GoldenMismatch mismatch) async {
8887
}
8988

9089
/// 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>[];
9393

9494
for (final item in report.items) {
9595
final mismatch = item.mismatch;
@@ -110,73 +110,95 @@ Future<(Image, FailureSceneMetadata)> paintFailureScene(WidgetTester tester, Gol
110110
absoluteDiff: absoluteDiff,
111111
relativeDiff: relativeDiff,
112112
);
113+
final image = await _convertImagePackageToUiImage(reportImage);
114+
final pixels = (await image.toByteData(format: ui.ImageByteFormat.png))!.buffer.asUint8List();
113115

114-
String description = item.metadata.id;
116+
String description = item.metadata.metadata.description;
115117
if (mismatch is PixelGoldenMismatch) {
116118
description += " (${mismatch.mismatchPixelCount.toInt()}px, ${(mismatch.percent * 100).toStringAsFixed(2)}%)";
117119
} else if (mismatch is WrongSizeGoldenMismatch) {
118120
description += " (wrong size)";
119121
}
122+
120123
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,
124129
),
125130
);
126131
}
127132

128133
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();
129137
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,
133145
),
134146
);
135147
}
136148

137149
for (final extraCandidate in report.extraCandidates) {
138150
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,
142158
),
143159
);
144160
}
145161

146-
return _layoutFailureScene(tester, report, photos);
162+
return _layoutFailureScene(tester, report, photos, layout);
147163
}
148164

149165
/// Generates a single image that shows all the golden failures.
150166
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>{};
153173
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();
157175
}
158176

159177
final sceneKey = GlobalKey();
160178
final scene = GoldenSceneBounds(
161179
child: IntrinsicWidth(
162180
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+
}),
169190
),
170191
),
171192
);
172193
await tester.pumpWidgetAndAdjustWindow(scene);
173194

174195
for (final entry in renderablePhotos.entries) {
175196
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)),
178199
);
179200
}
201+
180202
await tester.pumpAndSettle();
181203

182204
final uiImage = await captureImage(find.byKey(sceneKey).evaluate().single);
@@ -195,10 +217,10 @@ Future<(Image, FailureSceneMetadata)> _layoutFailureScene(
195217
images: [
196218
for (final golden in renderablePhotos.keys)
197219
FailureImageMetadata(
198-
id: golden.description,
220+
id: golden.id,
199221
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!,
202224
),
203225
],
204226
);
@@ -215,11 +237,22 @@ Image _layoutGoldenFailure({
215237
required Image absoluteDiff,
216238
required Image relativeDiff,
217239
}) {
240+
final maxWidth = max(golden.width, candidate.width);
241+
final maxHeight = max(golden.height, candidate.height);
242+
const gap = 4;
243+
218244
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,
221247
);
222248

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+
223256
// Copy golden to top left corner.
224257
_drawImage(
225258
source: golden,
@@ -232,7 +265,7 @@ Image _layoutGoldenFailure({
232265
_drawImage(
233266
source: candidate,
234267
destination: image,
235-
x: golden.width,
268+
x: maxWidth + gap,
236269
y: 0,
237270
);
238271

@@ -241,15 +274,15 @@ Image _layoutGoldenFailure({
241274
source: absoluteDiff,
242275
destination: image,
243276
x: 0,
244-
y: golden.height,
277+
y: maxHeight + gap,
245278
);
246279

247280
// Copy relative diff to bottom right corner.
248281
_drawImage(
249282
source: relativeDiff,
250283
destination: image,
251-
x: golden.width,
252-
y: golden.height,
284+
x: maxWidth + gap,
285+
y: maxHeight + gap,
253286
);
254287

255288
return image;
@@ -258,8 +291,8 @@ Image _layoutGoldenFailure({
258291
/// Generates an image that shows the absolute differences between the golden
259292
/// and the candidate images.
260293
Image _generateAbsoluteDiff(
261-
GoldenImage golden,
262-
GoldenImage candidate,
294+
GoldenSceneScreenshot golden,
295+
GoldenSceneScreenshot candidate,
263296
GoldenMismatch mismatch,
264297
) {
265298
final maxWidth = max(golden.image.width, candidate.image.width);
@@ -283,8 +316,8 @@ void _paintAbsoluteDiff({
283316
required Image destination,
284317
required int originX,
285318
required int originY,
286-
required GoldenImage golden,
287-
required GoldenImage candidate,
319+
required GoldenSceneScreenshot golden,
320+
required GoldenSceneScreenshot candidate,
288321
}) {
289322
final maxWidth = max(golden.image.width, candidate.image.width);
290323
final maxHeight = max(golden.image.height, candidate.image.height);
@@ -324,8 +357,8 @@ void _paintAbsoluteDiff({
324357
/// Generates an image that shows the relative differences between the golden
325358
/// and the candidate images.
326359
Image _generateRelativeDiff(
327-
GoldenImage golden,
328-
GoldenImage candidate,
360+
GoldenSceneScreenshot golden,
361+
GoldenSceneScreenshot candidate,
329362
GoldenMismatch mismatch,
330363
) {
331364
final maxWidth = max(golden.image.width, candidate.image.width);
@@ -349,8 +382,8 @@ void _paintRelativeDiff({
349382
required Image destination,
350383
required int originX,
351384
required int originY,
352-
required GoldenImage golden,
353-
required GoldenImage candidate,
385+
required GoldenSceneScreenshot golden,
386+
required GoldenSceneScreenshot candidate,
354387
}) {
355388
final maxWidth = max(golden.image.width, candidate.image.width);
356389
final maxHeight = max(golden.image.height, candidate.image.height);
@@ -421,82 +454,6 @@ Future<ui.Image> _convertImagePackageToUiImage(Image image) async {
421454
return completer.future;
422455
}
423456

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-
500457
class GoldenFailurePhoto {
501458
const GoldenFailurePhoto({
502459
required this.description,

lib/src/scenes/gallery.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ Image.memory(
621621
Directory(_goldenFailureDirectoryPath).createSync();
622622

623623
await tester.runAsync(() async {
624-
final (failureImage, metadata) = await paintFailureScene(_tester, report);
624+
final (failureImage, metadata) = await paintFailureScene(tester, report, _layout);
625625

626626
Uint8List pngData = encodePng(failureImage);
627627
pngData = pngData.copyWithTextMetadata(

0 commit comments

Comments
 (0)