Skip to content

Commit 826fa8b

Browse files
Added a gallery builder to go along with the film strip builder.
1 parent 95e69e4 commit 826fa8b

13 files changed

+583
-75
lines changed

README.md

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,53 @@
22
A toolkit for writing golden tests.
33

44
## Getting Started
5+
The following shows an example of how to define a golden test that captures
6+
a variety of independent UIs as a gallery.
7+
8+
```dart
9+
testGoldenSceneOnMac("extended floating action button gallery", (tester) async {
10+
await Gallery(
11+
tester,
12+
itemDecorator: (context, child) {
13+
return FlutterWidgetScaffold(
14+
child: child,
15+
);
16+
},
17+
items: [
18+
GalleryItem.withWidget(
19+
id: "1",
20+
description: "Icon + Text",
21+
child: FloatingActionButton.extended(
22+
icon: Icon(Icons.edit),
23+
label: Text("Hello"),
24+
onPressed: () {},
25+
),
26+
),
27+
GalleryItem.withWidget(
28+
id: "2",
29+
description: "Icon",
30+
child: FloatingActionButton.extended(
31+
icon: Icon(Icons.edit),
32+
label: Text(""),
33+
onPressed: () {},
34+
),
35+
),
36+
GalleryItem.withWidget(
37+
id: "3",
38+
description: "Text",
39+
child: FloatingActionButton.extended(
40+
label: Text("Hello"),
41+
onPressed: () {},
42+
),
43+
),
44+
],
45+
).renderOrCompareGolden(
46+
goldenName: "button_extended_fab_gallery",
47+
layout: SceneLayout.row,
48+
);
49+
});
50+
```
51+
552
The following shows an example of how to define a golden test that captures
653
screenshots over time, placing all of them in a single scene file.
754

