Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/marketing_goldens/flutter_test_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:super_text_layout/super_text_layout.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
// Adjust the theme that's applied to all golden tests in this suite.
GoldenSceneTheme.push(GoldenSceneTheme.standard.copyWith(
GoldenTestConfig.push(GoldenTestConfig.standard.copyWith(
directory: Directory("."),
));

Expand Down
1 change: 1 addition & 0 deletions lib/flutter_test_goldens.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ export 'src/scenes/layouts/magazine_layout.dart';
export 'src/scenes/layouts/row_and_column_layout.dart';
export 'src/scenes/scene_layout.dart';
export 'src/scenes/single_shot.dart';
export 'src/test_config.dart';
export 'src/logging.dart';
export 'src/test_runners.dart';
3 changes: 2 additions & 1 deletion lib/src/scenes/gallery.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' hide Image;
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_test_goldens/src/test_config.dart';
import 'package:flutter_test_goldens/src/flutter/flutter_camera.dart';
import 'package:flutter_test_goldens/src/flutter/flutter_test_extensions.dart';
import 'package:flutter_test_goldens/src/goldens/golden_collections.dart';
Expand Down Expand Up @@ -40,7 +41,7 @@ class Gallery {
_itemBoundsFinder = itemBoundsFinder,
_layout = layout,
_itemSetup = itemSetup {
_directory = directory ?? GoldenSceneTheme.current.directory;
_directory = directory ?? GoldenTestConfig.current.directory;
}

/// A scaffold built around each item widget tree in this scene when new screenshots are
Expand Down
7 changes: 0 additions & 7 deletions lib/src/scenes/golden_files.dart

This file was deleted.

31 changes: 4 additions & 27 deletions lib/src/scenes/golden_scene.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart' show Colors, MaterialApp, Scaffold, ThemeData;
Expand All @@ -11,19 +10,9 @@ import 'package:flutter_test_goldens/src/goldens/golden_collections.dart';
import 'package:flutter_test_goldens/src/goldens/golden_comparisons.dart';
import 'package:flutter_test_goldens/src/goldens/golden_rendering.dart';
import 'package:flutter_test_goldens/src/goldens/golden_scenes.dart';
import 'package:flutter_test_goldens/src/scenes/golden_files.dart';
import 'package:golden_bricks/golden_bricks.dart';

/// A theme, which is applied to various [GoldenScene]s.
///
/// The purpose of [GoldenSceneTheme] is to make it easy to configure similar visual styles
/// for all [GoldenScene]s in a project, file, group, or within a test.
///
/// A [GoldenSceneTheme] captures various details that are visually common among
/// [GoldenScene]s. For example, a theme includes an [itemScaffold] and [itemDecorator] that are
/// built around every golden in a scene. It includes a [background] that renders behind the
/// golden images. For logistics, it includes a relative [directory] path, which says where
/// to store [GoldenScene]s in relation to each golden test file.
/// A theme, which contains visual aspects that are common to most types of Golden Scenes.
class GoldenSceneTheme {
/// The [GoldenSceneTheme] that should be used for the currently executing test.
///
Expand All @@ -50,16 +39,16 @@ class GoldenSceneTheme {
addTearDown(() => GoldenSceneTheme.pop());
}

/// Pushes the given [theme] on to the global theme stack, which will make it
/// Pushes the given [theme] on to the global config stack, which will make it
/// the global theme until there's a call to [pop].
///
/// Pushing and popping themes is useful within group and test setups and teardowns
/// to configure a [GoldenSceneTheme] for that group or test.
static void push(GoldenSceneTheme theme) => _themeStack.add(theme);

/// Removes to the top theme on the global stack, which was added with [push].
/// Removes to the top config on the global stack, which was added with [push].
///
/// If there is no corresponding theme that was added by an earlier [push], then
/// If there is no corresponding config that was added by an earlier [push], then
/// this method does nothing.
static void pop() {
if (_themeStack.length > 1) {
Expand All @@ -69,7 +58,6 @@ class GoldenSceneTheme {

/// The default [GoldenSceneTheme] for all tests.
static final standard = GoldenSceneTheme(
directory: defaultGoldenDirectory,
background: defaultGoldenSceneBackground,
defaultTextStyle: TextStyle(
color: Colors.black,
Expand All @@ -84,7 +72,6 @@ class GoldenSceneTheme {
/// This theme isn't used anywhere by default, but it's a convenient theme if
/// you want a dark theme and you don't care about all the specifics.
static final standardDark = GoldenSceneTheme(
directory: defaultGoldenDirectory,
background: defaultDarkGoldenSceneBackground,
defaultTextStyle: TextStyle(
color: Colors.white,
Expand All @@ -96,20 +83,12 @@ class GoldenSceneTheme {
);

const GoldenSceneTheme({
required this.directory,
required this.background,
required this.defaultTextStyle,
required this.itemScaffold,
required this.itemDecorator,
});

/// The relative path from a running test to where that test's goldens are
/// stored.
///
/// The [standard] directory is `Directory("./goldens/")`. To store goldens in the same
/// directory as the running tests, use `Directory(".")`.
final Directory directory;

/// The background that's painted full-bleed across the scene, behind the goldens.
///
/// The [standard] background is a color.
Expand All @@ -133,14 +112,12 @@ class GoldenSceneTheme {
final GoldenSceneItemDecorator itemDecorator;

GoldenSceneTheme copyWith({
Directory? directory,
GoldenSceneBackground? background,
TextStyle? defaultTextStyle,
GoldenSceneItemScaffold? itemScaffold,
GoldenSceneItemDecorator? itemDecorator,
}) {
return GoldenSceneTheme(
directory: directory ?? this.directory,
background: background ?? this.background,
defaultTextStyle: defaultTextStyle ?? this.defaultTextStyle,
itemScaffold: itemScaffold ?? this.itemScaffold,
Expand Down
1 change: 1 addition & 0 deletions lib/src/scenes/layouts/animation_timeline_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_test_goldens/src/test_config.dart';
import 'package:flutter_test_goldens/src/flutter/flutter_pixel_alignment.dart';
import 'package:flutter_test_goldens/src/fonts/fonts.dart';
import 'package:flutter_test_goldens/src/goldens/golden_collections.dart';
Expand Down
102 changes: 102 additions & 0 deletions lib/src/scenes/layouts/golden_image_showcase.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import 'package:flutter/widgets.dart';

class GoldenImageShowcase extends SlottedMultiChildRenderObjectWidget {
static const _slotGolden = "golden";
static const _slotLabel = "label";

const GoldenImageShowcase({
super.key,
required this.golden,
required this.label,
this.description,
});

final Widget golden;
final Widget label;

final String? description;

@override
Iterable get slots => [_slotGolden, _slotLabel];

@override
Widget? childForSlot(slot) {
switch (slot) {
case _slotGolden:
return golden;
case _slotLabel:
return label;
default:
return null;
}
}

@override
RenderGoldenShowcase createRenderObject(BuildContext context) {
return RenderGoldenShowcase()..description = description;
}

@override
void updateRenderObject(BuildContext context, RenderGoldenShowcase renderObject) {
renderObject.description = description;
}
}

class RenderGoldenShowcase extends RenderBox with SlottedContainerRenderObjectMixin {
String? description;

@override
void performLayout() {
print("------- performLayout -------");
print("Laying out $description - constraints: $constraints");
final renderGolden = childForSlot(GoldenImageShowcase._slotGolden)! as RenderBox;
renderGolden.layout(constraints.copyWith(minHeight: 0), parentUsesSize: true);
print(
" - golden, intrinsic width: ${renderGolden.computeMinIntrinsicWidth(double.infinity)}, wants to be: ${renderGolden.size} ($renderGolden)");

final renderLabel = childForSlot(GoldenImageShowcase._slotLabel)! as RenderBox;
renderLabel.layout(constraints.copyWith(minHeight: 0), parentUsesSize: true);
print(
" - label intrinsic width: ${renderLabel.computeMaxIntrinsicWidth(double.infinity)}, wants to be: ${renderLabel.size} ($renderLabel)");

late final double width;
if (renderGolden.size.width >= renderLabel.size.width) {
print("Golden is setting the reference width");
width = renderGolden.size.width;
print(" - golden width: $width");

final goldenHeight = renderGolden.computeMinIntrinsicHeight(width);
print(" - golden min intrinsic height: $goldenHeight");
renderGolden.layout(BoxConstraints.tightFor(width: width, height: goldenHeight), parentUsesSize: true);
print(" - final golden size: ${renderGolden.size}");

print(" - label min intrinsic height: ${renderLabel.computeMinIntrinsicHeight(width)}");
renderLabel.layout(BoxConstraints.tightFor(width: width), parentUsesSize: true);
print(" - final label size: ${renderLabel.size}");
} else {
print("Label is setting the reference width");
width = renderLabel.size.width;
print(" - label width: $width");
print(" - label min intrinsic height: ${renderLabel.computeMinIntrinsicHeight(width)}");

final goldenHeight = renderGolden.computeMinIntrinsicHeight(width);
print(" - golden min intrinsic height: $goldenHeight");
renderGolden.layout(BoxConstraints.tightFor(width: width, height: goldenHeight), parentUsesSize: true);
print(" - final golden size: ${renderGolden.size}");
}

final desiredSize = Size(width, renderGolden.size.height + renderLabel.size.height);
print("Description: $description, desired size: $desiredSize, max allowable size: ${constraints.biggest}");

size = Size(width, renderGolden.size.height + renderLabel.size.height);
}

@override
void paint(PaintingContext context, Offset offset) {
final renderGolden = childForSlot(GoldenImageShowcase._slotGolden)! as RenderBox;
final renderLabel = childForSlot(GoldenImageShowcase._slotLabel)! as RenderBox;

context.paintChild(renderGolden, offset + Offset.zero);
context.paintChild(renderLabel, offset + Offset(0, renderGolden.size.height.ceilToDouble()));
}
}
3 changes: 2 additions & 1 deletion lib/src/scenes/timeline.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'dart:ui' as ui;

import 'package:flutter/material.dart' hide Image;
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_test_goldens/src/test_config.dart';
import 'package:flutter_test_goldens/src/flutter/flutter_camera.dart';
import 'package:flutter_test_goldens/src/flutter/flutter_test_extensions.dart';
import 'package:flutter_test_goldens/src/goldens/golden_collections.dart';
Expand Down Expand Up @@ -548,7 +549,7 @@ class Timeline {

String get _goldenDirectory => "$_testFileDirectory$_relativeGoldenDirectory$separator";

String get _relativeGoldenDirectory => _directory?.path ?? GoldenSceneTheme.current.directory.path;
String get _relativeGoldenDirectory => _directory?.path ?? GoldenTestConfig.current.directory.path;

/// Calculates and returns a complete file path to the golden file specified by
/// this gallery, which consists of the current test file directory + an optional
Expand Down
94 changes: 94 additions & 0 deletions lib/src/test_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import 'dart:io';

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_test_goldens/src/scenes/golden_scene.dart';

/// A configuration for golden tests.
///
/// The purpose of [GoldenTestConfig] is to make it easy to configure multiple golden
/// tests in a project, file, group, or within a test.
///
/// A [GoldenTestConfig] captures various details that are common among [GoldenScene]s. For
/// example, a relative [directory] path, which says where to store [GoldenScene]s in
/// relation to each golden test file.
class GoldenTestConfig {
/// The [GoldenTestConfig] that should be used for the currently executing test.
///
/// By default, this value is [standard]. The theme can be customized
/// by [push]ing a new theme on the stack. Any theme that is [push]ed on the stack
/// will be reported as the [current] theme until it is [pop]ed.
static GoldenTestConfig get current => _themeStack.last;

static final _themeStack = [standard];

/// Configures a [setUp] that makes the given [config] the [current] global
/// [GoldenTestConfig] within the current test group, and configures a
/// [tearDown] that returns the previous global theme when the group exits.
static void useForGroup(GoldenTestConfig config) {
setUp(() => GoldenTestConfig.push(config));
tearDown(() => GoldenTestConfig.pop());
}

/// Configures a [setUp] that makes the given [config] the [current] global
/// [GoldenTestConfig] within the current test, and configures a [tearDown]
/// that returns the previous global theme when the group exits.
static void useForTest(GoldenTestConfig config) {
GoldenTestConfig.push(config);
addTearDown(() => GoldenTestConfig.pop());
}

/// Pushes the given [config] on to the global config stack, which will make it
/// the global theme until there's a call to [pop].
///
/// Pushing and popping themes is useful within group and test setups and teardowns
/// to configure a [GoldenTestConfig] for that group or test.
static void push(GoldenTestConfig config) => _themeStack.add(config);

/// Removes to the top config on the global stack, which was added with [push].
///
/// If there is no corresponding config that was added by an earlier [push], then
/// this method does nothing.
static void pop() {
if (_themeStack.length > 1) {
_themeStack.removeLast();
}
}

/// The default [GoldenTestConfig] for all tests.
static final standard = GoldenTestConfig(
directory: defaultGoldenDirectory,
);

/// The default dark [GoldenTestConfig].
///
/// This theme isn't used anywhere by default, but it's a convenient theme if
/// you want a dark theme and you don't care about all the specifics.
static final standardDark = GoldenTestConfig(
directory: defaultGoldenDirectory,
);

const GoldenTestConfig({
required this.directory,
});

/// The relative path from a running test to where that test's goldens are stored.
///
/// The [standard] directory is `Directory("./goldens/")`. To store goldens in the same
/// directory as the running tests, use `Directory(".")`.
final Directory directory;

GoldenTestConfig copyWith({
Directory? directory,
GoldenSceneTheme? theme,
}) {
return GoldenTestConfig(
directory: directory ?? this.directory,
);
}
}

/// The standard path to where goldens are saved.
///
/// The default path is a `/goldens/` directory, which sits in the same parent directory as
/// the test file that's running the test.
final defaultGoldenDirectory = Directory("./goldens/");
2 changes: 1 addition & 1 deletion test_goldens/flutter_test_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:flutter_test_goldens/flutter_test_goldens.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
// Adjust the theme that's applied to all golden tests in this suite.
GoldenSceneTheme.push(GoldenSceneTheme.standard.copyWith(
GoldenTestConfig.push(GoldenTestConfig.standard.copyWith(
directory: Directory("."),
));

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading