mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
446 lines
17 KiB
Dart
446 lines
17 KiB
Dart
// 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;
|
|
}
|
|
}
|