【问题标题】:Golden tests with custom icon font class带有自定义图标字体类的黄金测试
【发布时间】:2021-03-19 07:27:40
【问题描述】:

我有一个自定义的图标字体 .ttf,我在我的应用程序中使用它作为 IconData,允许以与 Flutter 的内置材质图标相同的方式使用。

我的自定义字体类:

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
}

用法:

Icon(
  MyIcons.edit_outline,
  size: 24,
)

并且在应用程序中一切正常。但是现在我正在尝试生成黄金测试文件,以确保我的图标在更新 .ttf 后按预期工作,但图标总是只替换为测试 Ahem 字体方块。

如果我使用 Flutter 的默认图标,并在 pubspec.yaml 中设置 uses-material-design: true,这允许默认图标在黄金测试文件中正确呈现,但是无论我尝试什么,我都无法获得自己的图标渲染。

我尝试过但没有成功的其他事情:

有没有办法做到这一点?

【问题讨论】:

    标签: flutter flutter-test


    【解决方案1】:

    是的,这是可能的,但是您需要像 Flutter 测试中的任何其他自定义字体一样手动加载图标字体。

    我使用了来自 Flutter Gallery 演示应用的部分解决方案: https://github.com/flutter/gallery/blob/master/golden_test/testing/font_loader.dart

    对于材质图标,我使用了此处的解决方案:

    问题:https://github.com/flutter/flutter/issues/75391

    代码:https://github.com/flutter/flutter/pull/74131/files

    一些陷阱是:

    • 似乎字体加载必须在测试函数之外完成,所以先执行它然后运行测试。
    • 确保文件名和字体系列在所有地方都是正确的。

    鉴于有一个正确设置的自定义字体“Matter”和一个正确设置的自定义图标字体存在于项目根目录的“字体”目录中 -->

    演示代码:

    import 'dart:io';
    import 'dart:typed_data';
    
    import 'package:[your_path_to_icon_definition_file]/custom_icons.dart';
    import 'package:file/file.dart' as f;
    import 'package:file/local.dart' as l;
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    import 'package:flutter_test/flutter_test.dart';
    import 'package:path/path.dart' as path;
    import 'package:platform/platform.dart' as p;
    
    Future<void> main() async {
      await loadFonts();
      run();
    }
    
    void run() {
      group('mobile', () {
        testWidgets('Test font and icon loading', (tester) async {
          await tester.pumpWidget(MaterialApp(
            theme: ThemeData(
              fontFamily: 'Matter', // Define the custom font family.
            ),
            home: Scaffold(
              appBar: AppBar(
                title: Text('AppBar'),
              ),
              body: Column(
                children: [
                  Text('test'),
                  Icon(CustomIcons.camera), // Custom icon file from for example fluttericon.com
                  Icon(Icons.add), // material icons ('uses-material-design: true' Must exist in pubspec.yaml)
                ],
              ),
            ),
          ));
    
          expect(find.byType(MaterialApp), findsOneWidget);
    
          await expectLater(
            find.byType(MaterialApp),
            matchesGoldenFile('goldens/material_app.png'),
          );
        });
      });
    }
    
    /// Load fonts to make sure they show up in golden tests.
    Future<void> loadFonts() async {
      await _load(await loadFontsFromFontsDir());
      await _loadMaterialIconFont();
    }
    
    // Loads the cached material icon font.
    // Only necessary for golden tests. Relies on the tool updating cached assets before
    // running tests.
    Future<void> _loadMaterialIconFont() async {
      const f.FileSystem fs = l.LocalFileSystem();
      const p.Platform platform = p.LocalPlatform();
      final flutterRoot = fs.directory(platform.environment['FLUTTER_ROOT']);
    
      final iconFont = flutterRoot.childFile(
        fs.path.join(
          'bin',
          'cache',
          'artifacts',
          'material_fonts',
          'MaterialIcons-Regular.otf',
        ),
      );
    
      final bytes = Future<ByteData>.value(iconFont.readAsBytesSync().buffer.asByteData());
    
      await (FontLoader('MaterialIcons')..addFont(bytes)).load();
    }
    
    /// Assumes a fonts dir in root of project
    Map<String, List<Future<ByteData>>> loadFontsFromFontsDir() {
      final fontFamilyToData = <String, List<Future<ByteData>>>{};
      final currentDir = path.dirname(Platform.script.path);
      final fontsDirectory = path.join(
        currentDir,
        'fonts',
      );
      for (var file in Directory(fontsDirectory).listSync()) {
        if (file is File) {
          final fontFamily = path.basenameWithoutExtension(file.path).split('-').first;
          (fontFamilyToData[fontFamily] ??= []).add(file.readAsBytes().then((bytes) => ByteData.view(bytes.buffer)));
        }
      }
      return fontFamilyToData;
    }
    
    Future<void> _load(Map<String, List<Future<ByteData>>> fontFamilyToData) async {
      final waitList = <Future<void>>[];
      for (final entry in fontFamilyToData.entries) {
        print('entry.key=${entry.key}');
        final loader = FontLoader(entry.key);
        for (final data in entry.value) {
          loader.addFont(data);
        }
        waitList.add(loader.load());
      }
      await Future.wait(waitList);
    }
    
    

    导致:

    【讨论】:

    【解决方案2】:

    感谢a related question here.,我最终解决了这个问题

    有一种两全其美的方法,您可以拥有独立的字体包,而不必在使用它的应用中声明打包的字体文件。

    例如,我们有一个公司品牌/排版包,我们在多个应用程序中使用它,其中包含我们所有预配置的 TextStyle 声明,另一个独立包具有自定义生成的 IconData 存储在 @987654324 中@ 文件(如 FontAwesome)。

    包装方面:

    pubspec.yaml

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

    打包后的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
    
    }
    

    黄金测试

    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>/', ''),
        );
      }
    }
    

    或者,如果像我一样,您对自定义图标也有同样的问题,可以在您的自定义 IconData 的黄金测试中使用类似的扩展方法完成相同的操作,删除 fontPackage 值:

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

    您的应用端

    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,
            ),
          ),
        );
      }
    
    }
    

    现在不再需要在您的应用程序pubspec.yaml 中声明您的字体,甚至无需将样式包与您的实施应用程序放在同一个项目/存储库中。

    【讨论】:

      猜你喜欢
      • 2022-11-11
      • 1970-01-01
      • 1970-01-01
      • 2017-05-13
      • 1970-01-01
      • 1970-01-01
      • 2021-01-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多