Unverified Commit 07f7ffde authored by pdblasi-google's avatar pdblasi-google Committed by GitHub

Adds `TestDisplay` API for testing `Display` features (#127525)

* Adds `TestDisplay`
* Updates `TestPlatformDispatcher` to wrap all `Display`s and relate them to their appropriate `TestFlutterView`
* Updates `TestFlutterView` to tie `devicePixelRatio` to its display as per the documentation on `Display`

Closes #127225
parent 51f7fdbc
This diff is collapsed.
// 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:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'utils/fake_and_mock_utils.dart';
void main() {
group('TestDisplay', () {
Display trueDisplay() => PlatformDispatcher.instance.displays.single;
TestDisplay boundDisplay() => WidgetsBinding.instance.platformDispatcher.displays.single as TestDisplay;
tearDown(() {
boundDisplay().reset();
});
testWidgets('can handle new methods without breaking', (WidgetTester tester) async {
final dynamic testDisplay = tester.view.display;
//ignore: avoid_dynamic_calls
expect(testDisplay.someNewProperty, null);
});
testWidgets('can fake devicePixelRatio', (WidgetTester tester) async {
verifyPropertyFaked<double>(
tester: tester,
realValue: trueDisplay().devicePixelRatio,
fakeValue: trueDisplay().devicePixelRatio + 1,
propertyRetriever: () => boundDisplay().devicePixelRatio,
propertyFaker: (_, double fake) {
boundDisplay().devicePixelRatio = fake;
},
);
});
testWidgets('can reset devicePixelRatio', (WidgetTester tester) async {
verifyPropertyReset<double>(
tester: tester,
fakeValue: trueDisplay().devicePixelRatio + 1,
propertyRetriever: () => boundDisplay().devicePixelRatio,
propertyResetter: () => boundDisplay().resetDevicePixelRatio(),
propertyFaker: (double fake) {
boundDisplay().devicePixelRatio = fake;
},
);
});
testWidgets('resetting devicePixelRatio also resets view.devicePixelRatio', (WidgetTester tester) async {
verifyPropertyReset(
tester: tester,
fakeValue: trueDisplay().devicePixelRatio + 1,
propertyRetriever: () => tester.view.devicePixelRatio,
propertyResetter: () => boundDisplay().resetDevicePixelRatio(),
propertyFaker: (double dpr) => boundDisplay().devicePixelRatio = dpr,
);
});
testWidgets('updating devicePixelRatio also updates view.devicePixelRatio', (WidgetTester tester) async {
tester.view.display.devicePixelRatio = tester.view.devicePixelRatio + 1;
expect(tester.view.devicePixelRatio, tester.view.display.devicePixelRatio);
});
testWidgets('can fake refreshRate', (WidgetTester tester) async {
verifyPropertyFaked<double>(
tester: tester,
realValue: trueDisplay().refreshRate,
fakeValue: trueDisplay().refreshRate + 1,
propertyRetriever: () => boundDisplay().refreshRate,
propertyFaker: (_, double fake) {
boundDisplay().refreshRate = fake;
},
);
});
testWidgets('can reset refreshRate', (WidgetTester tester) async {
verifyPropertyReset<double>(
tester: tester,
fakeValue: trueDisplay().refreshRate + 1,
propertyRetriever: () => boundDisplay().refreshRate,
propertyResetter: () => boundDisplay().resetRefreshRate(),
propertyFaker: (double fake) {
boundDisplay().refreshRate = fake;
},
);
});
testWidgets('can fake size', (WidgetTester tester) async {
verifyPropertyFaked<Size>(
tester: tester,
realValue: trueDisplay().size,
fakeValue: const Size(354, 856),
propertyRetriever: () => boundDisplay().size,
propertyFaker: (_, Size fake) {
boundDisplay().size = fake;
},
);
});
testWidgets('can reset size', (WidgetTester tester) async {
verifyPropertyReset<Size>(
tester: tester,
fakeValue: const Size(465, 980),
propertyRetriever: () => boundDisplay().size,
propertyResetter: () => boundDisplay().resetSize(),
propertyFaker: (Size fake) {
boundDisplay().size = fake;
},
);
});
testWidgets('can reset all values', (WidgetTester tester) async {
final DisplaySnapshot initial = DisplaySnapshot(tester.view.display);
tester.view.display.devicePixelRatio = 7;
tester.view.display.refreshRate = 40;
tester.view.display.size = const Size(476, 823);
final DisplaySnapshot faked = DisplaySnapshot(tester.view.display);
tester.view.display.reset();
final DisplaySnapshot reset = DisplaySnapshot(tester.view.display);
expect(initial, isNot(matchesSnapshot(faked)));
expect(initial, matchesSnapshot(reset));
});
});
}
class DisplaySnapshot {
DisplaySnapshot(Display display) :
devicePixelRatio = display.devicePixelRatio,
refreshRate = display.refreshRate,
id = display.id,
size = display.size;
final double devicePixelRatio;
final double refreshRate;
final int id;
final Size size;
}
Matcher matchesSnapshot(DisplaySnapshot expected) => _DisplaySnapshotMatcher(expected);
class _DisplaySnapshotMatcher extends Matcher {
_DisplaySnapshotMatcher(this.expected);
final DisplaySnapshot expected;
@override
Description describe(Description description) {
description.add('snapshot of a Display matches');
return description;
}
@override
Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
assert(item is DisplaySnapshot, 'Can only match against snapshots of Display.');
final DisplaySnapshot actual = item as DisplaySnapshot;
if (actual.devicePixelRatio != expected.devicePixelRatio) {
mismatchDescription.add('actual.devicePixelRatio (${actual.devicePixelRatio}) did not match expected.devicePixelRatio (${expected.devicePixelRatio})');
}
if (actual.refreshRate != expected.refreshRate) {
mismatchDescription.add('actual.refreshRate (${actual.refreshRate}) did not match expected.refreshRate (${expected.refreshRate})');
}
if (actual.size != expected.size) {
mismatchDescription.add('actual.size (${actual.size}) did not match expected.size (${expected.size})');
}
if (actual.id != expected.id) {
mismatchDescription.add('actual.id (${actual.id}) did not match expected.id (${expected.id})');
}
return mismatchDescription;
}
@override
bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
assert(item is DisplaySnapshot, 'Can only match against snapshots of Display.');
final DisplaySnapshot actual = item as DisplaySnapshot;
return actual.devicePixelRatio == expected.devicePixelRatio &&
actual.refreshRate == expected.refreshRate &&
actual.size == expected.size &&
actual.id == expected.id;
}
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' show AccessibilityFeatures, Brightness, Locale, PlatformDispatcher; import 'dart:ui' show AccessibilityFeatures, Brightness, Display, FlutterView, Locale, PlatformDispatcher, VoidCallback;
import 'package:flutter/widgets.dart' show WidgetsBinding, WidgetsBindingObserver; import 'package:flutter/widgets.dart' show WidgetsBinding, WidgetsBindingObserver;
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -152,6 +152,102 @@ void main() { ...@@ -152,6 +152,102 @@ void main() {
expect(observer.locales, equals(expectedValue)); expect(observer.locales, equals(expectedValue));
retrieveTestBinding(tester).platformDispatcher.localesTestValue = defaultLocales; retrieveTestBinding(tester).platformDispatcher.localesTestValue = defaultLocales;
}); });
// 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 {
expect(() {
TestPlatformDispatcher(
platformDispatcher: _FakePlatformDispatcher(
displays: <Display>[],
views: <FlutterView>[
_FakeFlutterView(),
],
)
);
}, isNot(throwsA(anything)));
});
testWidgets('can initialize with mismatched displays', (WidgetTester tester) async {
expect(() {
TestPlatformDispatcher(
platformDispatcher: _FakePlatformDispatcher(
displays: <Display>[
_FakeDisplay(id: 2),
],
views: <FlutterView>[
_FakeFlutterView(display: _FakeDisplay(id: 1)),
],
)
);
}, isNot(throwsA(anything)));
});
testWidgets('creates test views for all views', (WidgetTester tester) async {
final PlatformDispatcher backingDispatcher = _FakePlatformDispatcher(
displays: <Display>[],
views: <FlutterView>[
_FakeFlutterView(),
],
);
final TestPlatformDispatcher testDispatcher = TestPlatformDispatcher(
platformDispatcher: backingDispatcher,
);
expect(testDispatcher.views.length, backingDispatcher.views.length);
});
group('creates TestFlutterViews', () {
testWidgets('that defaults to the correct devicePixelRatio', (WidgetTester tester) async {
const double expectedDpr = 2.5;
final TestPlatformDispatcher testDispatcher = TestPlatformDispatcher(
platformDispatcher: _FakePlatformDispatcher(
displays: <Display>[],
views: <FlutterView>[
_FakeFlutterView(devicePixelRatio: expectedDpr),
],
)
);
expect(testDispatcher.views.single.devicePixelRatio, expectedDpr);
});
testWidgets('with working devicePixelRatio setter', (WidgetTester tester) async {
const double expectedDpr = 2.5;
const double defaultDpr = 4;
final TestPlatformDispatcher testDispatcher = TestPlatformDispatcher(
platformDispatcher: _FakePlatformDispatcher(
displays: <Display>[],
views: <FlutterView>[
_FakeFlutterView(devicePixelRatio: defaultDpr),
],
)
);
testDispatcher.views.single.devicePixelRatio = expectedDpr;
expect(testDispatcher.views.single.devicePixelRatio, expectedDpr);
});
testWidgets('with working resetDevicePixelRatio', (WidgetTester tester) async {
const double changedDpr = 2.5;
const double defaultDpr = 4;
final TestPlatformDispatcher testDispatcher = TestPlatformDispatcher(
platformDispatcher: _FakePlatformDispatcher(
displays: <Display>[],
views: <FlutterView>[
_FakeFlutterView(devicePixelRatio: defaultDpr),
],
)
);
testDispatcher.views.single.devicePixelRatio = changedDpr;
testDispatcher.views.single.resetDevicePixelRatio();
expect(testDispatcher.views.single.devicePixelRatio, defaultDpr);
});
});
});
} }
class TestObserver with WidgetsBindingObserver { class TestObserver with WidgetsBindingObserver {
...@@ -162,3 +258,45 @@ class TestObserver with WidgetsBindingObserver { ...@@ -162,3 +258,45 @@ class TestObserver with WidgetsBindingObserver {
this.locales = locales; this.locales = locales;
} }
} }
class _FakeDisplay extends Fake implements Display {
_FakeDisplay({this.id = 0});
@override
final int id;
}
class _FakeFlutterView extends Fake implements FlutterView {
_FakeFlutterView({
this.devicePixelRatio = 1,
Display? display,
}) : _display = display;
@override
final double devicePixelRatio;
// This emulates the PlatformDispatcher not having a display on the engine
// side. We don't have access to the `_displayId` used in the engine to try
// to find it and can't directly extend `FlutterView` to emulate it closer.
@override
Display get display {
assert(_display != null);
return _display!;
}
final Display? _display;
@override
final Object viewId = 1;
}
class _FakePlatformDispatcher extends Fake implements PlatformDispatcher {
_FakePlatformDispatcher({required this.displays, required this.views});
@override
final Iterable<Display> displays;
@override
final Iterable<FlutterView> views;
@override
VoidCallback? onMetricsChanged;
}
...@@ -52,6 +52,12 @@ void main() { ...@@ -52,6 +52,12 @@ void main() {
); );
}); });
testWidgets('updating devicePixelRatio also updates display.devicePixelRatio', (WidgetTester tester) async {
tester.view.devicePixelRatio = tester.view.devicePixelRatio + 1;
expect(tester.view.display.devicePixelRatio, tester.view.devicePixelRatio);
});
testWidgets('can fake displayFeatures', (WidgetTester tester) async { testWidgets('can fake displayFeatures', (WidgetTester tester) async {
verifyPropertyFaked<List<DisplayFeature>>( verifyPropertyFaked<List<DisplayFeature>>(
tester: tester, tester: tester,
...@@ -288,6 +294,7 @@ void main() { ...@@ -288,6 +294,7 @@ void main() {
final TestFlutterView view = TestFlutterView( final TestFlutterView view = TestFlutterView(
view: backingView, view: backingView,
platformDispatcher: tester.binding.platformDispatcher, platformDispatcher: tester.binding.platformDispatcher,
display: _FakeDisplay(),
); );
view.render(expectedScene); view.render(expectedScene);
...@@ -302,6 +309,7 @@ void main() { ...@@ -302,6 +309,7 @@ void main() {
final TestFlutterView view = TestFlutterView( final TestFlutterView view = TestFlutterView(
view: backingView, view: backingView,
platformDispatcher: tester.binding.platformDispatcher, platformDispatcher: tester.binding.platformDispatcher,
display: _FakeDisplay(),
); );
view.updateSemantics(expectedUpdate); view.updateSemantics(expectedUpdate);
...@@ -312,7 +320,6 @@ void main() { ...@@ -312,7 +320,6 @@ void main() {
}); });
} }
Matcher matchesSnapshot(FlutterViewSnapshot expected) => _FlutterViewSnapshotMatcher(expected); Matcher matchesSnapshot(FlutterViewSnapshot expected) => _FlutterViewSnapshotMatcher(expected);
class _FlutterViewSnapshotMatcher extends Matcher { class _FlutterViewSnapshotMatcher extends Matcher {
...@@ -424,7 +431,7 @@ class FlutterViewSnapshot { ...@@ -424,7 +431,7 @@ class FlutterViewSnapshot {
final ViewPadding viewPadding; final ViewPadding viewPadding;
} }
class _FakeFlutterView implements FlutterView { class _FakeFlutterView extends Fake implements FlutterView {
SemanticsUpdate? lastSemanticsUpdate; SemanticsUpdate? lastSemanticsUpdate;
Scene? lastRenderedScene; Scene? lastRenderedScene;
...@@ -437,9 +444,6 @@ class _FakeFlutterView implements FlutterView { ...@@ -437,9 +444,6 @@ class _FakeFlutterView implements FlutterView {
void render(Scene scene) { void render(Scene scene) {
lastRenderedScene = scene; lastRenderedScene = scene;
} }
@override
dynamic noSuchMethod(Invocation invocation) {
return null;
}
} }
class _FakeDisplay extends Fake implements TestDisplay { }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment