Skip to content
Closed
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: 2 additions & 0 deletions .github/workflows/test-flutter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
name: multi_packages_app_app
- dir: example/multi_packages_app/theme
name: multi_packages_app_theme
- dir: .
name: adaptive_test
defaults:
run:
working-directory: ${{ matrix.dir }}
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## 0.11.0

- fix: **BREAKING CHANGE** — `loadFonts()` now also loads the system fonts, on
top of the ones the font manifest declares: the families the framework falls
back to on each platform (`Roboto`, `CupertinoSystemText`, `Segoe UI`, ...),
and a dependency's font under its bare family name (`Roboto`) as well as its
manifest name (`packages/my_theme/Roboto`). Widgets that do not specify a font
— a `DatePickerDialog` for instance — used to render as placeholder blocks in
goldens. Opt out with
`AdaptiveTestConfiguration.instance.setLoadPlatformFallbackFonts(false)`.
- feat: `expectGolden` warns when the snapshotted tree asks for a font family no
font is registered for. Configure it with
`AdaptiveTestConfiguration.instance.setMissingFontsBehavior(...)`.
- **Goldens that contained placeholder blocks change with this version**, run
`flutter test --update-goldens` to regenerate them.

## 0.10.4

- fix: catch flaky offstage widget finder errors in awaitImages
Expand Down
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,41 @@ Future<void> testExecutable(FutureOr<void> Function() testMain) async {

> ℹ️ `loadFonts()` loads fonts from `pubspec.yaml` and from every separate package dependency as well.

A font bundled by a dependency is registered under both its manifest name
(`packages/my_theme/Roboto`) and its bare family name (`Roboto`), since that is
the name a `TextStyle` or a `ThemeData` asks for.

`loadFonts()` also registers the families the framework itself falls back to on
each platform — `Roboto` on Android, `CupertinoSystemText` on iOS, `Segoe UI` on
Windows, ... They are declared in no `pubspec.yaml`: they either ship with the
Flutter SDK or belong to the host OS. Without them, any widget that does not
specify a font — a `DatePickerDialog`, a `CupertinoButton` — renders as
placeholder blocks in your goldens.

Opt out to register nothing but what the manifest declares:

```dart
AdaptiveTestConfiguration.instance.setLoadPlatformFallbackFonts(false);
```

#### Catching a missing font

When a golden is taken, `expectGolden` checks the font families the widget tree
asks for against the ones that were loaded, and warns about the text that will
render as blocks:

```
adaptive_test: no font is registered for 'Poppins'.
The text using that family renders as placeholder blocks in the golden.
```

Make it a failure — recommended on CI once your suite is clean — or silence it:

```dart
AdaptiveTestConfiguration.instance
.setMissingFontsBehavior(MissingFontsBehavior.fail);
```

### Setting Up Test Devices

1. Define a set of device variants:
Expand Down
2 changes: 1 addition & 1 deletion example/multi_packages_app/app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ packages:
path: "../../.."
relative: true
source: path
version: "0.10.3"
version: "0.11.0"
async:
dependency: transitive
description:
Expand Down
68 changes: 68 additions & 0 deletions example/multi_packages_app/app/test/src/fonts_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

/// Whether text laid out with [fontFamily] is rendered with a real font.
///
/// The placeholder font `flutter_test` uses for unknown families gives every
/// glyph the same advance, so a narrow and a wide string of the same length
/// measure the same. Any real proportional font measures them differently.
bool _rendersWithARealFont(String fontFamily) {
double widthOf(String text) {
final painter = TextPainter(
text: TextSpan(
text: text,
style: TextStyle(fontFamily: fontFamily, fontSize: 40),
),
textDirection: TextDirection.ltr,
)..layout();
final width = painter.width;
painter.dispose();

return width;
}

return widthOf('iiii') != widthOf('WWWW');
}

void main() {
group('loadFonts in an app whose fonts live in a dependency', () {
test('renders a font bundled by a dependency under its bare name', () {
// ThemeSans is bundled by the theme package this app depends on, so the
// manifest only exposes it as
// `packages/multi_packages_example_theme/ThemeSans` — but a widget or a
// theme defaulting to that family asks for the bare name.
expect(_rendersWithARealFont('ThemeSans'), isTrue);
});

test('keeps the family under its manifest name as well', () {
expect(
_rendersWithARealFont(
'packages/multi_packages_example_theme/ThemeSans',
),
isTrue,
);
});

test('renders the Material default typeface of the host platform', () {
final defaultFamily = Typography.material2021(
platform: TargetPlatform.iOS,
).black.bodyMedium?.fontFamily;

expect(defaultFamily, isNotNull);
// ignore: avoid-non-null-assertion, asserted right above
expect(_rendersWithARealFont(defaultFamily!), isTrue);
});

test('renders text with a family no font could ever provide', () {
// Apple's system typeface: it ships with iOS and cannot be bundled, so it
// is stood in for by the typeface the SDK ships.
const cupertinoTextTheme = CupertinoTextThemeData();
final cupertinoFamily = cupertinoTextTheme.textStyle.fontFamily;

expect(cupertinoFamily, isNotNull);
// ignore: avoid-non-null-assertion, asserted right above
expect(_rendersWithARealFont(cupertinoFamily!), isTrue);
});
});
}
2 changes: 1 addition & 1 deletion example/multi_packages_app/theme/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ packages:
path: "../../.."
relative: true
source: path
version: "0.10.3"
version: "0.11.0"
async:
dependency: transitive
description:
Expand Down
7 changes: 6 additions & 1 deletion example/multi_packages_app/theme/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,9 @@ flutter:
- asset: fonts/Roboto-MediumItalic.ttf
- asset: fonts/Roboto-Regular.ttf
- asset: fonts/Roboto-Thin.ttf
- asset: fonts/Roboto-ThinItalic.ttf
- asset: fonts/Roboto-ThinItalic.ttf
# Declared from an existing asset, to cover a font family bundled by a
# dependency that does not collide with a platform default one.
- family: ThemeSans
fonts:
- asset: fonts/Roboto-Regular.ttf
24 changes: 12 additions & 12 deletions example/simple_app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ packages:
path: "../.."
relative: true
source: path
version: "0.10.3"
version: "0.11.0"
async:
dependency: transitive
description:
Expand All @@ -28,10 +28,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev"
source: hosted
version: "1.4.1"
version: "1.4.0"
clock:
dependency: transitive
description:
Expand Down Expand Up @@ -118,18 +118,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev"
source: hosted
version: "0.12.19"
version: "0.12.17"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev"
source: hosted
version: "0.13.0"
version: "0.11.1"
material_symbols_icons:
dependency: "direct main"
description:
Expand All @@ -142,10 +142,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.17.0"
version: "1.16.0"
package_config:
dependency: transitive
description:
Expand Down Expand Up @@ -227,10 +227,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
version: "0.7.6"
theodo_analysis:
dependency: "direct dev"
description:
Expand All @@ -256,5 +256,5 @@ packages:
source: hosted
version: "14.2.5"
sdks:
dart: ">=3.9.0-0 <4.0.0"
dart: ">=3.8.0-0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
14 changes: 8 additions & 6 deletions lib/adaptive_test.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
export 'src/adaptive/adaptive_test.dart';
export 'src/adaptive/devices_data.dart';
export 'src/adaptive/widgets/adaptive_wrapper.dart';
export 'src/adaptive/window_configuration_tester.dart';
export 'src/adaptive/window_config.dart';
export 'src/adaptive/window_config_data/dynamic_island_data.dart';
export 'src/adaptive/window_config_data/punch_hole_data.dart';
export 'src/adaptive/window_config_data/system_nav_bar_data.dart'
show SystemNavBarData;
export 'src/adaptive/window_config_data/window_config_data.dart';
export 'src/adaptive/window_configuration_tester.dart';
export 'src/configuration.dart';
export 'src/helpers/await_images.dart';
export 'src/helpers/font_registration.dart';
export 'src/helpers/fonts_loader.dart';
export 'src/helpers/goldens_difference.dart';
export 'src/helpers/missing_fonts.dart';
export 'src/helpers/skip_test_extension.dart';
export 'src/adaptive/window_config_data/window_config_data.dart';
export 'src/adaptive/window_config_data/system_nav_bar_data.dart'
show SystemNavBarData;
export 'src/adaptive/window_config_data/punch_hole_data.dart';
export 'src/adaptive/window_config_data/dynamic_island_data.dart';
32 changes: 30 additions & 2 deletions lib/src/adaptive/adaptive_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:adaptive_test/src/adaptive/window_config_data/window_config_data
import 'package:adaptive_test/src/adaptive/window_configuration_tester.dart';
import 'package:adaptive_test/src/configuration.dart';
import 'package:adaptive_test/src/helpers/await_images.dart';
import 'package:adaptive_test/src/helpers/missing_fonts.dart';
import 'package:adaptive_test/src/helpers/skip_test_extension.dart';
import 'package:adaptive_test/src/helpers/target_platform_extension.dart';
import 'package:flutter/foundation.dart';
Expand Down Expand Up @@ -120,12 +121,39 @@ extension Adaptive on WidgetTester {
await awaitImages();
}

// Find by its type except if the widget's unique key was given.
final finder =
byKey != null ? find.byKey(byKey) : find.byType(AdaptiveWrapper);

_reportMissingFonts(finder);

final key = path ??
'preview/${windowConfig.name}-${name.snakeCase}$localSuffix.png';
await expectLater(
// Find by its type except if the widget's unique key was given.
byKey != null ? find.byKey(byKey) : find.byType(AdaptiveWrapper),
finder,
matchesGoldenFile(key, version: version),
);
}

