window.dart 19 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:typed_data' show ByteData;
6
import 'dart:ui' as ui hide window;
7

8 9
import 'package:flutter/foundation.dart';

10 11
/// [SingletonFlutterWindow] that wraps another [SingletonFlutterWindow] and
/// allows faking of some properties for testing purposes.
12 13
///
/// Tests for certain widgets, e.g., [MaterialApp], might require faking certain
14 15 16 17 18
/// properties of a [SingletonFlutterWindow]. [TestWindow] facilitates the
/// faking of these properties by overriding the properties of a real
/// [SingletonFlutterWindow] with desired fake values. The binding used within
/// tests, [TestWidgetsFlutterBinding], contains a [TestWindow] that is used by
/// all tests.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
///
/// ## Sample Code
///
/// A test can utilize a [TestWindow] in the following way:
///
/// ```dart
/// testWidgets('your test name here', (WidgetTester tester) async {
///   // Retrieve the TestWidgetsFlutterBinding.
///   final TestWidgetsFlutterBinding testBinding = tester.binding;
///
///   // Fake the desired properties of the TestWindow. All code running
///   // within this test will perceive the following fake text scale
///   // factor as the real text scale factor of the window.
///   testBinding.window.textScaleFactorFakeValue = 2.5;
///
///   // Test code that depends on text scale factor here.
/// });
/// ```
///
/// The [TestWidgetsFlutterBinding] is recreated for each test and
/// therefore any fake values defined in one test will not persist
/// to the next.
///
42 43 44 45
/// If a test needs to override a real [SingletonFlutterWindow] property and
/// then later return to using the real [SingletonFlutterWindow] property,
/// [TestWindow] provides methods to clear each individual test value, e.g.,
/// [clearLocaleTestValue()].
46
///
47 48
/// To clear all fake test values in a [TestWindow], consider using
/// [clearAllTestValues()].
49 50 51
class TestWindow implements ui.SingletonFlutterWindow {
  /// Constructs a [TestWindow] that defers all behavior to the given
  /// [dart:ui.SingletonFlutterWindow] unless explicitly overridden for test purposes.
52
  TestWindow({
53
    required ui.SingletonFlutterWindow window,
54 55
  }) : _window = window;

56 57
  /// The [dart:ui.SingletonFlutterWindow] that is wrapped by this [TestWindow].
  final ui.SingletonFlutterWindow _window;
58 59 60

  @override
  double get devicePixelRatio => _devicePixelRatio ?? _window.devicePixelRatio;
61
  double? _devicePixelRatio;
62 63
  /// Hides the real device pixel ratio and reports the given [devicePixelRatio]
  /// instead.
64
  set devicePixelRatioTestValue(double devicePixelRatio) { // ignore: avoid_setters_without_getters
65
    _devicePixelRatio = devicePixelRatio;
66
    onMetricsChanged?.call();
67
  }
68 69
  /// Deletes any existing test device pixel ratio and returns to using the real
  /// device pixel ratio.
70 71
  void clearDevicePixelRatioTestValue() {
    _devicePixelRatio = null;
72
    onMetricsChanged?.call();
73 74 75
  }

  @override
76 77
  ui.Size get physicalSize => _physicalSizeTestValue ?? _window.physicalSize;
  ui.Size? _physicalSizeTestValue;
78 79
  /// Hides the real physical size and reports the given [physicalSizeTestValue]
  /// instead.
80
  set physicalSizeTestValue (ui.Size physicalSizeTestValue) { // ignore: avoid_setters_without_getters
81
    _physicalSizeTestValue = physicalSizeTestValue;
82
    onMetricsChanged?.call();
83
  }
84 85
  /// Deletes any existing test physical size and returns to using the real
  /// physical size.
86 87
  void clearPhysicalSizeTestValue() {
    _physicalSizeTestValue = null;
88
    onMetricsChanged?.call();
89 90 91
  }

