Font namespacing in self-tests¶
Handled automatically since 1.1.1
loadAppFonts() now reads your package name from pubspec.yaml and also
registers each bundled text family under its packages/<pkg>/<family>
alias — so a package that references its own fonts with the prefix renders
correctly in its own test suite with no extra setup. Just call
loadAppFonts(). The rest of this page explains the underlying mismatch and
the manual workaround needed on older versions.
loadAppFonts() reads the family names Flutter wrote into FontManifest.json — but a package that references its own fonts with the packages/<name>/ prefix asks for a family name that the manifest does not contain when the package tests itself. This page explains why that mismatch happens, how to recognize it, and how to fix it.
The symptom¶
You call loadAppFonts() in flutter_test_config.dart, yet your goldens still render text as Ahem squares (or in the wrong typeface) — only inside the package's own test suite. The same widgets render correctly when the package is consumed by an app. Icons may also be missing.
The tell: text geometry is wrong even though loadAppFonts() ran without error and the font files are clearly bundled.
Why it happens¶
Flutter namespaces a package's fonts with a packages/<package_name>/ prefix only when that package is a dependency of another app. A package referencing its own bundled font must therefore hardcode the prefixed family name so it resolves in consumer apps:
// lib/src/tokens.dart — referenced by the package's widgets AND its public API
static const String fontDisplay = 'packages/ui_kit/BrandSans';
But when the package runs its own flutter test, it is the root project, not a dependency. The generated manifest lists the fonts with their bare family names and un-prefixed asset paths:
// build/unit_test_assets/FontManifest.json (package testing itself)
[
{ "family": "BrandSans",
"fonts": [ { "asset": "fonts/BrandSans-Medium.ttf", "weight": 500 } ] }
]
loadAppFonts() registers exactly what the manifest says — the family BrandSans. Its derivedFontFamily() helper only re-applies the packages/<pkg>/ prefix when the asset path starts with packages/.... Here the asset path is fonts/BrandSans-Medium.ttf, so no prefix is added.
The result is a name mismatch:
| Side | Family name |
|---|---|
loadAppFonts() registers |
BrandSans |
| Widget / theme requests | packages/ui_kit/BrandSans |
Flutter finds no match for the requested name and falls back to the font's fallback chain — Ahem (or Roboto) in a test — so glyphs and metrics are wrong.
This is specific to a package testing itself
Inside a consumer app's test suite the manifest asset paths are
packages/ui_kit/fonts/..., so derivedFontFamily() produces
packages/ui_kit/BrandSans and everything matches. The break only occurs
when the package that owns the prefixed font name is the root project
under test.
How to confirm it¶
-
Inspect the generated manifest and the family name your widgets request:
find build .dart_tool -name FontManifest.json -exec cat {} \; grep -rn "packages/<your_package>/" lib/If the manifest shows a bare family (
"family": "BrandSans") while your code requestspackages/<pkg>/BrandSans, that is the mismatch. -
The names won't line up: manifest family ≠ requested family.
The fix¶
On 1.1.1+ there is nothing to do — just call loadAppFonts(). It reads the
root package name from pubspec.yaml and registers every bundled text family
under both its bare name and its packages/<pkg>/<family> alias, so whichever
name your widgets request resolves. Icon families (MaterialIcons, etc.) are not
aliased; pass loadAppFonts(iconFonts: false) if you want the text fix without
also loading Material icons.
// test/flutter_test_config.dart — 1.1.1+
import 'dart:async';
import 'package:golden_matrix/golden_matrix.dart';
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
await loadAppFonts();
return testMain();
}
Manual workaround (older versions)¶
If you're pinned to an older golden_matrix, register the fonts under the
same prefixed names your widgets request by loading the files directly
instead of relying on the manifest-derived names, in flutter_test_config.dart:
// test/flutter_test_config.dart
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
/// Load the package's own fonts under their `packages/<pkg>/` family names —
/// the names the widgets/theme actually request — by reading the TTFs from
/// disk. `loadAppFonts()` cannot do this in a self-test because the manifest
/// lists the families un-prefixed.
Future<void> _loadOwnFonts() async {
const families = <String, List<String>>{
'packages/ui_kit/BrandSans': [
'fonts/BrandSans-Medium.ttf',
'fonts/BrandSans-SemiBold.ttf',
'fonts/BrandSans-Bold.ttf',
],
'packages/ui_kit/BrandSerif': ['fonts/BrandSerif-Regular.ttf'],
'packages/ui_kit/BrandMono': ['fonts/BrandMono-Regular.ttf'],
};
for (final entry in families.entries) {
final loader = FontLoader(entry.key);
for (final path in entry.value) {
final bytes = File(path).readAsBytesSync();
loader.addFont(Future.value(ByteData.view(bytes.buffer)));
}
await loader.load();
}
}
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
await _loadOwnFonts();
return testMain();
}
Keep loadAppFonts() for icons and Roboto
The custom loader only covers your prefixed text families. loadAppFonts()
additionally pulls MaterialIcons and Roboto from the Flutter SDK
cache, so Material icons render as real glyphs instead of empty boxes.
Call both — they register disjoint families and don't conflict:
Alternatives¶
- Drop the prefix in the package's own code. If your widgets referenced the
bare family (
'BrandSans'),loadAppFonts()would match in the self-test — but then the fonts would not resolve in consumer apps, where the prefix is required. Not recommended for a published UI package. - Move goldens into an
example/app that depends on the package. There the manifest is correctly prefixed andloadAppFonts()alone works — at the cost of testing through an extra app target.
Variable fonts with brackets in the filename¶
Handled automatically since 1.1.2
Geist[wght].ttf, Inter[opsz,wght].ttf, Roboto[wdth,wght].ttf — the
upstream naming convention for variable fonts — now load with no extra
setup. Up to 1.1.1 they were unloadable and took the whole test file
down with them.
The symptom on 1.1.1 and earlier¶
Every test file whose flutter_test_config.dart calls loadAppFonts() fails
at load time, before a single test runs — including files that never render
the font:
Failed to load ".../test/marketing/hero_golden_test.dart":
Unable to load asset: "packages/shadcn_ui/fonts/Geist%5Bwght%5D.ttf".
The asset does not exist or has empty data.
package:golden_matrix/src/flutter/font_loader.dart 90:37 loadAppFonts
test/marketing/flutter_test_config.dart 9:3 testExecutable
The blast radius is the whole file because loadAppFonts() is awaited in
testExecutable, and the message names your test file rather than the
offending dependency — which makes it read like a project bug.
Why it happens¶
[ and ] are URI gen-delims, so flutter_tools percent-encodes the key it
writes into FontManifest.json:
[{"family":"packages/shadcn_ui/DepFont",
"fonts":[{"asset":"packages/shadcn_ui/fonts/Geist%5Bwght%5D.ttf"}]}]
PlatformAssetBundle.load() builds its platform message with
Uri.encodeFull(key) — so that already-encoded key gets encoded again, the
lookup asks for Geist%255Bwght%255D.ttf, and nothing matches. The bug is the
double encoding, not the file: under flutter test the asset really is on disk
at build/unit_test_assets/packages/shadcn_ui/fonts/Geist%5Bwght%5D.ttf.
What 1.1.2 does¶
Retries the asset with the percent-decoded key
(packages/shadcn_ui/fonts/Geist[wght].ttf), whose single encoding by
load() lands exactly on the real filename. The raw key is always tried
first, so fonts that already worked take the same path as before.
If a font still cannot be loaded — for any reason — that family alone is skipped with a warning, and the rest of the suite runs:
golden_matrix: skipped font family "Geist" (packages/shadcn_ui/fonts/Geist[wght].ttf):
Unable to load asset: ... Goldens that use it fall back to another font.
On older versions
Pin the dependency to a version that ships static font weights, or load the
files yourself with the File(...).readAsBytes() +
FontLoader pattern above.
See also¶
- Advanced — the
## Font loadingsection andloadAppFonts()options - Migration guide
- CI integration
- Home