Skip to content

Commit

Permalink
Merge pull request #20 from eBay/improve-font-loading
Browse files Browse the repository at this point in the history
Improve font loading
  • Loading branch information
coreysprague authored Feb 10, 2020
2 parents de27314 + 548d28e commit 3c674fa
Show file tree
Hide file tree
Showing 19 changed files with 248 additions and 57 deletions.
7 changes: 7 additions & 0 deletions packages/golden_toolkit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.2.0-dev

Improved the mechanism for loading font assets. Consumers no longer need to supply a directory to read the .ttf files from.

They can now simply call: `await loadAppFonts()` and the package will automatically load any font assets from their pubspec.yaml or
from any packages they depend on.

## 0.1.0-dev

Initial release. Includes utility methods for easily pumping complex widgets, loading real fonts, and for writing more advanced Golden-based tests.
Expand Down
41 changes: 32 additions & 9 deletions packages/golden_toolkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,37 @@ By default, flutter test only uses a single "test" font called Ahem.
This font is designed to show black spaces for every character and icon. This obviously makes goldens much less valuable.

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.
In order to inject your fonts, just call font loader function on top of your test file:
In order to inject your fonts, we have a helper method:

```dart
await loadAppFonts(from: 'yourFontDirectoryPath');
await loadAppFonts();
```

Function will load all the fonts from that directory using FontLoader so they are properly rendered during the test.
Material icons like `Icons.battery` will be rendered in goldens ONLY if your pre-load MaterialIcons-Regular.ttf font that contains all the icons.
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.

Material icons like `Icons.battery` will be rendered in goldens ONLY if your pubspec.yaml includes:

```yaml
flutter:
uses-material-design: true
```
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.
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:

```dart
import 'dart:async';
import 'package:golden_toolkit/golden_toolkit.dart';
Future<void> main(FutureOr<void> testMain()) async {
await loadAppFonts();
return testMain();
}
```

For more information on `flutter_test_config.dart`, see the [Flutter Docs](https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html)

### testGoldens()

Expand Down Expand Up @@ -221,11 +244,11 @@ This software contains some 3rd party software licensed under open source licens
Available at URL: [https://github.com/google/fonts/tree/master/apache/roboto](https://github.com/google/fonts/tree/master/apache/roboto)
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)

2. Material Icon File:
URL: [https://github.com/google/material-design-icons](https://github.com/google/material-design-icons)
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)

