diff --git a/packages/flutter_test/lib/src/matchers.dart b/packages/flutter_test/lib/src/matchers.dart index 8b5a7e696e..7616f43c41 100644 --- a/packages/flutter_test/lib/src/matchers.dart +++ b/packages/flutter_test/lib/src/matchers.dart @@ -246,6 +246,22 @@ Matcher isSameColorAs(Color color, {double threshold = colorEpsilon}) { return _ColorMatcher(color, threshold); } +/// Asserts that the object is a [TextScaler] that reflects the user's font +/// scale preferences from the platform's accessibility settings. +/// +/// This matcher is useful for verifying the text scaling within a widget subtree +/// respects the user accessibility preferences, and not accidentally being +/// shadowed by a [MediaQuery] with a different type of [TextScaler]. +/// +/// In widget tests, the value of the system font scale preference can be +/// changed via [TestPlatformDispatcher.textScaleFactorTestValue]. +/// +/// If `withScaleFactor` is specified and non-null, this matcher also asserts +/// that the [TextScaler]'s' `textScaleFactor` equals `withScaleFactor`. +Matcher isSystemTextScaler({double? withScaleFactor}) { + return _IsSystemTextScaler(withScaleFactor); +} + /// Asserts that an object's toString() is a plausible one-line description. /// /// Specifically, this matcher checks that the string does not contains newline @@ -1205,6 +1221,58 @@ class _IsNotInCard extends Matcher { Description describe(Description description) => description.add('not in card'); } +class _IsSystemTextScaler extends Matcher { + const _IsSystemTextScaler(this.expectedUserTextScaleFactor); + + final double? expectedUserTextScaleFactor; + + // TODO(LongCatIsLooong): update the runtime type after introducing _SystemTextScaler. + static final Type _expectedRuntimeType = (const TextScaler.linear(2)).runtimeType; + + @override + bool matches(dynamic item, Map matchState) { + if (item is! TextScaler) { + return failWithDescription(matchState, '${item.runtimeType} is not a TextScaler'); + } + if (!identical(item.runtimeType, _expectedRuntimeType)) { + return failWithDescription(matchState, '${item.runtimeType} is not a system TextScaler'); + } + final double actualTextScaleFactor = item.textScaleFactor; + if (expectedUserTextScaleFactor != null && + expectedUserTextScaleFactor != actualTextScaleFactor) { + return failWithDescription( + matchState, + 'expecting a scale factor of $expectedUserTextScaleFactor, but got $actualTextScaleFactor', + ); + } + return true; + } + + @override + Description describe(Description description) { + final String scaleFactorExpectation = + expectedUserTextScaleFactor == null ? '' : '(${expectedUserTextScaleFactor}x)'; + return description.add( + 'A TextScaler that reflects the font scale settings in the system user preference ($_expectedRuntimeType$scaleFactorExpectation)', + ); + } + + bool failWithDescription(Map matchState, String description) { + matchState['failure'] = description; + return false; + } + + @override + Description describeMismatch( + dynamic item, + Description mismatchDescription, + Map matchState, + bool verbose, + ) { + return mismatchDescription.add(matchState['failure'] as String); + } +} + class _HasOneLineDescription extends Matcher { const _HasOneLineDescription(); diff --git a/packages/flutter_test/lib/src/window.dart b/packages/flutter_test/lib/src/window.dart index b698a8af5a..562cd1bcbe 100644 --- a/packages/flutter_test/lib/src/window.dart +++ b/packages/flutter_test/lib/src/window.dart @@ -320,6 +320,9 @@ class TestPlatformDispatcher implements PlatformDispatcher { onTextScaleFactorChanged?.call(); } + @override + double scaleFontSize(double unscaledFontSize) => textScaleFactor * unscaledFontSize; + @override Brightness get platformBrightness => _platformBrightnessTestValue ?? _platformDispatcher.platformBrightness; diff --git a/packages/flutter_test/test/matchers_test.dart b/packages/flutter_test/test/matchers_test.dart index d4341b0553..6054fc70a4 100644 --- a/packages/flutter_test/test/matchers_test.dart +++ b/packages/flutter_test/test/matchers_test.dart @@ -393,6 +393,17 @@ void main() { expect(const Color(0x00000000), isSameColorAs(const Color(0x00000002), threshold: 0.008)); }); + testWidgets('isSystemTextScaler', (WidgetTester tester) async { + addTearDown(tester.platformDispatcher.clearAllTestValues); + tester.platformDispatcher.textScaleFactorTestValue = 123; + + final MediaQueryData mediaQueryData = MediaQueryData.fromView(tester.view); + final TextScaler systemScaler = mediaQueryData.textScaler; + expect(systemScaler, isSystemTextScaler()); + expect(systemScaler, isSystemTextScaler(withScaleFactor: 123)); + expect(systemScaler, isNot(isSystemTextScaler(withScaleFactor: 2))); + }); + group('coversSameAreaAs', () { test('empty Paths', () { expect( diff --git a/packages/flutter_test/test/platform_dispatcher_test.dart b/packages/flutter_test/test/platform_dispatcher_test.dart index 14d13b23b6..f393151ecf 100644 --- a/packages/flutter_test/test/platform_dispatcher_test.dart +++ b/packages/flutter_test/test/platform_dispatcher_test.dart @@ -181,6 +181,20 @@ void main() { ); }); + testWidgets('TestPlatformDispatcher has a working scaleFontSize implementation', ( + WidgetTester tester, + ) async { + expect( + TestPlatformDispatcher( + platformDispatcher: _FakePlatformDispatcher( + displays: [_FakeDisplay(id: 2)], + views: [_FakeFlutterView(display: _FakeDisplay(id: 1))], + ), + ).scaleFontSize(2.0), + 2.0, + ); + }); + // TODO(pdblasi-google): Removed this group of tests when the Display API is stable and supported on all platforms. group('TestPlatformDispatcher with unsupported Display API', () { testWidgets('can initialize with empty displays', (WidgetTester tester) async { @@ -314,4 +328,7 @@ class _FakePlatformDispatcher extends Fake implements PlatformDispatcher { @override ViewFocusChangeCallback? onViewFocusChange; + + @override + double get textScaleFactor => 1.0; }