首页 > 解决方案 > 带有自定义图标字体类的黄金测试

问题描述

我有一个自定义的图标字体 .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 的默认图标,并uses-material-design: true在 中设置pubspec.yaml,这允许默认图标在黄金测试文件中正确呈现,但是无论我尝试什么,我都无法呈现我自己的图标。

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

有没有办法做到这一点?

标签: flutterflutter-test

解决方案


是的,这是可能的,但是您需要像 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);
}

导致: Golden_example


推荐阅读