3. Icons at:
2. Icons at:
Author: Adnen Kadri
URL: [https://www.iconfinder.com/iconsets/weather-281](https://www.iconfinder.com/iconsets/weather-281)
License: Free for commercial use

3. OpenSans Font File:
Available at URL: [https://github.com/googlefonts/opensans](https://github.com/googlefonts/opensans)
License: Available under Apache license at [https://github.com/googlefonts/opensans/blob/master/LICENSE.txt](https://github.com/googlefonts/opensans/blob/master/LICENSE.txt)
Binary file not shown.
75 changes: 38 additions & 37 deletions packages/golden_toolkit/lib/src/font_loader.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,53 +5,54 @@
/// license that can be found in the LICENSE file or at
/// https://opensource.org/licenses/BSD-3-Clause
/// ***************************************************
import 'dart:io';
import 'dart:typed_data';
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart';
import 'package:flutter_test/flutter_test.dart';

///By default, flutter test only uses a single "test" font called Ahem.
///
///This font is designed to show black spaces for every character and icon. This obviously makes goldens much less valuable.
///
///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.
/// Path to your folder with fonts [from] in required
Future<void> loadAppFonts({@required String from}) async {
if (_hasLoaded) {
print('skipping fonts');
return;
}
///To make the goldens more useful, we will automatically load any fonts included in your pubspec.yaml as well as from
///packages you depend on.
Future<void> loadAppFonts() async {
TestWidgetsFlutterBinding.ensureInitialized();
final fontManifest = await rootBundle.loadStructuredData<List<dynamic>>(
'FontManifest.json',
(string) async => json.decode(string),
);

final fontsDir = Directory.fromUri(Uri(path: _getPath(from)));
final Map<String, List<ByteData>> fontFamilies = {};
await for (final entity in fontsDir.list()) {
if (entity.path.endsWith('.ttf')) {
final fontName =
Uri.parse(entity.path).pathSegments.last.split('.ttf').first;
final family = fontName.split('-').first;
final Uint8List bytes = await File.fromUri(entity.uri).readAsBytes();
final byteData = ByteData.view(bytes.buffer);
fontFamilies[family] =
[byteData].followedBy(fontFamilies[family] ?? []).toList();
}
}
for (final family in fontFamilies.keys) {
final loader = FontLoader(family);
for (final font in fontFamilies[family]) {
loader.addFont(Future.value(font));
for (final Map<String, dynamic> font in fontManifest) {
final fontLoader = FontLoader(_processedFontFamily(font['family']));
for (final Map<String, dynamic> fontType in font['fonts']) {
fontLoader.addFont(rootBundle.load(fontType['asset']));
}
await loader.load();
await fontLoader.load();
}

_hasLoaded = true;
}

bool _hasLoaded = false;

String _getPath(String directory) {
if (Directory.current.path.endsWith('test')) {
return '../$directory';
} else {
return directory;
String _processedFontFamily(String fontFamily) {
/// There is no way to easily load the Roboto or Cupertino fonts.
/// To make them available in tests, a package needs to include their own copies of them.
///
/// GoldenToolkit supplies Roboto because it is free to use.
///
/// However, when a downstream package includes a font, the font family will be prefixed with
/// /packages/<package name>/<fontFamily> in order to disambiguate when multiple packages include
/// fonts with the same name.
///
/// Ultimately, the font loader will load whatever we tell it, so if we see a font that looks like
/// a Material or Cupertino font family, let's treat it as the main font family
if (fontFamily.startsWith('packages/') &&
_overridableFonts.any(fontFamily.contains)) {
return fontFamily.split('/').last;
}
return fontFamily;
}

const List<String> _overridableFonts = [
'Roboto',
'.SF UI Display',
'.SF UI Text',
];
7 changes: 7 additions & 0 deletions packages/golden_toolkit/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
sample_dependency:
dependency: "direct dev"
description:
path: "test/sample_dependency"
relative: true
source: path
version: "0.0.1"
sky_engine:
dependency: transitive
description: flutter
Expand Down
9 changes: 8 additions & 1 deletion packages/golden_toolkit/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: golden_toolkit
description: Common patterns for screenshot-based widget testing using Goldens.
version: 0.1.0-dev
version: 0.2.0-dev
homepage: https://github.com/eBay/flutter_glove_box/
repository: https://github.com/eBay/flutter_glove_box/tree/master/packages/golden_toolkit
issue_tracker: https://github.com/eBay/flutter_glove_box/issues
Expand All @@ -17,8 +17,15 @@ dependencies:

dev_dependencies:
pedantic: ^1.8.0
sample_dependency:
path: test/sample_dependency

# needed so the images can be loaded in the example tests
flutter:
uses-material-design: true
assets:
- images/
fonts:
- family: Roboto
fonts:
- asset: packages/golden_toolkit/fonts/Roboto-Regular.ttf
17 changes: 17 additions & 0 deletions packages/golden_toolkit/test/flutter_test_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// ***************************************************
/// Copyright 2019-2020 eBay Inc.
///
/// Use of this source code is governed by a BSD-style
/// license that can be found in the LICENSE file or at
/// https://opensource.org/licenses/BSD-3-Clause
/// ***************************************************
///
import 'dart:async';

import 'package:golden_toolkit/golden_toolkit.dart';

Future<void> main(FutureOr<void> testMain()) async {
await loadAppFonts();
return testMain();
}
36 changes: 36 additions & 0 deletions packages/golden_toolkit/test/font_loading_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/// ***************************************************
/// Copyright 2019-2020 eBay Inc.
///
/// Use of this source code is governed by a BSD-style
/// license that can be found in the LICENSE file or at
/// https://opensource.org/licenses/BSD-3-Clause
/// ***************************************************
///
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';

Future<void> main() async {
group('Font loading', () {
testGoldens('Roboto fonts should work', (tester) async {
final golden = GoldenBuilder.column()
..addScenario('Material Fonts should work',
const Text('This is material text in "Roboto"'))
..addScenario(
'Material Icons should work', const Icon(Icons.phone_in_talk))
..addScenario(
'Fonts from packages should work',
const Text('This is a custom font',
style: TextStyle(
fontFamily: 'OpenSans', package: 'sample_dependency')))
..addScenario('Unknown fonts render in Ahem',
const Text('unknown font', style: TextStyle(fontFamily: 'foo')));
await tester.pumpWidgetBuilder(golden.build());
await screenMatchesGolden(tester, 'material_fonts',
skip: !Platform.isMacOS);
});
});
}
5 changes: 0 additions & 5 deletions packages/golden_toolkit/test/golden_builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,13 @@
import 'dart:io';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:golden_toolkit/src/font_loader.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

import 'sample_weather_widget.dart';

Future<void> main() async {
/// Note: In order to see fonts and icons on goldens,
/// you need to preload all the fonts with this function in all test files
await loadAppFonts(from: 'fonts');

group('Basic golden test for empty container', () {
final squareContainer = Container(
width: 100,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 0 additions & 5 deletions packages/golden_toolkit/test/multi_screen_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,11 @@
import 'dart:io';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:golden_toolkit/src/font_loader.dart';
import 'package:flutter_test/flutter_test.dart';

import 'sample_weather_widget.dart';

Future<void> main() async {
/// Note: In order to see fonts and icons on goldens,
/// you need to preload all the fonts with this function in all test files
await loadAppFonts(from: 'fonts');

group('Multi Screen Golden examples', () {
testGoldens('Example of testing a responsive layout', (tester) async {
await tester.pumpWidgetBuilder(WeatherForecast());
Expand Down
10 changes: 10 additions & 0 deletions packages/golden_toolkit/test/sample_dependency/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.

version:
revision: 27321ebbad34b0a3fafe99fac037102196d655ff
channel: unknown

project_type: package
14 changes: 14 additions & 0 deletions packages/golden_toolkit/test/sample_dependency/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# sample_dependency

A new Flutter package project.

## Getting Started

This project is a starting point for a Dart
[package](https://flutter.dev/developing-packages/),
a library module containing code that can be shared easily across
multiple Flutter or Dart projects.

For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
library sample_dependency;
43 changes: 43 additions & 0 deletions packages/golden_toolkit/test/sample_dependency/pubspec.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.11"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.8"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
sdks:
dart: ">=2.2.2 <3.0.0"
16 changes: 16 additions & 0 deletions packages/golden_toolkit/test/sample_dependency/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: sample_dependency
description: A sample dependency that includes fonts to validate golden_toolkit's loadAppFonts() helper
version: 0.0.1
author:

environment:
sdk: ">=2.1.0 <3.0.0"

dependencies:
flutter:
sdk: flutter
flutter:
fonts:
- family: OpenSans
fonts:
- asset: packages/sample_dependency/fonts/OpenSans-Regular.ttf
Loading

0 comments on commit 3c674fa

Please sign in to comment.