From 7ff86738e0eb53b8136ea33cd9e35e4c42be6ecf Mon Sep 17 00:00:00 2001 From: Pascal Welsch Date: Tue, 19 Nov 2024 22:59:36 +0100 Subject: [PATCH] Test loading of invalid fonts --- lib/src/screenshot/load_fonts.dart | 81 ++++++++++++------- test/fonts/default_font_test.dart | 3 +- test/fonts/load_font_test.dart | 32 ++++++++ test/fonts/templates/app_font/test/test.dart | 2 - .../templates/default_font/test/test.dart | 1 - .../templates/dependency_font/test/test.dart | 1 - 6 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 test/fonts/load_font_test.dart diff --git a/lib/src/screenshot/load_fonts.dart b/lib/src/screenshot/load_fonts.dart index 02304a83..8216308f 100644 --- a/lib/src/screenshot/load_fonts.dart +++ b/lib/src/screenshot/load_fonts.dart @@ -6,18 +6,39 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -/// Loads all font from the FontManifest (Asset fonts defined in pubspec.yaml and -/// other Flutter package dependencies) and the following fonts from Flutter SDK: +/// Loads all font from the apps FontManifest and embedded in the Flutter SDK +/// +/// ## App Fonts (FontManifest) +/// - All fonts defined in the pubspec.yaml +/// - All fonts of dependencies that define fonts in their pubspec.yaml +/// +/// ## Embedded Flutter SDK Fonts /// - Roboto /// - RobotoCondensed /// - MaterialIcons /// -/// By default, widget tests run with [TargetPlatform.android] which -/// uses the Roboto font by default (see [Typography]). Loading this font, -/// although not defined as asset, allows most Flutter apps to render text. +/// ## Why load Roboto by default? +/// +/// Widget test run with [TargetPlatform.android] by default. [MaterialApp] sets +/// the Roboto fontFamily as default for [TargetPlatform.android] (see +/// [Typography]). Loading the Roboto fontFamily therefore allows showing text +/// in the default scenario of a Flutter app. +/// Fortunately, Robot is part of the Flutter SDK and can be loaded right away. +/// +/// ## Custom fonts +/// +/// Apps that use custom fonts, should declare them in the pubspec.yaml file (https://docs.flutter.dev/cookbook/design/fonts#declare-the-font-in-the-pubspec-yaml-file). +/// Those fonts are automatically added to the FontManifest.json file during build. +/// +/// The [loadAppFonts] function loads all font defined in the FontManifest.json file. /// -/// If your app depends on other system fonts (like "Segoe UI" on [TargetPlatform.windows] -/// or "Apple Color Emoji" on [TargetPlatform.iOS]) use [loadFont]. +/// ## Depending on System fonts +/// +/// Sometimes you don't want to ship a font with your app, but use a system +/// font e.g. "Segoe UI" on [TargetPlatform.windows] or "Apple Color Emoji" +/// on [TargetPlatform.iOS]. +/// Those fonts are not loaded by [loadAppFonts], load them individually with +/// [loadFont]. Future loadAppFonts() async { TestWidgetsFlutterBinding.ensureInitialized(); @@ -55,15 +76,21 @@ Future loadAppFonts() async { /// ``` /// /// Flutter support the following formats: .ttf, .otf, .ttc +/// +/// Calling [loadFont] multiple times with the same family will overwrite the previous +/// +/// The [family] is optional: '' will extract the family name from the font file. Future loadFont(String family, List fontPaths) async { + if (fontPaths.isEmpty) { + return; + } final fontLoader = FontLoader(family); for (final path in fontPaths) { final Uint8List bytes = File(path).readAsBytesSync(); - - /// TODO catch error when path loads to an unsupported font format (docx); - /// TODO print warning fontLoader.addFont(Future.sync(() => bytes.buffer.asByteData())); } + // the fontLoader is unusable after calling load(). + // No need to cache or return it. await fontLoader.load(); } @@ -83,23 +110,23 @@ Future _loadMaterialFontsFromSdk() async { (font) => fontFormats.any((element) => font.path.endsWith(element)), ); - final robotoFonts = - existingFonts.where((font) => font.path.contains('Roboto-')); - await loadFont('Roboto', [ - for (final font in robotoFonts) font.path, - ]); - - final robotoCondensedFonts = - existingFonts.where((font) => font.path.contains('RobotoCondensed-')); - await loadFont('RobotoCondensed', [ - for (final font in robotoCondensedFonts) font.path, - ]); - - final materialIcons = - existingFonts.where((font) => font.path.contains('MaterialIcons-')); - await loadFont('MaterialIcons', [ - for (final font in materialIcons) font.path, - ]); + final robotoFonts = existingFonts + .map((font) => font.path) + .where((path) => path.contains('Roboto-')) + .toList(); + await loadFont('Roboto', robotoFonts); + + final robotoCondensedFonts = existingFonts + .map((font) => font.path) + .where((path) => path.contains('RobotoCondensed-')) + .toList(); + await loadFont('RobotoCondensed', robotoCondensedFonts); + + final materialIcons = existingFonts + .map((font) => font.path) + .where((path) => path.contains('MaterialIcons-')) + .toList(); + await loadFont('MaterialIcons', materialIcons); } /// Returns the Flutter SDK root directory based on the current flutter diff --git a/test/fonts/default_font_test.dart b/test/fonts/default_font_test.dart index 1476e908..4ec9c69e 100644 --- a/test/fonts/default_font_test.dart +++ b/test/fonts/default_font_test.dart @@ -1,11 +1,10 @@ import 'dart:convert'; import 'dart:io'; -import 'package:flutter/rendering.dart'; import 'package:dartx/dartx_io.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../spot/screenshot_test.dart'; import 'font_test_project.dart'; void main() { diff --git a/test/fonts/load_font_test.dart b/test/fonts/load_font_test.dart new file mode 100644 index 00000000..6e2fdb3d --- /dev/null +++ b/test/fonts/load_font_test.dart @@ -0,0 +1,32 @@ +import 'dart:io'; +import 'dart:ui'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:spot/spot.dart'; + +void main() { + testWidgets('load non-font file does not throw', (tester) async { + final tempDir = Directory.systemTemp.createTempSync(); + addTearDown(() => tempDir.deleteSync(recursive: true)); + final notAFont = File('${tempDir.path}/someFile.txt'); + notAFont.writeAsStringSync('some data'); + + final List messages = []; + // ignore: deprecated_member_use + PlatformDispatcher.instance.onPlatformMessage = ( + String name, + ByteData? data, + PlatformMessageResponseCallback? callback, + ) { + final decoded = SystemChannels.system.codec.decodeMessage(data); + final type = (decoded! as Map)['type']; + messages.add(type); + callback?.call(null); + }; + + expect(messages, isEmpty); + await loadFont('someFont', [notAFont.path]); + expect(messages, ['fontsChange']); // success regardless of file content + }); +} diff --git a/test/fonts/templates/app_font/test/test.dart b/test/fonts/templates/app_font/test/test.dart index 0de9f988..84377525 100644 --- a/test/fonts/templates/app_font/test/test.dart +++ b/test/fonts/templates/app_font/test/test.dart @@ -1,7 +1,5 @@ // ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables -import 'dart:ui'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/test/fonts/templates/default_font/test/test.dart b/test/fonts/templates/default_font/test/test.dart index ee09f217..29681bb3 100644 --- a/test/fonts/templates/default_font/test/test.dart +++ b/test/fonts/templates/default_font/test/test.dart @@ -1,7 +1,6 @@ // ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/test/fonts/templates/dependency_font/test/test.dart b/test/fonts/templates/dependency_font/test/test.dart index d02e0f2c..faf85bb5 100644 --- a/test/fonts/templates/dependency_font/test/test.dart +++ b/test/fonts/templates/dependency_font/test/test.dart @@ -1,7 +1,6 @@ // ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables import 'dart:typed_data'; -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart';