import 'dart:io'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:miler/views/helpers/constants/Font_constant.dart'; /// Fonts a reference shot needs, loaded once per suite. /// /// ── Why every shot harness must call this ── /// /// A golden rendered without a font is a picture of the fallback, and the /// fallback for an icon font is an empty box. That is not a loud failure — the /// test passes, the image is written, and a whole page of missing glyphs /// becomes the reference every future run is compared against. /// /// It has happened twice. Poppins ships as separate weight files and had to be /// registered under one family before `w800` headings stopped rendering as /// smeared SemiBold; then Lucide arrived as a package-declared font the test /// binding does not resolve at all, and every icon on Activity came out as a /// hollow square. One loader, so the next family added to the app is added /// here rather than in five harnesses that will drift apart. Future loadGoldenFonts() async { final poppins = FontLoader(FontConstants.fontFamily); for (final e in const <(String, FontWeight)>[ ('Light', FontWeight.w300), ('Regular', FontWeight.w400), ('Medium', FontWeight.w500), ('SemiBold', FontWeight.w600), ('Bold', FontWeight.w700), ('ExtraBold', FontWeight.w800), ('Black', FontWeight.w900), ]) { final f = File('assets/fonts/Poppins/Poppins-${e.$1}.ttf'); if (f.existsSync()) { poppins.addFont(f.readAsBytes().then((b) => ByteData.view(b.buffer))); } } await poppins.load(); await _loadIfPresent('MaterialIcons', const [ '/opt/homebrew/share/flutter/bin/cache/artifacts/material_fonts/' 'MaterialIcons-Regular.otf', ]); // ── The family name is not `Lucide` ── // // Its `IconData` carries `fontPackage: 'lucide_icons_flutter'`, and Flutter // resolves a packaged font under `packages//`. Registering // it as plain `Lucide` loads a font nothing ever asks for, which looks // exactly like not loading it at all: every glyph renders as a hollow box // and the golden is written anyway. // // Both names are registered — the prefixed one is what the widgets resolve, // and the bare one costs nothing and covers any direct use. final lucide = _lucideCandidates(); await _loadIfPresent('packages/lucide_icons_flutter/Lucide', lucide); await _loadIfPresent('Lucide', lucide, quiet: true); } Future _loadIfPresent( String family, List paths, { bool quiet = false, }) async { for (final p in paths) { final f = File(p); if (!f.existsSync()) continue; final loader = FontLoader(family) ..addFont(f.readAsBytes().then((b) => ByteData.view(b.buffer))); await loader.load(); return; } // Absent is reported, never silent: the alternative is a reference image // full of empty boxes that nothing ever flags. if (quiet) return; // ignore: avoid_print print('[GOLDEN] $family not found — its glyphs will render as boxes'); } List _lucideCandidates() { final home = Platform.environment['HOME'] ?? ''; final root = Directory('$home/.pub-cache/hosted/pub.dev'); if (!root.existsSync()) return const []; return [ for (final d in root.listSync().whereType()) if (d.path .split(Platform.pathSeparator) .last .startsWith('lucide_icons_flutter-')) '${d.path}/assets/lucide.ttf', ]; }