/// Warns about the font families the snapshotted tree asks for while no font
/// is registered for them: they render as placeholder blocks in the golden.
void _reportMissingFonts(Finder finder) {
final configuration = AdaptiveTestConfiguration.instance;
if (configuration.missingFontsBehavior == MissingFontsBehavior.ignore) {
return;
}

final renderObject = finder.evaluate().firstOrNull?.renderObject;
if (renderObject == null) return;

final warnedFamilies = reportUnregisteredFontFamilies(
findUnregisteredFontFamilies(
renderObject,
loadedFamilies: configuration.loadedFontFamilies,
),
configuration.missingFontsBehavior,
alreadyWarned: configuration.warnedFontFamilies,
);
configuration.addWarnedFontFamilies(warnedFamilies);
}
}
2 changes: 2 additions & 0 deletions lib/src/adaptive/devices_data.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// ignore_for_file: constant_identifier_names

import 'package:adaptive_test/src/adaptive/window_config_data/dynamic_island_data.dart';
import 'package:adaptive_test/src/adaptive/window_config_data/punch_hole_data.dart';
import 'package:adaptive_test/src/adaptive/window_config_data/system_nav_bar_data.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/src/adaptive/widgets/adaptive_wrapper.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import 'package:adaptive_test/src/adaptive/widgets/layers/hardware_layer.dart';
import 'package:adaptive_test/src/adaptive/widgets/layers/keyboard_layer.dart';
import 'package:adaptive_test/src/adaptive/widgets/layers/system_nav_bar_layer.dart';
import 'package:adaptive_test/src/adaptive/window_config_data/window_config_data.dart';
import 'package:adaptive_test/src/adaptive/window_config.dart';
import 'package:adaptive_test/src/adaptive/window_config_data/window_config_data.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

