diff --git a/packages/golden_toolkit/CHANGELOG.md b/packages/golden_toolkit/CHANGELOG.md index 2adbad6..a71d6e0 100644 --- a/packages/golden_toolkit/CHANGELOG.md +++ b/packages/golden_toolkit/CHANGELOG.md @@ -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. diff --git a/packages/golden_toolkit/README.md b/packages/golden_toolkit/README.md index 72fa555..5975ec5 100644 --- a/packages/golden_toolkit/README.md +++ b/packages/golden_toolkit/README.md @@ -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 main(FutureOr 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() @@ -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) diff --git a/packages/golden_toolkit/fonts/MaterialIcons-Regular.ttf b/packages/golden_toolkit/fonts/MaterialIcons-Regular.ttf deleted file mode 100644 index 9519e1d..0000000 Binary files a/packages/golden_toolkit/fonts/MaterialIcons-Regular.ttf and /dev/null differ diff --git a/packages/golden_toolkit/fonts/Roboto-Regular.ttf b/packages/golden_toolkit/lib/fonts/Roboto-Regular.ttf similarity index 100% rename from packages/golden_toolkit/fonts/Roboto-Regular.ttf rename to packages/golden_toolkit/lib/fonts/Roboto-Regular.ttf diff --git a/packages/golden_toolkit/lib/src/font_loader.dart b/packages/golden_toolkit/lib/src/font_loader.dart index 5aff468..0f745a2 100644 --- a/packages/golden_toolkit/lib/src/font_loader.dart +++ b/packages/golden_toolkit/lib/src/font_loader.dart @@ -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 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 loadAppFonts() async { + TestWidgetsFlutterBinding.ensureInitialized(); + final fontManifest = await rootBundle.loadStructuredData>( + 'FontManifest.json', + (string) async => json.decode(string), + ); - final fontsDir = Directory.fromUri(Uri(path: _getPath(from))); - final Map> 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 font in fontManifest) { + final fontLoader = FontLoader(_processedFontFamily(font['family'])); + for (final Map 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// 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 _overridableFonts = [ + 'Roboto', + '.SF UI Display', + '.SF UI Text', +]; diff --git a/packages/golden_toolkit/pubspec.lock b/packages/golden_toolkit/pubspec.lock index 3431ba1..7eba2e0 100644 --- a/packages/golden_toolkit/pubspec.lock +++ b/packages/golden_toolkit/pubspec.lock @@ -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 diff --git a/packages/golden_toolkit/pubspec.yaml b/packages/golden_toolkit/pubspec.yaml index 095b51e..77bac42 100644 --- a/packages/golden_toolkit/pubspec.yaml +++ b/packages/golden_toolkit/pubspec.yaml @@ -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 @@ -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 diff --git a/packages/golden_toolkit/test/flutter_test_config.dart b/packages/golden_toolkit/test/flutter_test_config.dart new file mode 100644 index 0000000..4f6ea04 --- /dev/null +++ b/packages/golden_toolkit/test/flutter_test_config.dart @@ -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 main(FutureOr testMain()) async { + await loadAppFonts(); + return testMain(); +} diff --git a/packages/golden_toolkit/test/font_loading_test.dart b/packages/golden_toolkit/test/font_loading_test.dart new file mode 100644 index 0000000..6e031bb --- /dev/null +++ b/packages/golden_toolkit/test/font_loading_test.dart @@ -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 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); + }); + }); +} diff --git a/packages/golden_toolkit/test/golden_builder_test.dart b/packages/golden_toolkit/test/golden_builder_test.dart index cf28e6d..b8290f7 100644 --- a/packages/golden_toolkit/test/golden_builder_test.dart +++ b/packages/golden_toolkit/test/golden_builder_test.dart @@ -8,7 +8,6 @@ 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'; @@ -16,10 +15,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'sample_weather_widget.dart'; Future 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, diff --git a/packages/golden_toolkit/test/goldens/material_fonts.png b/packages/golden_toolkit/test/goldens/material_fonts.png new file mode 100644 index 0000000..c9b659a Binary files /dev/null and b/packages/golden_toolkit/test/goldens/material_fonts.png differ diff --git a/packages/golden_toolkit/test/multi_screen_golden_test.dart b/packages/golden_toolkit/test/multi_screen_golden_test.dart index f5d60c0..44ed3bb 100644 --- a/packages/golden_toolkit/test/multi_screen_golden_test.dart +++ b/packages/golden_toolkit/test/multi_screen_golden_test.dart @@ -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 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()); diff --git a/packages/golden_toolkit/test/sample_dependency/.metadata b/packages/golden_toolkit/test/sample_dependency/.metadata new file mode 100644 index 0000000..2309932 --- /dev/null +++ b/packages/golden_toolkit/test/sample_dependency/.metadata @@ -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 diff --git a/packages/golden_toolkit/test/sample_dependency/README.md b/packages/golden_toolkit/test/sample_dependency/README.md new file mode 100644 index 0000000..00b28ba --- /dev/null +++ b/packages/golden_toolkit/test/sample_dependency/README.md @@ -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. diff --git a/packages/golden_toolkit/test/sample_dependency/lib/fonts/OpenSans-Regular.ttf b/packages/golden_toolkit/test/sample_dependency/lib/fonts/OpenSans-Regular.ttf new file mode 100755 index 0000000..29bfd35 Binary files /dev/null and b/packages/golden_toolkit/test/sample_dependency/lib/fonts/OpenSans-Regular.ttf differ diff --git a/packages/golden_toolkit/test/sample_dependency/lib/sample_dependency.dart b/packages/golden_toolkit/test/sample_dependency/lib/sample_dependency.dart new file mode 100644 index 0000000..8484418 --- /dev/null +++ b/packages/golden_toolkit/test/sample_dependency/lib/sample_dependency.dart @@ -0,0 +1 @@ +library sample_dependency; diff --git a/packages/golden_toolkit/test/sample_dependency/pubspec.lock b/packages/golden_toolkit/test/sample_dependency/pubspec.lock new file mode 100644 index 0000000..7c07229 --- /dev/null +++ b/packages/golden_toolkit/test/sample_dependency/pubspec.lock @@ -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" diff --git a/packages/golden_toolkit/test/sample_dependency/pubspec.yaml b/packages/golden_toolkit/test/sample_dependency/pubspec.yaml new file mode 100644 index 0000000..87548b2 --- /dev/null +++ b/packages/golden_toolkit/test/sample_dependency/pubspec.yaml @@ -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 diff --git a/packages/golden_toolkit/test/sample_dependency/sample_dependency.iml b/packages/golden_toolkit/test/sample_dependency/sample_dependency.iml new file mode 100644 index 0000000..8d48a06 --- /dev/null +++ b/packages/golden_toolkit/test/sample_dependency/sample_dependency.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file