Skip to content

Commit 3c674fa

Browse files
authored
Merge pull request #20 from eBay/improve-font-loading
Improve font loading
2 parents de27314 + 548d28e commit 3c674fa

19 files changed

+248
-57
lines changed

packages/golden_toolkit/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## 0.2.0-dev
4+
5+
Improved the mechanism for loading font assets. Consumers no longer need to supply a directory to read the .ttf files from.
6+
7+
They can now simply call: `await loadAppFonts()` and the package will automatically load any font assets from their pubspec.yaml or
8+
from any packages they depend on.
9+
310
## 0.1.0-dev
411

512
Initial release. Includes utility methods for easily pumping complex widgets, loading real fonts, and for writing more advanced Golden-based tests.

packages/golden_toolkit/README.md

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -149,14 +149,37 @@ By default, flutter test only uses a single "test" font called Ahem.
149149
This font is designed to show black spaces for every character and icon. This obviously makes goldens much less valuable.
150150

151151
To make the goldens more useful, we have a utility to dynamically inject additional fonts into the flutter test engine so that we can get more human viewable output.
152-
In order to inject your fonts, just call font loader function on top of your test file:
152+
In order to inject your fonts, we have a helper method:
153153

154154
```dart
155-
await loadAppFonts(from: 'yourFontDirectoryPath');
155+
await loadAppFonts();
156156
```
157157

158-
Function will load all the fonts from that directory using FontLoader so they are properly rendered during the test.
159-
Material icons like `Icons.battery` will be rendered in goldens ONLY if your pre-load MaterialIcons-Regular.ttf font that contains all the icons.
158+
This function will automatically load the `Roboto` font, and any fonts included from packages you depend on so that they are properly rendered during the test.
159+
160+
Material icons like `Icons.battery` will be rendered in goldens ONLY if your pubspec.yaml includes:
161+
162+
```yaml
163+
flutter:
164+
uses-material-design: true
165+
```
166+
167+
Note, if you need Cupertino fonts, you will need to find a copy of .SF UI Display Text, and .SF UI Text to include in your package's yaml. These are not included in this package by default for licensing reasons.
168+
169+
The easiest, and recommended way to use this, is to create a `flutter_test_config.dart` file in the root of your package's test directory with the following content:
170+
171+
```dart
172+
import 'dart:async';
173+
174+
import 'package:golden_toolkit/golden_toolkit.dart';
175+
176+
Future<void> main(FutureOr<void> testMain()) async {
177+
await loadAppFonts();
178+
return testMain();
179+
}
180+
```
181+
182+
For more information on `flutter_test_config.dart`, see the [Flutter Docs](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html)
160183

161184
### testGoldens()
162185

