| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:ui'; |
| |
| import 'package:collection/collection.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import 'utils/fake_and_mock_utils.dart'; |
| |
| void main() { |
| group('TestFlutterView', () { |
| FlutterView trueImplicitView() => PlatformDispatcher.instance.implicitView!; |
| FlutterView boundImplicitView() => WidgetsBinding.instance.platformDispatcher.implicitView!; |
| |
| tearDown(() { |
| final TestFlutterView view = (WidgetsBinding.instance as TestWidgetsFlutterBinding).platformDispatcher.views.single; |
| view.reset(); |
| }); |
| |
| testWidgets('can handle new methods without breaking', (WidgetTester tester) async { |
| final dynamic testView = tester.view; |
| // ignore: avoid_dynamic_calls |
| expect(testView.someNewProperty, null); |
| }); |
| |
| testWidgets('can fake devicePixelRatio', (WidgetTester tester) async { |
| verifyPropertyFaked<double>( |
| tester: tester, |
| realValue: trueImplicitView().devicePixelRatio, |
| fakeValue: 2.5, |
| propertyRetriever: () => boundImplicitView().devicePixelRatio, |
| propertyFaker: (_, double fakeValue) { |
| tester.view.devicePixelRatio = fakeValue; |
| }, |
| ); |
| }); |
| |
| testWidgets('can reset devicePixelRatio', (WidgetTester tester) async { |
| verifyPropertyReset<double>( |
| tester: tester, |
| fakeValue: 2.5, |
| propertyRetriever: () => boundImplicitView().devicePixelRatio, |
| propertyResetter: () { |
| tester.view.resetDevicePixelRatio(); |
| }, |
| propertyFaker: (double fakeValue) { |
| tester.view.devicePixelRatio = fakeValue; |
| }, |
| ); |
| }); |
| |
| testWidgets('can fake displayFeatures', (WidgetTester tester) async { |
| verifyPropertyFaked<List<DisplayFeature>>( |
| tester: tester, |
| realValue: trueImplicitView().displayFeatures, |
| fakeValue: <DisplayFeature>[const DisplayFeature(bounds: Rect.fromLTWH(0, 0, 500, 30), type: DisplayFeatureType.unknown, state: DisplayFeatureState.unknown)], |
| propertyRetriever: () => boundImplicitView().displayFeatures, |
| propertyFaker: (_, List<DisplayFeature> fakeValue) { |
| tester.view.displayFeatures = fakeValue; |
| }, |
| ); |
| }); |
| |
| testWidgets('can reset displayFeatures', (WidgetTester tester) async { |
| verifyPropertyReset<List<DisplayFeature>>( |
| tester: tester, |
| fakeValue: <DisplayFeature>[const DisplayFeature(bounds: Rect.fromLTWH(0, 0, 500, 30), type: DisplayFeatureType.unknown, state: DisplayFeatureState.unknown)], |
| propertyRetriever: () => boundImplicitView().displayFeatures, |
| propertyResetter: () { |
| tester.view.resetDisplayFeatures(); |
| }, |
| propertyFaker: (List<DisplayFeature> fakeValue) { |
| tester.view.displayFeatures = fakeValue; |
| }, |
| ); |
| }); |
| |
| testWidgets('can fake padding', (WidgetTester tester) async { |
| verifyPropertyFaked<ViewPadding>( |
| tester: tester, |
| realValue: trueImplicitView().padding, |
| fakeValue: FakeViewPadding.zero, |
| propertyRetriever: () => boundImplicitView().padding, |
| propertyFaker: (_, ViewPadding fakeValue) { |
| tester.view.padding = fakeValue as FakeViewPadding; |
| }, |
| matcher: matchesViewPadding |
| ); |
| }); |
| |
| testWidgets('can reset padding', (WidgetTester tester) async { |
| verifyPropertyReset<ViewPadding>( |
| tester: tester, |
| fakeValue: FakeViewPadding.zero, |
| propertyRetriever: () => boundImplicitView().padding, |
| propertyResetter: () { |
| tester.view.resetPadding(); |
| }, |
| propertyFaker: (ViewPadding fakeValue) { |
| tester.view.padding = fakeValue as FakeViewPadding; |
| }, |
| matcher: matchesViewPadding |
| ); |
| }); |
| |
| testWidgets('can fake physicalGeometry', (WidgetTester tester) async { |
| verifyPropertyFaked<Rect>( |
| tester: tester, |
| realValue: trueImplicitView().physicalGeometry, |
| fakeValue: const Rect.fromLTWH(0, 0, 550, 850), |
| propertyRetriever: () => boundImplicitView().physicalGeometry, |
| propertyFaker: (_, Rect fakeValue) { |
| tester.view.physicalGeometry = fakeValue; |
| }, |
| ); |
| }); |
| |
| testWidgets('can reset physicalGeometry', (WidgetTester tester) async { |
| verifyPropertyReset<Rect>( |
| tester: tester, |
| fakeValue: const Rect.fromLTWH(0, 0, 35, 475), |
| propertyRetriever: () => boundImplicitView().physicalGeometry, |
| propertyResetter: () { |
| tester.view.resetPhysicalGeometry(); |
| }, |
| propertyFaker: (Rect fakeValue) { |
| tester.view.physicalGeometry = fakeValue; |
| }, |
| ); |
| }); |
| |
| testWidgets('updating physicalGeometry also updates physicalSize', (WidgetTester tester) async { |
| const Rect testGeometry = Rect.fromLTWH(0, 0, 450, 575); |
| tester.view.physicalGeometry = testGeometry; |
| |
| expect(tester.view.physicalSize, testGeometry.size); |
| }); |
| |
| testWidgets('can fake physicalSize', (WidgetTester tester) async { |
| verifyPropertyFaked<Size>( |
| tester: tester, |
| realValue: trueImplicitView().physicalSize, |
| fakeValue: const Size(50, 50), |
| propertyRetriever: () => boundImplicitView().physicalSize, |
| propertyFaker: (_, Size fakeValue) { |
| tester.view.physicalSize = fakeValue; |
| }, |
| ); |
| }); |
| |
| testWidgets('can reset physicalSize', (WidgetTester tester) async { |
| verifyPropertyReset<Size>( |
| tester: tester, |
| fakeValue: const Size(50, 50), |
| propertyRetriever: () => boundImplicitView().physicalSize, |
| propertyResetter: () { |
| tester.view.resetPhysicalSize(); |
| }, |
| propertyFaker: (Size fakeValue) { |
| tester.view.physicalSize = fakeValue; |
| }, |
| ); |
| }); |
| |
| testWidgets('updating physicalSize also updates physicalGeometry', (WidgetTester tester) async { |
| const Rect testGeometry = Rect.fromLTWH(0, 0, 450, 575); |
| const Size testSize = Size(50, 50); |
| const Rect expectedGeometry = Rect.fromLTWH(0, 0, 50, 50); |
| |
| tester.view.physicalGeometry = testGeometry; |
| tester.view.physicalSize = testSize; |
| |
| expect(tester.view.physicalGeometry, expectedGeometry); |
| }); |
| |
| testWidgets('can fake systemGestureInsets', (WidgetTester tester) async { |
| verifyPropertyFaked<ViewPadding>( |
| tester: tester, |
| realValue: trueImplicitView().systemGestureInsets, |
| fakeValue: FakeViewPadding.zero, |
| propertyRetriever: () => boundImplicitView().systemGestureInsets, |
| propertyFaker: (_, ViewPadding fakeValue) { |
| tester.view.systemGestureInsets = fakeValue as FakeViewPadding; |
| }, |
| matcher: matchesViewPadding |
| ); |
| }); |
| |
| testWidgets('can reset systemGestureInsets', (WidgetTester tester) async { |
| verifyPropertyReset<ViewPadding>( |
| tester: tester, |
| fakeValue: FakeViewPadding.zero, |
| propertyRetriever: () => boundImplicitView().systemGestureInsets, |
| propertyResetter: () { |
| tester.view.resetSystemGestureInsets(); |
| }, |
| propertyFaker: (ViewPadding fakeValue) { |
| tester.view.systemGestureInsets = fakeValue as FakeViewPadding; |
| }, |
| matcher: matchesViewPadding |
| ); |
| }); |
| |
| testWidgets('can fake viewInsets', (WidgetTester tester) async { |
| verifyPropertyFaked<ViewPadding>( |
| tester: tester, |
| realValue: trueImplicitView().viewInsets, |
| fakeValue: FakeViewPadding.zero, |
| propertyRetriever: () => boundImplicitView().viewInsets, |
| propertyFaker: (_, ViewPadding fakeValue) { |
| tester.view.viewInsets = fakeValue as FakeViewPadding; |
| }, |
| matcher: matchesViewPadding |
| ); |
| }); |
| |
| testWidgets('can reset viewInsets', (WidgetTester tester) async { |
| verifyPropertyReset<ViewPadding>( |
| tester: tester, |
| fakeValue: FakeViewPadding.zero, |
| propertyRetriever: () => boundImplicitView().viewInsets, |
| propertyResetter: () { |
| tester.view.resetViewInsets(); |
| }, |
| propertyFaker: (ViewPadding fakeValue) { |
| tester.view.viewInsets = fakeValue as FakeViewPadding; |
| }, |
| matcher: matchesViewPadding |
| ); |
| }); |
| |
| testWidgets('can fake viewPadding', (WidgetTester tester) async { |
| verifyPropertyFaked<ViewPadding>( |
| tester: tester, |
| realValue: trueImplicitView().viewPadding, |
| fakeValue: FakeViewPadding.zero, |
| propertyRetriever: () => boundImplicitView().viewPadding, |
| propertyFaker: (_, ViewPadding fakeValue) { |
| tester.view.viewPadding = fakeValue as FakeViewPadding; |
| }, |
| matcher: matchesViewPadding |
| ); |
| }); |
| |
| testWidgets('can reset viewPadding', (WidgetTester tester) async { |
| verifyPropertyReset<ViewPadding>( |
| tester: tester, |
| fakeValue: FakeViewPadding.zero, |
| propertyRetriever: () => boundImplicitView().viewPadding, |
| propertyResetter: () { |
| tester.view.resetViewPadding(); |
| }, |
| propertyFaker: (ViewPadding fakeValue) { |
| tester.view.viewPadding = fakeValue as FakeViewPadding; |
| }, |
| matcher: matchesViewPadding |
| ); |
| }); |
| |
| testWidgets('can clear out fake properties all at once', (WidgetTester tester) async { |
| final FlutterViewSnapshot initial = FlutterViewSnapshot(tester.view); |
| |
| tester.view.devicePixelRatio = 7; |
| tester.view.displayFeatures = <DisplayFeature>[const DisplayFeature(bounds: Rect.fromLTWH(0, 0, 20, 300), type: DisplayFeatureType.unknown, state: DisplayFeatureState.unknown)]; |
| tester.view.padding = FakeViewPadding.zero; |
| tester.view.physicalGeometry = const Rect.fromLTWH(0, 0, 505, 805); |
| tester.view.systemGestureInsets = FakeViewPadding.zero; |
| tester.view.viewInsets = FakeViewPadding.zero; |
| tester.view.viewPadding = FakeViewPadding.zero; |
| tester.view.gestureSettings = const GestureSettings(physicalTouchSlop: 4, physicalDoubleTapSlop: 5); |
| |
| final FlutterViewSnapshot faked = FlutterViewSnapshot(tester.view); |
| |
| tester.view.reset(); |
| |
| final FlutterViewSnapshot reset = FlutterViewSnapshot(tester.view); |
| |
| expect(initial, isNot(matchesSnapshot(faked))); |
| expect(initial, matchesSnapshot(reset)); |
| }); |
| |
| testWidgets('render is passed through to backing FlutterView', (WidgetTester tester) async { |
| final Scene expectedScene = SceneBuilder().build(); |
| final _FakeFlutterView backingView = _FakeFlutterView(); |
| final TestFlutterView view = TestFlutterView( |
| view: backingView, |
| platformDispatcher: tester.binding.platformDispatcher, |
| ); |
| |
| view.render(expectedScene); |
| |
| expect(backingView.lastRenderedScene, isNotNull); |
| expect(backingView.lastRenderedScene, expectedScene); |
| }); |
| |
| testWidgets('updateSemantics is passed through to backing FlutterView', (WidgetTester tester) async { |
| final SemanticsUpdate expectedUpdate = SemanticsUpdateBuilder().build(); |
| final _FakeFlutterView backingView = _FakeFlutterView(); |
| final TestFlutterView view = TestFlutterView( |
| view: backingView, |
| platformDispatcher: tester.binding.platformDispatcher, |
| ); |
| |
| view.updateSemantics(expectedUpdate); |
| |
| expect(backingView.lastSemanticsUpdate, isNotNull); |
| expect(backingView.lastSemanticsUpdate, expectedUpdate); |
| }); |
| }); |
| } |
| |
| |
| Matcher matchesSnapshot(FlutterViewSnapshot expected) => _FlutterViewSnapshotMatcher(expected); |
| |
| class _FlutterViewSnapshotMatcher extends Matcher { |
| _FlutterViewSnapshotMatcher(this.expected); |
| |
| final FlutterViewSnapshot expected; |
| |
| @override |
| Description describe(Description description) { |
| description.add('snapshot of a FlutterView matches'); |
| return description; |
| } |
| |
| @override |
| Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) { |
| assert(item is FlutterViewSnapshot, 'Can only match against snapshots of FlutterView.'); |
| final FlutterViewSnapshot actual = item as FlutterViewSnapshot; |
| |
| if (actual.devicePixelRatio != expected.devicePixelRatio) { |
| mismatchDescription.add('actual.devicePixelRatio (${actual.devicePixelRatio}) did not match expected.devicePixelRatio (${expected.devicePixelRatio})'); |
| } |
| if (!actual.displayFeatures.equals(expected.displayFeatures)) { |
| mismatchDescription.add('actual.displayFeatures did not match expected.devicePixelRatio'); |
| mismatchDescription.addAll('Actual: [', ',', ']', actual.displayFeatures); |
| mismatchDescription.addAll('Expected: [', ',', ']', expected.displayFeatures); |
| } |
| if (actual.gestureSettings != expected.gestureSettings) { |
| mismatchDescription.add('actual.gestureSettings (${actual.gestureSettings}) did not match expected.gestureSettings (${expected.gestureSettings})'); |
| } |
| |
| final Matcher paddingMatcher = matchesViewPadding(expected.padding); |
| if (!paddingMatcher.matches(actual.padding, matchState)) { |
| mismatchDescription.add('actual.padding (${actual.padding}) did not match expected.padding (${expected.padding})'); |
| paddingMatcher.describeMismatch(actual.padding, mismatchDescription, matchState, verbose); |
| } |
| |
| if (actual.physicalGeometry != expected.physicalGeometry) { |
| mismatchDescription.add('actual.physicalGeometry (${actual.physicalGeometry}) did not match expected.physicalGeometry (${expected.physicalGeometry})'); |
| } |
| if (actual.physicalSize != expected.physicalSize) { |
| mismatchDescription.add('actual.physicalSize (${actual.physicalSize}) did not match expected.physicalSize (${expected.physicalSize})'); |
| } |
| |
| final Matcher systemGestureInsetsMatcher = matchesViewPadding(expected.systemGestureInsets); |
| if (!systemGestureInsetsMatcher.matches(actual.systemGestureInsets, matchState)) { |
| mismatchDescription.add('actual.systemGestureInsets (${actual.systemGestureInsets}) did not match expected.systemGestureInsets (${expected.systemGestureInsets})'); |
| systemGestureInsetsMatcher.describeMismatch(actual.systemGestureInsets, mismatchDescription, matchState, verbose); |
| } |
| |
| if (actual.viewId != expected.viewId) { |
| mismatchDescription.add('actual.viewId (${actual.viewId}) did not match expected.viewId (${expected.viewId})'); |
| } |
| |
| final Matcher viewInsetsMatcher = matchesViewPadding(expected.viewInsets); |
| if (!viewInsetsMatcher.matches(actual.viewInsets, matchState)) { |
| mismatchDescription.add('actual.viewInsets (${actual.viewInsets}) did not match expected.viewInsets (${expected.viewInsets})'); |
| viewInsetsMatcher.describeMismatch(actual.viewInsets, mismatchDescription, matchState, verbose); |
| } |
| |
| final Matcher viewPaddingMatcher = matchesViewPadding(expected.viewPadding); |
| if (!viewPaddingMatcher.matches(actual.viewPadding, matchState)) { |
| mismatchDescription.add('actual.viewPadding (${actual.viewPadding}) did not match expected.devicePixelRatio (${expected.viewPadding})'); |
| viewPaddingMatcher.describeMismatch(actual.viewPadding, mismatchDescription, matchState, verbose); |
| } |
| |
| return mismatchDescription; |
| } |
| |
| @override |
| bool matches(dynamic item, Map<dynamic, dynamic> matchState) { |
| assert(item is FlutterViewSnapshot, 'Can only match against snapshots of FlutterView.'); |
| final FlutterViewSnapshot actual = item as FlutterViewSnapshot; |
| |
| return actual.devicePixelRatio == expected.devicePixelRatio && |
| actual.displayFeatures.equals(expected.displayFeatures) && |
| actual.gestureSettings == expected.gestureSettings && |
| matchesViewPadding(expected.padding).matches(actual.padding, matchState) && |
| actual.physicalGeometry == expected.physicalGeometry && |
| actual.physicalSize == expected.physicalSize && |
| matchesViewPadding(expected.systemGestureInsets).matches(actual.padding, matchState) && |
| actual.viewId == expected.viewId && |
| matchesViewPadding(expected.viewInsets).matches(actual.viewInsets, matchState) && |
| matchesViewPadding(expected.viewPadding).matches(actual.viewPadding, matchState); |
| } |
| } |
| |
| class FlutterViewSnapshot { |
| FlutterViewSnapshot(FlutterView view) : |
| devicePixelRatio = view.devicePixelRatio, |
| displayFeatures = <DisplayFeature>[...view.displayFeatures], |
| gestureSettings = view.gestureSettings, |
| padding = view.padding, |
| physicalGeometry = view.physicalGeometry, |
| physicalSize = view.physicalSize, |
| systemGestureInsets = view.systemGestureInsets, |
| viewId = view.viewId, |
| viewInsets = view.viewInsets, |
| viewPadding = view.viewPadding; |
| |
| final double devicePixelRatio; |
| final List<DisplayFeature> displayFeatures; |
| final GestureSettings gestureSettings; |
| final ViewPadding padding; |
| final Rect physicalGeometry; |
| final Size physicalSize; |
| final ViewPadding systemGestureInsets; |
| final Object viewId; |
| final ViewPadding viewInsets; |
| final ViewPadding viewPadding; |
| } |
| |
| class _FakeFlutterView implements FlutterView { |
| SemanticsUpdate? lastSemanticsUpdate; |
| Scene? lastRenderedScene; |
| |
| @override |
| void updateSemantics(SemanticsUpdate update) { |
| lastSemanticsUpdate = update; |
| } |
| |
| @override |
| void render(Scene scene) { |
| lastRenderedScene = scene; |
| } |
| |
| @override |
| dynamic noSuchMethod(Invocation invocation) { |
| return null; |
| } |
| } |