  @override
92 93
  ui.WindowPadding get viewInsets => _viewInsetsTestValue ??  _window.viewInsets;
  ui.WindowPadding? _viewInsetsTestValue;
94 95
  /// Hides the real view insets and reports the given [viewInsetsTestValue]
  /// instead.
96
  set viewInsetsTestValue(ui.WindowPadding viewInsetsTestValue) { // ignore: avoid_setters_without_getters
97
    _viewInsetsTestValue = viewInsetsTestValue;
98
    onMetricsChanged?.call();
99
  }
100 101
  /// Deletes any existing test view insets and returns to using the real view
  /// insets.
102 103
  void clearViewInsetsTestValue() {
    _viewInsetsTestValue = null;
104
    onMetricsChanged?.call();
105 106
  }

107
  @override
108 109
  ui.WindowPadding get viewPadding => _viewPaddingTestValue ?? _window.padding;
  ui.WindowPadding? _viewPaddingTestValue;
110 111
  /// Hides the real view padding and reports the given [paddingTestValue]
  /// instead.
112
  set viewPaddingTestValue(ui.WindowPadding viewPaddingTestValue) { // ignore: avoid_setters_without_getters
113
    _viewPaddingTestValue = viewPaddingTestValue;
114
    onMetricsChanged?.call();
115
  }
116 117
  /// Deletes any existing test view padding and returns to using the real
  /// viewPadding.
118 119
  void clearViewPaddingTestValue() {
    _viewPaddingTestValue = null;
120
    onMetricsChanged?.call();
121 122
  }

123
  @override
124 125
  ui.WindowPadding get padding => _paddingTestValue ?? _window.padding;
  ui.WindowPadding? _paddingTestValue;
126
  /// Hides the real padding and reports the given [paddingTestValue] instead.
127
  set paddingTestValue(ui.WindowPadding paddingTestValue) { // ignore: avoid_setters_without_getters
128
    _paddingTestValue = paddingTestValue;
129
    onMetricsChanged?.call();
130 131 132 133
  }
  /// Deletes any existing test padding and returns to using the real padding.
  void clearPaddingTestValue() {
    _paddingTestValue = null;
134
    onMetricsChanged?.call();
135 136
  }

137
  @override
138 139
  ui.WindowPadding get systemGestureInsets => _systemGestureInsetsTestValue ?? _window.systemGestureInsets;
  ui.WindowPadding? _systemGestureInsetsTestValue;
140
  /// Hides the real system gesture insets and reports the given [systemGestureInsetsTestValue] instead.
141
  set systemGestureInsetsTestValue(ui.WindowPadding systemGestureInsetsTestValue) { // ignore: avoid_setters_without_getters
142
    _systemGestureInsetsTestValue = systemGestureInsetsTestValue;
143
    onMetricsChanged?.call();
144 145 146 147
  }
  /// Deletes any existing test system gesture insets and returns to using the real system gesture insets.
  void clearSystemGestureInsetsTestValue() {
    _systemGestureInsetsTestValue = null;
148
    onMetricsChanged?.call();
149 150
  }

151
  @override
152
  ui.VoidCallback? get onMetricsChanged => platformDispatcher.onMetricsChanged;
153
  @override
154 155
  set onMetricsChanged(ui.VoidCallback? callback) {
    platformDispatcher.onMetricsChanged = callback;
156 157 158
  }

  @override
159
  ui.Locale get locale => _localeTestValue ?? platformDispatcher.locale;
160
  ui.Locale? _localeTestValue;
161
  /// Hides the real locale and reports the given [localeTestValue] instead.
162
  set localeTestValue(ui.Locale localeTestValue) { // ignore: avoid_setters_without_getters
163
    _localeTestValue = localeTestValue;
164
    onLocaleChanged?.call();
165 166 167 168
  }
  /// Deletes any existing test locale and returns to using the real locale.
  void clearLocaleTestValue() {
    _localeTestValue = null;
169
    onLocaleChanged?.call();
170 171 172
  }

  @override
173
  List<ui.Locale> get locales => _localesTestValue ?? platformDispatcher.locales;
174
  List<ui.Locale>? _localesTestValue;
175
  /// Hides the real locales and reports the given [localesTestValue] instead.
176
  set localesTestValue(List<ui.Locale> localesTestValue) { // ignore: avoid_setters_without_getters
177
    _localesTestValue = localesTestValue;
178
    onLocaleChanged?.call();
179 180 181 182
  }
  /// Deletes any existing test locales and returns to using the real locales.
  void clearLocalesTestValue() {
    _localesTestValue = null;
183
    onLocaleChanged?.call();
184 185 186
  }

  @override
187
  ui.VoidCallback? get onLocaleChanged => platformDispatcher.onLocaleChanged;
188
  @override
189 190
  set onLocaleChanged(ui.VoidCallback? callback) {
    platformDispatcher.onLocaleChanged = callback;
191 192
  }

193 194
  @override
  String get initialLifecycleState => _initialLifecycleStateTestValue;
195
  String _initialLifecycleStateTestValue = '';
196
  /// Sets a faked initialLifecycleState for testing.
197
  set initialLifecycleStateTestValue(String state) { // ignore: avoid_setters_without_getters
198 199 200
    _initialLifecycleStateTestValue = state;
  }

201
  @override
202
  double get textScaleFactor => _textScaleFactorTestValue ?? platformDispatcher.textScaleFactor;
203
  double? _textScaleFactorTestValue;
204 205
  /// Hides the real text scale factor and reports the given
  /// [textScaleFactorTestValue] instead.
206
  set textScaleFactorTestValue(double textScaleFactorTestValue) { // ignore: avoid_setters_without_getters
207
    _textScaleFactorTestValue = textScaleFactorTestValue;
208
    onTextScaleFactorChanged?.call();
209
  }
210 211
  /// Deletes any existing test text scale factor and returns to using the real
  /// text scale factor.
212 213
  void clearTextScaleFactorTestValue() {
    _textScaleFactorTestValue = null;
214
    onTextScaleFactorChanged?.call();
215 216
  }

217
  @override
218 219
  ui.Brightness get platformBrightness => _platformBrightnessTestValue ?? platformDispatcher.platformBrightness;
  ui.Brightness? _platformBrightnessTestValue;
220
  @override
221
  ui.VoidCallback? get onPlatformBrightnessChanged => platformDispatcher.onPlatformBrightnessChanged;
222
  @override
223 224
  set onPlatformBrightnessChanged(ui.VoidCallback? callback) {
    platformDispatcher.onPlatformBrightnessChanged = callback;
225
  }
226 227
  /// Hides the real text scale factor and reports the given
  /// [platformBrightnessTestValue] instead.
228
  set platformBrightnessTestValue(ui.Brightness platformBrightnessTestValue) { // ignore: avoid_setters_without_getters
229
    _platformBrightnessTestValue = platformBrightnessTestValue;
230
    onPlatformBrightnessChanged?.call();
231
  }
232 233
  /// Deletes any existing test platform brightness and returns to using the
  /// real platform brightness.
234 235
  void clearPlatformBrightnessTestValue() {
    _platformBrightnessTestValue = null;
236
    onPlatformBrightnessChanged?.call();
237 238
  }

239
  @override
240
  bool get alwaysUse24HourFormat => _alwaysUse24HourFormatTestValue ?? platformDispatcher.alwaysUse24HourFormat;
241
  bool? _alwaysUse24HourFormatTestValue;
242 243
  /// Hides the real clock format and reports the given
  /// [alwaysUse24HourFormatTestValue] instead.
244
  set alwaysUse24HourFormatTestValue(bool alwaysUse24HourFormatTestValue) { // ignore: avoid_setters_without_getters
245 246
    _alwaysUse24HourFormatTestValue = alwaysUse24HourFormatTestValue;
  }
247 248
  /// Deletes any existing test clock format and returns to using the real clock
  /// format.
249 250 251 252 253
  void clearAlwaysUse24HourTestValue() {
    _alwaysUse24HourFormatTestValue = null;
  }

  @override
254
  ui.VoidCallback? get onTextScaleFactorChanged => platformDispatcher.onTextScaleFactorChanged;
255
  @override
256 257
  set onTextScaleFactorChanged(ui.VoidCallback? callback) {
    platformDispatcher.onTextScaleFactorChanged = callback;
258 259 260
  }

  @override
261
  ui.FrameCallback? get onBeginFrame => platformDispatcher.onBeginFrame;
262
  @override
263 264
  set onBeginFrame(ui.FrameCallback? callback) {
    platformDispatcher.onBeginFrame = callback;
265 266 267
  }

  @override
268
  ui.VoidCallback? get onDrawFrame => platformDispatcher.onDrawFrame;
269
  @override
270 271
  set onDrawFrame(ui.VoidCallback? callback) {
    platformDispatcher.onDrawFrame = callback;
272 273
  }

274
  @override
275
  ui.TimingsCallback? get onReportTimings => platformDispatcher.onReportTimings;
276
  @override
277 278
  set onReportTimings(ui.TimingsCallback? callback) {
    platformDispatcher.onReportTimings = callback;
279
  }
280

281
  @override
282
  ui.PointerDataPacketCallback? get onPointerDataPacket => platformDispatcher.onPointerDataPacket;
283
  @override
284 285
  set onPointerDataPacket(ui.PointerDataPacketCallback? callback) {
    platformDispatcher.onPointerDataPacket = callback;
286 287 288
  }

  @override
289
  String get defaultRouteName => _defaultRouteNameTestValue ?? platformDispatcher.defaultRouteName;
290
  String? _defaultRouteNameTestValue;
291 292
  /// Hides the real default route name and reports the given
  /// [defaultRouteNameTestValue] instead.
293
  set defaultRouteNameTestValue(String defaultRouteNameTestValue) { // ignore: avoid_setters_without_getters
294 295
    _defaultRouteNameTestValue = defaultRouteNameTestValue;
  }
296 297
  /// Deletes any existing test default route name and returns to using the real
  /// default route name.
298 299 300 301 302 303
  void clearDefaultRouteNameTestValue() {
    _defaultRouteNameTestValue = null;
  }

  @override
  void scheduleFrame() {
304
    platformDispatcher.scheduleFrame();
305 306 307
  }

  @override
308
  void render(ui.Scene scene) {
309 310 311 312
    _window.render(scene);
  }

  @override
313
  bool get semanticsEnabled => _semanticsEnabledTestValue ?? platformDispatcher.semanticsEnabled;
314
  bool? _semanticsEnabledTestValue;
315 316
  /// Hides the real semantics enabled and reports the given
  /// [semanticsEnabledTestValue] instead.
317
  set semanticsEnabledTestValue(bool semanticsEnabledTestValue) { // ignore: avoid_setters_without_getters
318
    _semanticsEnabledTestValue = semanticsEnabledTestValue;
319
    onSemanticsEnabledChanged?.call();
320
  }
321 322
  /// Deletes any existing test semantics enabled and returns to using the real
  /// semantics enabled.
323 324
  void clearSemanticsEnabledTestValue() {
    _semanticsEnabledTestValue = null;
325
    onSemanticsEnabledChanged?.call();
326 327 328
  }

  @override
329
  ui.VoidCallback? get onSemanticsEnabledChanged => platformDispatcher.onSemanticsEnabledChanged;
330
  @override
331 332
  set onSemanticsEnabledChanged(ui.VoidCallback? callback) {
    platformDispatcher.onSemanticsEnabledChanged = callback;
333 334 335
  }

  @override
336
  ui.SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction;
337
  @override
338 339
  set onSemanticsAction(ui.SemanticsActionCallback? callback) {
    platformDispatcher.onSemanticsAction = callback;
340 341 342
  }

  @override
343 344
  ui.AccessibilityFeatures get accessibilityFeatures => _accessibilityFeaturesTestValue ?? platformDispatcher.accessibilityFeatures;
  ui.AccessibilityFeatures? _accessibilityFeaturesTestValue;
345 346
  /// Hides the real accessibility features and reports the given
  /// [accessibilityFeaturesTestValue] instead.
347 348 349
  ///
  /// Consider using [FakeAccessibilityFeatures] to provide specific
  /// values for the various accessibility features under test.
350
  set accessibilityFeaturesTestValue(ui.AccessibilityFeatures accessibilityFeaturesTestValue) { // ignore: avoid_setters_without_getters
351
    _accessibilityFeaturesTestValue = accessibilityFeaturesTestValue;
352
    onAccessibilityFeaturesChanged?.call();
353
  }
354 355
  /// Deletes any existing test accessibility features and returns to using the
  /// real accessibility features.
356 357
  void clearAccessibilityFeaturesTestValue() {
    _accessibilityFeaturesTestValue = null;
358
    onAccessibilityFeaturesChanged?.call();
359 360
  }

361 362 363 364 365
  @override
  ui.ViewConfiguration get viewConfiguration => _viewConfiguration ?? _window.viewConfiguration;
  ui.ViewConfiguration? _viewConfiguration;

  /// Hide the real view configuration and report the provided [value] instead.
366
  set viewConfigurationTestValue(ui.ViewConfiguration? value) { // ignore: avoid_setters_without_getters
367 368 369 370
    _viewConfiguration = value;
    onMetricsChanged?.call();
  }

371
  @override
372
  ui.VoidCallback? get onAccessibilityFeaturesChanged => platformDispatcher.onAccessibilityFeaturesChanged;
373
  @override
374 375
  set onAccessibilityFeaturesChanged(ui.VoidCallback? callback) {
    platformDispatcher.onAccessibilityFeaturesChanged = callback;
376 377 378
  }

  @override
379 380
  void updateSemantics(ui.SemanticsUpdate update) {
    platformDispatcher.updateSemantics(update);
381 382 383 384
  }

  @override
  void setIsolateDebugName(String name) {
385
    platformDispatcher.setIsolateDebugName(name);
386 387 388
  }

