import 'dart:io'; import 'package:flutter/services.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. /// /// ── Load the family the app actually reads ── /// /// This registers `FontConstants.fontFamily`, so it must be fed that family's /// files. When the app moved off Poppins and back to Manrope, the loader still /// pointed at `assets/fonts/Poppins/*` — which does not fail, it registers the /// wrong glyphs under the right name and every reference shot becomes a /// picture of a typeface the app does not ship. Manrope is a single variable /// font, so one file covers the whole weight axis. Future loadGoldenFonts() async { await _loadIfPresent(FontConstants.fontFamily, const [ 'assets/fonts/Manrope/Manrope-VF.ttf', ]); 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', ]; }