Expand Down
2 changes: 1 addition & 1 deletion lib/src/adaptive/widgets/layers/keyboard_layer.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:adaptive_test/src/adaptive/window_configuration_tester.dart';
import 'package:adaptive_test/src/adaptive/window_config.dart';
import 'package:adaptive_test/src/adaptive/window_configuration_tester.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class ThreeButtonSystemNavBarLayer extends StatelessWidget {
children: const [
Icons.arrow_back_ios_rounded,
Icons.circle,
Icons.square_rounded
Icons.square_rounded,
]
.map(
(iconData) => Icon(
Expand Down
3 changes: 2 additions & 1 deletion lib/src/adaptive/window_config_data/window_config_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ class WindowConfigData extends Equatable {
/// This is null when the device has no notch.
final Size? notchSize;

/// Describe the size of the device physical screen top dynamic island in `dp`.
/// Describe the size of the device physical screen top dynamic island in
/// `dp`.
///
/// This is null when the device has no dynamic island.
final DynamicIslandData? dynamicIsland;
Expand Down
6 changes: 4 additions & 2 deletions lib/src/adaptive/window_configuration_tester.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ extension WidgetTesterWithConfigurableWindow on WidgetTester {
addTearDown(view.resetViewInsets);
}

/// Configure the tester window to represent an opened keyboard on the given device variant.
/// Configure the tester window to represent an opened keyboard on the given
/// device variant.
void configureOpenedKeyboardWindow(WindowConfigData windowConfig) {
view.viewInsets = windowConfig.viewInsets;
view.padding = windowConfig.padding.copyWith(bottom: 0);
}

/// Configure the tester window to represent a closed keyboard on the given device variant.
/// Configure the tester window to represent a closed keyboard on the given
/// device variant.
void configureClosedKeyboardWindow(WindowConfigData windowConfig) {
view.resetViewInsets();
view.padding = windowConfig.padding;
Expand Down
Loading
Loading