  @override
389 390
  void sendPlatformMessage(
    String name,
391
    ByteData? data,
392
    ui.PlatformMessageResponseCallback? callback,
393
  ) {
394
    platformDispatcher.sendPlatformMessage(name, data, callback);
395 396
  }

397 398 399 400
  @Deprecated(
    'Instead of calling this callback, use ServicesBinding.instance.channelBuffers.push. '
    'This feature was deprecated after v2.1.0-10.0.pre.'
  )
401
  @override
402
  ui.PlatformMessageCallback? get onPlatformMessage => platformDispatcher.onPlatformMessage;
403 404 405 406
  @Deprecated(
    'Instead of setting this callback, use ServicesBinding.instance.defaultBinaryMessenger.setMessageHandler. '
    'This feature was deprecated after v2.1.0-10.0.pre.'
  )
407
  @override
408 409
  set onPlatformMessage(ui.PlatformMessageCallback? callback) {
    platformDispatcher.onPlatformMessage = callback;
410 411
  }

412 413 414
  @override
  ui.PlatformDispatcher get platformDispatcher => _window.platformDispatcher;

415
  /// Delete any test value properties that have been set on this [TestWindow]
416 417
  /// and return to reporting the real [SingletonFlutterWindow] values for all
  /// [SingletonFlutterWindow] properties.
418
  ///
419 420
  /// If desired, clearing of properties can be done on an individual basis,
  /// e.g., [clearLocaleTestValue()].
421 422 423 424 425
  void clearAllTestValues() {
    clearAccessibilityFeaturesTestValue();
    clearAlwaysUse24HourTestValue();
    clearDefaultRouteNameTestValue();
    clearDevicePixelRatioTestValue();
426
    clearPlatformBrightnessTestValue();
427 428 429 430 431 432 433 434
    clearLocaleTestValue();
    clearLocalesTestValue();
    clearPaddingTestValue();
    clearPhysicalSizeTestValue();
    clearSemanticsEnabledTestValue();
    clearTextScaleFactorTestValue();
    clearViewInsetsTestValue();
  }
435 436

  /// This gives us some grace time when the dart:ui side adds something to
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
  /// [SingletonFlutterWindow], and makes things easier when we do rolls to give
  /// us time to catch up.
  @override
  dynamic noSuchMethod(Invocation invocation) {
    return null;
  }
}

/// Test version of [AccessibilityFeatures] in which specific features may
/// be set to arbitrary values.
///
/// By default, all features are disabled. For an instance where all the
/// features are enabled, consider the [FakeAccessibilityFeatures.allOn]
/// constant.
@immutable
// ignore: avoid_implementing_value_types
class FakeAccessibilityFeatures implements ui.AccessibilityFeatures {
  /// Creates a test instance of [AccessibilityFeatures].
  ///
  /// By default, all features are disabled.
  const FakeAccessibilityFeatures({
    this.accessibleNavigation = false,
    this.invertColors = false,
    this.disableAnimations = false,
    this.boldText = false,
    this.reduceMotion = false,
    this.highContrast = false,
  });

  /// An instance of [AccessibilityFeatures] where all the features are enabled.
  static const FakeAccessibilityFeatures allOn = FakeAccessibilityFeatures(
    accessibleNavigation: true,
    invertColors: true,
    disableAnimations: true,
    boldText: true,
    reduceMotion: true,
    highContrast: true,
  );

  @override
  final bool accessibleNavigation;

  @override
  final bool invertColors;

  @override
  final bool disableAnimations;

  @override
  final bool boldText;

  @override
  final bool reduceMotion;

  @override
  final bool highContrast;

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType)
      return false;
    return other is FakeAccessibilityFeatures
        && other.accessibleNavigation == accessibleNavigation
        && other.invertColors == invertColors
        && other.disableAnimations == disableAnimations
        && other.boldText == boldText
        && other.reduceMotion == reduceMotion
        && other.highContrast == highContrast;
  }

  @override
  int get hashCode => ui.hashValues(accessibleNavigation, invertColors, disableAnimations, boldText, reduceMotion, highContrast);

  /// This gives us some grace time when the dart:ui side adds something to
  /// [AccessibilityFeatures], and makes things easier when we do rolls to
  /// give us time to catch up.
  ///
  /// If you would like to add to this class, changes must first be made in the
  /// engine, followed by the framework.
516 517 518 519
  @override
  dynamic noSuchMethod(Invocation invocation) {
    return null;
  }
520
}