@@ -34,7 +81,7 @@ testGoldenSceneOnMac("elevated button interactions", (tester) async {
3481
// an existing scene file.
3582
.renderOrCompareGolden(
3683
goldenName: "button_elevated_interactions",
37-
layout: FilmStripLayout.row,
84+
layout: SceneLayout.row,
3885
);
3986
});
4087
```

lib/flutter_test_goldens.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
export 'src/flutter/flutter_test_extensions.dart';
22
export 'src/fonts/fonts.dart';
3-
export 'src/galleries/film_strip.dart';
4-
export 'src/galleries/gallery.dart';
53
export 'src/goldens/golden_camera.dart';
64
export 'src/goldens/golden_collections.dart';
75
export 'src/goldens/golden_comparisons.dart';
86
export 'src/goldens/golden_rendering.dart';
97
export 'src/goldens/golden_scenes.dart';
108
export 'src/qr_codes/qr_code_image_scanning.dart';
9+
export 'src/scenes/film_strip.dart';
10+
export 'src/scenes/golden_scene.dart';
1111
export 'src/logging.dart';
1212
export 'src/test_runners.dart';
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import 'package:flutter/material.dart' as m;
2+
import 'package:image/image.dart';
3+
4+
double calculateColorMismatchPercent(Color c1, Color c2) {
5+
final doLog = false; //c1.r == 255; //c1.r != c1.g;
6+
final color1 = m.Color.fromARGB(255, c1.r.toInt(), c1.g.toInt(), c1.b.toInt());
7+
final hsv1 = m.HSVColor.fromColor(color1);
8+
9+
final color2 = m.Color.fromARGB(255, c2.r.toInt(), c2.g.toInt(), c2.b.toInt());
10+
final hsv2 = m.HSVColor.fromColor(color2);
11+
12+
// Calculate per-component difference.
13+
var deltaHue = (hsv2.hue - hsv1.hue).abs();
14+
final deltaSaturation = (hsv2.saturation - hsv2.saturation).abs();
15+
final deltaValue = (hsv2.value - hsv1.value).abs();
16+
17+
// Handle hue circularity (i.e., fact that 360 degrees go back to 0 degrees)
18+
deltaHue = deltaHue > 180 ? 360 - deltaHue : deltaHue;
19+
20+
// Normalize each component difference.
21+
deltaHue = deltaHue / 180;
22+
// Other components are already in range [0, 1].
23+
24+
// Combine components instead single distance value.
25+
//
26+
// The difference formula is arbitrary, but the goal is to draw attention to differences that are likely
27+
// to be important, which means reducing differences between things that aren't. In this equation we treat
28+
// the value difference as the most important, such that black vs white goes directly to the max. Assuming
29+
// the difference in value doesn't eat up all the difference, the hue is then given 3 times the
30+
// weight of the saturation difference.
31+
//
32+
// This formula can easily exceed the max value of `1.0` so it's clamped. This means that many different
33+
// color variations will all be at the highest intensity, but that's OK, be there is more than one color
34+
// difference that's worthy of attention.
35+
final difference = (deltaValue + ((deltaHue * 3) + deltaSaturation) / 4).clamp(0.0, 1.0);
36+
if (doLog) {
37+
print(
38+
"Color 1 - red: ${c1.r}, green: ${c1.g}, blue: ${c1.b} - h: ${hsv1.hue}, s: ${hsv1.saturation}, v: ${hsv1.value}");
39+
print(
40+
"Color 2 - red: ${c2.r}, green: ${c2.g}, blue: ${c2.b} - h: ${hsv2.hue}, s: ${hsv2.saturation}, v: ${hsv2.value}");
41+
print("Difference: $difference");
42+
}
43+
44+
return difference;
45+
}

lib/src/logging.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import 'package:logging/logging.dart';
22

3+
export 'package:logging/logging.dart' show Level;
4+
35
/// Loggers for Flutter Test Goldens (FTG).
46
abstract class FtgLog {
57
static final pipeline = Logger('ftg.pipeline');
68

79
static final _activeLoggers = <Logger>{};
810

9-
static void initAllLogs(Level level) {
10-
initLoggers(level, {Logger.root});
11+
static void initAllLogs([Level? level = Level.ALL]) {
12+
initLoggers({Logger.root}, level);
1113
}
1214

13-
static void initLoggers(Level level, Set<Logger> loggers) {
15+
static void initLoggers(Set<Logger> loggers, [Level? level = Level.ALL]) {
1416
hierarchicalLoggingEnabled = true;
1517

1618
for (final logger in loggers) {

lib/src/galleries/film_strip.dart renamed to lib/src/scenes/film_strip.dart

Lines changed: 9 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ import 'package:flutter/material.dart' hide Image;
88
import 'package:flutter/material.dart' as m;
99
import 'package:flutter_test/flutter_test.dart';
1010
import 'package:flutter_test_goldens/src/flutter/flutter_test_extensions.dart';
11-
import 'package:flutter_test_goldens/src/galleries/gallery.dart';
1211
import 'package:flutter_test_goldens/src/goldens/golden_camera.dart';
1312
import 'package:flutter_test_goldens/src/goldens/golden_collections.dart';
1413
import 'package:flutter_test_goldens/src/goldens/golden_comparisons.dart';
1514
import 'package:flutter_test_goldens/src/goldens/golden_rendering.dart';
1615
import 'package:flutter_test_goldens/src/goldens/golden_scenes.dart';
16+
import 'package:flutter_test_goldens/src/goldens/pixel_comparisons.dart';
1717
import 'package:flutter_test_goldens/src/logging.dart';
18+
import 'package:flutter_test_goldens/src/scenes/golden_scene.dart';
19+
import 'package:flutter_test_goldens/src/scenes/scene_layout.dart';
1820
import 'package:image/image.dart';
1921
import 'package:qr_bar_code/code/code.dart';
2022

@@ -119,7 +121,7 @@ class FilmStrip {
119121

120122
Future<void> renderOrCompareGolden({
121123
required String goldenName,
122-
required FilmStripLayout layout,
124+
required SceneLayout layout,
123125
Widget? goldenBackground,
124126
m.Color qrCodeColor = m.Colors.black,
125127
m.Color qrCodeBackgroundColor = m.Colors.white,
@@ -231,7 +233,7 @@ class FilmStrip {
231233
Future<GoldenSceneMetadata> _layoutPhotos(
232234
List<GoldenPhoto> photos,
233235
Map<GoldenPhoto, (Uint8List, GlobalKey)> renderablePhotos,
234-
FilmStripLayout layout, {
236+
SceneLayout layout, {
235237
Widget? goldenBackground,
236238
Widget? qrCode,
237239
required m.Color qrCodeBackgroundColor,
@@ -242,9 +244,9 @@ class FilmStrip {
242244

243245
late final Axis filmStripDirection;
244246
switch (layout) {
245-
case FilmStripLayout.row:
247+
case SceneLayout.row:
246248
filmStripDirection = Axis.horizontal;
247-
case FilmStripLayout.column:
249+
case SceneLayout.column:
248250
filmStripDirection = Axis.vertical;
249251
}
250252

@@ -311,7 +313,7 @@ class FilmStrip {
311313
mainAxisSize: MainAxisSize.min,
312314
crossAxisAlignment: CrossAxisAlignment.stretch,
313315
children: [
314-
GoldenGallery(
316+
GoldenScene(
315317
key: galleryKey,
316318
direction: filmStripDirection,
317319
renderablePhotos: renderablePhotos,
@@ -434,7 +436,7 @@ class FilmStrip {
434436
failureImage.setPixel(x, maxHeight + y, absoluteDiffColor);
435437

436438
// Paint this pixel in the relative severity diff image.
437-
final mismatchPercent = _calculateColorMismatchPercent(goldenPixel, screenshotPixel);
439+
final mismatchPercent = calculateColorMismatchPercent(goldenPixel, screenshotPixel);
438440
final yellowAmount = ui.lerpDouble(0.2, 1.0, mismatchPercent)!;
439441
failureImage.setPixel(
440442
goldenWidth + x,
@@ -456,49 +458,6 @@ class FilmStrip {
456458
FtgLog.pipeline.info("No golden mismatches found");
457459
}
458460
}
459-
460-
double _calculateColorMismatchPercent(Color c1, Color c2) {
461-
final doLog = false; //c1.r == 255; //c1.r != c1.g;
462-
final color1 = m.Color.fromARGB(255, c1.r.toInt(), c1.g.toInt(), c1.b.toInt());
463-
final hsv1 = m.HSVColor.fromColor(color1);
464-
465-
final color2 = m.Color.fromARGB(255, c2.r.toInt(), c2.g.toInt(), c2.b.toInt());
466-
final hsv2 = m.HSVColor.fromColor(color2);
467-
468-
// Calculate per-component difference.
469-
var deltaHue = (hsv2.hue - hsv1.hue).abs();
470-
final deltaSaturation = (hsv2.saturation - hsv2.saturation).abs();
471-
final deltaValue = (hsv2.value - hsv1.value).abs();
472-
473-
// Handle hue circularity (i.e., fact that 360 degrees go back to 0 degrees)
474-
deltaHue = deltaHue > 180 ? 360 - deltaHue : deltaHue;
475-
476-
// Normalize each component difference.
477-
deltaHue = deltaHue / 180;
478-
// Other components are already in range [0, 1].
479-
480-
// Combine components instead single distance value.
481-
//
482-
// The difference formula is arbitrary, but the goal is to draw attention to differences that are likely
483-
// to be important, which means reducing differences between things that aren't. In this equation we treat
484-
// the value difference as the most important, such that black vs white goes directly to the max. Assuming
485-
// the difference in value doesn't eat up all the difference, the hue is then given 3 times the
486-
// weight of the saturation difference.
487-
//
488-
// This formula can easily exceed the max value of `1.0` so it's clamped. This means that many different
489-
// color variations will all be at the highest intensity, but that's OK, be there is more than one color
490-
// difference that's worthy of attention.
491-
final difference = (deltaValue + ((deltaHue * 3) + deltaSaturation) / 4).clamp(0.0, 1.0);
492-
if (doLog) {
493-
print(
494-
"Color 1 - red: ${c1.r}, green: ${c1.g}, blue: ${c1.b} - h: ${hsv1.hue}, s: ${hsv1.saturation}, v: ${hsv1.value}");
495-
print(
496-
"Color 2 - red: ${c2.r}, green: ${c2.g}, blue: ${c2.b} - h: ${hsv2.hue}, s: ${hsv2.saturation}, v: ${hsv2.value}");
497-
print("Difference: $difference");
498-
}
499-
500-
return difference;
501-
}
502461
}
503462

504463
class _FilmStripSetup {
@@ -532,8 +491,3 @@ class FilmStripTestContext {
532491

533492
final scratchPad = <String, dynamic>{};
534493
}
535-
536-
enum FilmStripLayout {
537-
row,
538-
column;
539-
}

0 commit comments

Comments
 (0)