Search code examples
flutterflutter-test

Golden tests with custom icon font class


I have a custom made icon font .ttf which I use within my apps as IconData, allowing for use the same way you would with Flutter's built-in material icons.

My custom font class:

class MyIcons {
    MyIcons._();

    static const iconFontFamily = 'MyIcons';
    static const iconFontPackage = 'my_icon_package';

    /// edit_outline
    static const IconData edit_outline = IconData(0xe000, fontFamily: iconFontFamily, fontPackage: iconFontPackage);


  // etc
}

Usage:

Icon(
  MyIcons.edit_outline,
  size: 24,
)

And all works well within the app. However now I am trying to generate golden test files to ensure my icons work as expected after I've updated the .ttf, but the icons are always only replaced with the test Ahem font squares.

If I use Flutter's default icons, and set uses-material-design: true in the pubspec.yaml, this allows the default icons to be rendered properly within the golden test files, however no matter what I try I cannot get my own icons to be rendered.

Other things I've tried and been unsuccessful with:

Is there a way to do this?


Solution

  • I ended up solving the gotchas with this thanks to a related question here.

    There is a way to have the best of both worlds where you can have your standalone font package and not have to declare your packaged font files in your app that is using it.

    For example, we have a company branding/typography package which we use across multiple apps that contains all our pre-configured TextStyle declarations, and another standalone package which has custom generated IconData that is stored within a *.ttf file (like FontAwesome).

    The package side:

    pubspec.yaml

    
    flutter:
      uses-material-design: true
      assets:
        - assets/fonts/
      fonts:
        - family: MyFont
          fonts:
            - asset: assets/fonts/MyFont.ttf
              weight: 400
    
        # etc
    
    

    The packaged TextStyle:

    class BrandStyles {
      static const _packageName = '<package_name>';
    
      static const headline1Style = TextStyle(
        color: Colors.black,
        fontFamily: 'MyFont',
        fontSize: 60.0,
        fontStyle: FontStyle.normal,
        fontWeight: FontWeight.w400,
        height: 1.16,
        letterSpacing: 0,
        package: _packageName,
      );
    
    
      // etc
    
    }
    

    Golden test

    void main() {
      final widget = MaterialApp(
        theme: ThemeData(
          textTheme: TextTheme(
            // use custom extension method to remove `package` value
            headline1: BrandStyles.headline1Style.trimFontPackage(),
          ),
        ),
        home: Scaffold(
          body: SafeArea(child: StylesExample()),
        ),
      );
    
      setUp(() async {
        TestWidgetsFlutterBinding.ensureInitialized();
        final file = File('path/to/packaged/asset/MyFont.ttf').readAsBytesSync();
        final bytes = Future<ByteData>.value(file.buffer.asByteData());
    
        await (FontLoader('MyFont')..addFont(bytes)).load();
      });
    
      testWidgets('Golden typography test', (WidgetTester tester) async {
        await tester.pumpWidget(widget);
        await expectLater(
            find.byType(MaterialApp), matchesGoldenFile('goldens/typography.png'));
      });
    }
    
    extension StylingExtensions on TextStyle {
      
      TextStyle trimFontPackage() {
        return TextStyle(
          inherit: inherit,
          color: color,
          backgroundColor: backgroundColor,
          fontSize: fontSize,
          fontWeight: fontWeight,
          fontStyle: fontStyle,
          letterSpacing: letterSpacing,
          wordSpacing: wordSpacing,
          textBaseline: textBaseline,
          height: height,
          locale: locale,
          foreground: foreground,
          background: background,
          shadows: shadows,
          fontFeatures: fontFeatures,
          decoration: decoration,
          decorationColor: decorationColor,
          decorationStyle: decorationStyle,
          decorationThickness: decorationThickness,
          debugLabel: debugLabel,
          /// `replaceAll` only required if loading multiple fonts, 
          /// otherwise set value to your single `fontFamily` name
          fontFamily: fontFamily.replaceAll('packages/<package_name>/', ''),
        );
      }
    }
    

    Or if like me, you have the same issue with custom icons, the same can be done within your golden test for your custom IconData with a similar extension method, removing the fontPackage value:

    extension IconExtensions on IconData {
      IconData convertToGolden() => IconData(
            this.codePoint,
            fontFamily: this.fontFamily,
          );
    }
    
    

    Your app side

    pubspec.yaml

    
    # ...
    
    dependencies:
      flutter:
        sdk: flutter
    
      <package_name>:
        git:
          url: <url_to_hosted_package>.git
          ref: <release_tag>
    
    

    main.dart

    
    class MyApp extends StatelessWidget {
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData.light().copyWith(
            textTheme: TextTheme(
              headline1: BrandStyles.headline1Style,
            ),
          ),
        );
      }
    
    }
    

    There is now no longer a need to declare your fonts within your apps pubspec.yaml, or even have the style package(s) within the same project/repository as your implementing app.