|
| 1 | +--- |
| 2 | +title: Load Fonts and Icons |
| 3 | +navOrder: 32 |
| 4 | +--- |
| 5 | +By default, in golden tests, Flutter replaces all fonts with a big blocky font called |
| 6 | +Ahem. Flutter also chooses not to load the standard Material and Cupertino icon sets. |
| 7 | + |
| 8 | +## Load Fonts |
| 9 | +The easiest way to load your real app fonts in golden tests is to use the `loadAppFonts()` |
| 10 | +function in `flutter_test_goldens`. This method is a port from the discontinued `golden_toolkit` |
| 11 | +package. |
| 12 | + |
| 13 | +```dart |
| 14 | +await loadAppFonts(); |
| 15 | +``` |
| 16 | + |
| 17 | +The `loadAppFonts()` method can be called from anywhere. However, it's important to know |
| 18 | +that once you load app fonts, they remain loaded for the remainder of the test execution. |
| 19 | +Therefore, app fonts are typically loaded for all tests. This can be done from a special |
| 20 | +file that Flutter automatically looks for, called `flutter_test_config.dart`. Place that |
| 21 | +file at the root of your test directory. |
| 22 | + |
| 23 | +```dart |
| 24 | +import 'package:flutter_test_goldens/flutter_test_goldens.dart'; |
| 25 | +
|
| 26 | +Future<void> testExecutable(FutureOr<void> Function() testMain) async { |
| 27 | + await loadAppfonts(); |
| 28 | +
|
| 29 | + return testMain(); |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +### How Font Loading Works |
| 34 | +The `loadAppFonts()` isn't magic. It may be helpful to understand what it's doing |
| 35 | +internally. |
| 36 | + |
| 37 | +At a high level loading app fonts requires the following steps: |
| 38 | + * Load the font manifest, which lists all the app's fonts |
| 39 | + * Iterate through each font |
| 40 | + * Determine the appropriate name to load the font |
| 41 | + * Load the font into Flutter's `FontLoader` |
| 42 | + |
| 43 | +The following code replicates the implementation of `loadAppFonts`, which was |
| 44 | +originally shipped with `golden_toolkit`. Educational comments have been added. |
| 45 | + |
| 46 | +```dart |
| 47 | +Future<void> loadAppFonts() async { |
| 48 | + TestWidgetsFlutterBinding.ensureInitialized(); |
| 49 | + |
| 50 | + // Flutter stores the font manifest in a JSON file within the root app |
| 51 | + // asset bundle. Load it and parse it. |
| 52 | + final fontManifest = await rootBundle.loadStructuredData<Iterable<dynamic>>( |
| 53 | + 'FontManifest.json', |
| 54 | + (string) async => json.decode(string), |
| 55 | + ); |
| 56 | +
|
| 57 | + // Iterate through each font data entry in the JSON and try to load the font. |
| 58 | + for (final Map<String, dynamic> font in fontManifest) { |
| 59 | + // Create a `FontLoader` for this font family (which may include multiple fonts, |
| 60 | + // e.g., normal, bold, italic). |
| 61 | + // |
| 62 | + // Special steps need to be taken to load default fonts like Roboto and SF Pro. |
| 63 | + // Those steps are taken by `derivedFontFamily()`. |
| 64 | + final fontLoader = FontLoader(derivedFontFamily(font)); |
| 65 | + |
| 66 | + for (final Map<String, dynamic> fontType in font['fonts']) { |
| 67 | + // Add each font file for this font family, e.g., normal, bold, italic. |
| 68 | + fontLoader.addFont(rootBundle.load(fontType['asset'])); |
| 69 | + } |
| 70 | + |
| 71 | + // Load the font. |
| 72 | + await fontLoader.load(); |
| 73 | + } |
| 74 | +} |
| 75 | +
|
| 76 | +/// There is no way to easily load the Roboto or Cupertino fonts. |
| 77 | +/// To make them available in tests, a package needs to include their own copies of them. |
| 78 | +/// |
| 79 | +/// GoldenToolkit supplies Roboto because it is free to use. |
| 80 | +/// |
| 81 | +/// However, when a downstream package includes a font, the font family will be prefixed with |
| 82 | +/// `/packages/<package name>/<fontFamily>` in order to disambiguate when multiple packages include |
| 83 | +/// fonts with the same name. |
| 84 | +/// |
| 85 | +/// Ultimately, the font loader will load whatever we tell it, so if we see a font that looks like |
| 86 | +/// a Material or Cupertino font family, let's treat it as the main font family |
| 87 | +String derivedFontFamily(Map<String, dynamic> fontDefinition) { |
| 88 | + if (!fontDefinition.containsKey('family')) { |
| 89 | + return ''; |
| 90 | + } |
| 91 | +
|
| 92 | + final String fontFamily = fontDefinition['family']; |
| 93 | +
|
| 94 | + if (_overridableFonts.contains(fontFamily)) { |
| 95 | + return fontFamily; |
| 96 | + } |
| 97 | +
|
| 98 | + if (fontFamily.startsWith('packages/')) { |
| 99 | + final fontFamilyName = fontFamily.split('/').last; |
| 100 | + if (_overridableFonts.any((font) => font == fontFamilyName)) { |
| 101 | + return fontFamilyName; |
| 102 | + } |
| 103 | + } else { |
| 104 | + for (final Map<String, dynamic> fontType in fontDefinition['fonts']) { |
| 105 | + final String? asset = fontType['asset']; |
| 106 | + if (asset != null && asset.startsWith('packages')) { |
| 107 | + final packageName = asset.split('/')[1]; |
| 108 | + return 'packages/$packageName/$fontFamily'; |
| 109 | + } |
| 110 | + } |
| 111 | + } |
| 112 | + return fontFamily; |
| 113 | +} |
| 114 | +
|
| 115 | +const List<String> _overridableFonts = [ |
| 116 | + 'Roboto', |
| 117 | + '.SF UI Display', |
| 118 | + '.SF UI Text', |
| 119 | + '.SF Pro Text', |
| 120 | + '.SF Pro Display', |
| 121 | +]; |
| 122 | +``` |
| 123 | + |
| 124 | +## Load Material Icons |
| 125 | +Material icons aren't available in golden tests because they come from a font, and |
| 126 | +Flutter doesn't load any fonts in golden tests. |
| 127 | + |
| 128 | +The easiest way to load Material icons is to use the `loadMaterialIconsFont()` function |
| 129 | +in `flutter_test_goldens`. Once loaded, the icons appear in all subsequent tests, |
| 130 | +therefore, most developers choose to load icons for all tests. To do this, you |
| 131 | +can load icons in a `flutter_test_config.dart` file at the root of your test suite. |
| 132 | + |
| 133 | +```dart |
| 134 | +import 'package:flutter_test_goldens/flutter_test_goldens.dart'; |
| 135 | +
|
| 136 | +Future<void> testExecutable(FutureOr<void> Function() testMain) async { |
| 137 | + await loadMaterialIconsFont(); |
| 138 | +
|
| 139 | + return testMain(); |
| 140 | +} |
| 141 | +``` |
| 142 | + |
| 143 | +### How Material Icon Loading Works |
| 144 | +It may be helpful to know how Material icons are loaded by `loadMaterialIconsFont()`. |
| 145 | + |
| 146 | +The following code is a copy of `loadMaterialIconsFont()` for reference. |
| 147 | + |
| 148 | +```dart |
| 149 | +Future<void> loadMaterialIconsFont() async { |
| 150 | + const FileSystem fs = LocalFileSystem(); |
| 151 | + const Platform platform = LocalPlatform(); |
| 152 | + final Directory flutterRoot = fs.directory(platform.environment['FLUTTER_ROOT']); |
| 153 | +
|
| 154 | + // The following path locates the Material icons within Flutter's cache. |
| 155 | + // This is a magical path that's not publicly exposed by Flutter, but this |
| 156 | + // is the best that the Flutter team has given us. |
| 157 | + final File iconFont = flutterRoot.childFile( |
| 158 | + fs.path.join( |
| 159 | + 'bin', |
| 160 | + 'cache', |
| 161 | + 'artifacts', |
| 162 | + 'material_fonts', |
| 163 | + 'MaterialIcons-Regular.otf', |
| 164 | + ), |
| 165 | + ); |
| 166 | +
|
| 167 | + // Load the Material icon font. |
| 168 | + final Future<ByteData> bytes = Future<ByteData>.value(iconFont.readAsBytesSync().buffer.asByteData()); |
| 169 | +
|
| 170 | + // Load the Material icon font into Flutter's `FontLoader`. |
| 171 | + await (FontLoader('MaterialIcons')..addFont(bytes)).load(); |
| 172 | +} |
| 173 | +
|
| 174 | +extension on Directory { |
| 175 | + File childFile(String basename) => File("$path$separator$basename"); |
| 176 | +} |
| 177 | +``` |
0 commit comments