@@ -221,11 +244,11 @@ This software contains some 3rd party software licensed under open source licens
221244
Available at URL: [https://github.com/google/fonts/tree/master/apache/roboto](https://github.com/google/fonts/tree/master/apache/roboto)
222245
License: Available under Apache license at [https://github.com/google/fonts/blob/master/apache/roboto/LICENSE.txt](https://github.com/google/fonts/blob/master/apache/roboto/LICENSE.txt)
223246

224-
2. Material Icon File:
225-
URL: [https://github.com/google/material-design-icons](https://github.com/google/material-design-icons)
226-
License: Available under Apache license at [https://github.com/google/material-design-icons/blob/master/LICENSE](https://github.com/google/material-design-icons/blob/master/LICENSE)
227-
228-
3. Icons at:
247+
2. Icons at:
229248
Author: Adnen Kadri
230249
URL: [https://www.iconfinder.com/iconsets/weather-281](https://www.iconfinder.com/iconsets/weather-281)
231250
License: Free for commercial use
251+
252+
3. OpenSans Font File:
253+
Available at URL: [https://github.com/googlefonts/opensans](https://github.com/googlefonts/opensans)
254+
License: Available under Apache license at [https://github.com/googlefonts/opensans/blob/master/LICENSE.txt](https://github.com/googlefonts/opensans/blob/master/LICENSE.txt)
-131 KB
Binary file not shown.

packages/golden_toolkit/lib/src/font_loader.dart

Lines changed: 38 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,54 @@
55
/// license that can be found in the LICENSE file or at
66
/// https://opensource.org/licenses/BSD-3-Clause
77
/// ***************************************************
8-
import 'dart:io';
9-
import 'dart:typed_data';
8+
9+
import 'dart:convert';
1010
import 'package:flutter/services.dart';
11-
import 'package:meta/meta.dart';
11+
import 'package:flutter_test/flutter_test.dart';
1212

1313
///By default, flutter test only uses a single "test" font called Ahem.
1414
///
1515
///This font is designed to show black spaces for every character and icon. This obviously makes goldens much less valuable.
1616
///
17-
///To make the goldens more useful, we have a utility to dynamically inject additional fonts into the flutter test engine so that we can get more human viewable output.
18-
/// Path to your folder with fonts [from] in required
19-
Future<void> loadAppFonts({@required String from}) async {
20-
if (_hasLoaded) {
21-
print('skipping fonts');
22-
return;
23-
}
17+
///To make the goldens more useful, we will automatically load any fonts included in your pubspec.yaml as well as from
18+
///packages you depend on.
19+
Future<void> loadAppFonts() async {
20+
TestWidgetsFlutterBinding.ensureInitialized();
21+
final fontManifest = await rootBundle.loadStructuredData<List<dynamic>>(
22+
'FontManifest.json',
23+
(string) async => json.decode(string),
24+
);
2425

25-
final fontsDir = Directory.fromUri(Uri(path: _getPath(from)));
26-
final Map<String, List<ByteData>> fontFamilies = {};
27-
await for (final entity in fontsDir.list()) {
28-
if (entity.path.endsWith('.ttf')) {
29-
final fontName =
30-
Uri.parse(entity.path).pathSegments.last.split('.ttf').first;
31-
final family = fontName.split('-').first;
32-
final Uint8List bytes = await File.fromUri(entity.uri).readAsBytes();
33-
final byteData = ByteData.view(bytes.buffer);
34-
fontFamilies[family] =
35-
[byteData].followedBy(fontFamilies[family] ?? []).toList();
36-
}
37-
}
38-
for (final family in fontFamilies.keys) {
39-
final loader = FontLoader(family);
40-
for (final font in fontFamilies[family]) {
41-
loader.addFont(Future.value(font));
26+
for (final Map<String, dynamic> font in fontManifest) {
27+
final fontLoader = FontLoader(_processedFontFamily(font['family']));
28+
for (final Map<String, dynamic> fontType in font['fonts']) {
29+
fontLoader.addFont(rootBundle.load(fontType['asset']));
4230
}
43-
await loader.load();
31+
await fontLoader.load();
4432
}
45-
46-
_hasLoaded = true;
4733
}
4834

49-
bool _hasLoaded = false;
50-
51-
String _getPath(String directory) {
52-
if (Directory.current.path.endsWith('test')) {
53-
return '../$directory';
54-
} else {
55-
return directory;
35+
String _processedFontFamily(String fontFamily) {
36+
/// There is no way to easily load the Roboto or Cupertino fonts.
37+
/// To make them available in tests, a package needs to include their own copies of them.
38+
///
39+
/// GoldenToolkit supplies Roboto because it is free to use.
40+
///
41+
/// However, when a downstream package includes a font, the font family will be prefixed with
42+
/// /packages/<package name>/<fontFamily> in order to disambiguate when multiple packages include
43+
/// fonts with the same name.
44+
///
45+
/// Ultimately, the font loader will load whatever we tell it, so if we see a font that looks like
46+
/// a Material or Cupertino font family, let's treat it as the main font family
47+
if (fontFamily.startsWith('packages/') &&
48+
_overridableFonts.any(fontFamily.contains)) {
49+
return fontFamily.split('/').last;
5650
}
51+
return fontFamily;
5752
}
53+
54+
const List<String> _overridableFonts = [
55+
'Roboto',
56+
'.SF UI Display',
57+
'.SF UI Text',
58+
];

packages/golden_toolkit/pubspec.lock

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ packages:
116116
url: "https://pub.dartlang.org"
117117
source: hosted
118118
version: "2.0.5"
119+
sample_dependency:
120+
dependency: "direct dev"
121+
description:
122+
path: "test/sample_dependency"
123+
relative: true
124+
source: path
125+
version: "0.0.1"
119126
sky_engine:
120127
dependency: transitive
121128
description: flutter

packages/golden_toolkit/pubspec.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: golden_toolkit
22
description: Common patterns for screenshot-based widget testing using Goldens.
3-
version: 0.1.0-dev
3+
version: 0.2.0-dev
44
homepage: https://github.com/eBay/flutter_glove_box/
55
repository: https://github.com/eBay/flutter_glove_box/tree/master/packages/golden_toolkit
66
issue_tracker: https://github.com/eBay/flutter_glove_box/issues
@@ -17,8 +17,15 @@ dependencies:
1717

1818
dev_dependencies:
1919
pedantic: ^1.8.0
20+
sample_dependency:
21+
path: test/sample_dependency
2022

2123
# needed so the images can be loaded in the example tests
2224
flutter:
25+
uses-material-design: true
2326
assets:
2427
- images/
28+
fonts:
29+
- family: Roboto
30+
fonts:
31+
- asset: packages/golden_toolkit/fonts/Roboto-Regular.ttf
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/// ***************************************************
2+
/// Copyright 2019-2020 eBay Inc.
3+
///
4+
/// Use of this source code is governed by a BSD-style
5+
/// license that can be found in the LICENSE file or at
6+
/// https://opensource.org/licenses/BSD-3-Clause
7+
/// ***************************************************
8+
///
9+
10+
import 'dart:async';
11+
12+
import 'package:golden_toolkit/golden_toolkit.dart';
13+
14+
Future<void> main(FutureOr<void> testMain()) async {
15+
await loadAppFonts();
16+
return testMain();
17+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/// ***************************************************
2+
/// Copyright 2019-2020 eBay Inc.
3+
///
4+
/// Use of this source code is governed by a BSD-style
5+
/// license that can be found in the LICENSE file or at
6+
/// https://opensource.org/licenses/BSD-3-Clause
7+
/// ***************************************************
8+
///
9+
10+
import 'dart:io';
11+
12+
import 'package:flutter/material.dart';
13+
import 'package:flutter_test/flutter_test.dart';
14+
import 'package:golden_toolkit/golden_toolkit.dart';
15+
16+
Future<void> main() async {
17+
group('Font loading', () {
18+
testGoldens('Roboto fonts should work', (tester) async {
19+
final golden = GoldenBuilder.column()
20+
..addScenario('Material Fonts should work',
21+
const Text('This is material text in "Roboto"'))
22+
..addScenario(
23+
'Material Icons should work', const Icon(Icons.phone_in_talk))
24+
..addScenario(
25+
'Fonts from packages should work',
26+
const Text('This is a custom font',
27+
style: TextStyle(
28+
fontFamily: 'OpenSans', package: 'sample_dependency')))
29+
..addScenario('Unknown fonts render in Ahem',
30+
const Text('unknown font', style: TextStyle(fontFamily: 'foo')));
31+
await tester.pumpWidgetBuilder(golden.build());
32+
await screenMatchesGolden(tester, 'material_fonts',
33+
skip: !Platform.isMacOS);
34+
});
35+
});
36+
}

packages/golden_toolkit/test/golden_builder_test.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,13 @@
88
99
import 'dart:io';
1010
import 'package:golden_toolkit/golden_toolkit.dart';
11-
import 'package:golden_toolkit/src/font_loader.dart';
1211
import 'package:flutter/material.dart';
1312
import 'package:flutter/widgets.dart';
1413
import 'package:flutter_test/flutter_test.dart';
1514

1615
import 'sample_weather_widget.dart';
1716

1817
Future<void> main() async {
19-
/// Note: In order to see fonts and icons on goldens,
20-
/// you need to preload all the fonts with this function in all test files
21-
await loadAppFonts(from: 'fonts');
22-
2318
group('Basic golden test for empty container', () {
2419
final squareContainer = Container(
2520
width: 100,

0 commit comments

Comments
 (0)