mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Implementation of TestWindowingOwner for testing multi-window (#179355)
## What's new? This pull request makes it so that multi window applications can be tested via `testWidgets`. This PR implements the following: - A `_TestWindowingOwner` that is used by the `TestWidgetsFlutterBinding` - A `_TestRegularWindowController` implementation - A `_TestDialogWindowController` implementation - A `_TestFlutterView` and `_TestDisplay` for use by the controllers - Proper `isActivated` status across controllers - Proper minimization, maximization, and fullscreen status - Proper sizing (based on constraints) - Using the new testing abilities in the multiple windows example app I have purposefully not implemented tooltip windows yet, as they are still awaiting an implementation in Win32 to be useful. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
This commit is contained in:
parent
0f591daf10
commit
4000ce2b4e
@ -161,7 +161,7 @@ class _WindowCreatorCard extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
onPressed: () {
|
||||
final UniqueKey key = UniqueKey();
|
||||
windowManager.add(
|
||||
KeyedWindow(
|
||||
@ -180,7 +180,7 @@ class _WindowCreatorCard extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
onPressed: () {
|
||||
final UniqueKey key = UniqueKey();
|
||||
windowManager.add(
|
||||
KeyedWindow(
|
||||
|
||||
@ -64,8 +64,8 @@ class WindowManagerAccessor extends InheritedNotifier<WindowManager> {
|
||||
/// Settings that control the behavior of newly created windows.
|
||||
class WindowSettings {
|
||||
WindowSettings({
|
||||
this.regularSize = const Size(400, 300),
|
||||
this.dialogSize = const Size(200, 200),
|
||||
this.regularSize = const Size(800, 600),
|
||||
this.dialogSize = const Size(400, 400),
|
||||
});
|
||||
|
||||
/// The initial size for newly created regular windows.
|
||||
|
||||
115
examples/multiple_windows/test/multiple_windows_test.dart
Normal file
115
examples/multiple_windows/test/multiple_windows_test.dart
Normal file
@ -0,0 +1,115 @@
|
||||
// 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.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
// ignore: avoid_relative_lib_imports
|
||||
import '../lib/main.dart' as multiple_windows;
|
||||
import 'package:flutter/src/foundation/_features.dart' show isWindowingEnabled;
|
||||
|
||||
void main() {
|
||||
if (!isWindowingEnabled) {
|
||||
const String windowingDisabledErrorMessage = '''
|
||||
Skipping multiple_windows_test.dart because Windowing APIs are not enabled.
|
||||
|
||||
Windowing APIs are currently experimental. Do not use windowing APIs in
|
||||
production applications or plugins published to pub.dev.
|
||||
|
||||
To try experimental windowing APIs:
|
||||
1. Switch to Flutter's main release channel.
|
||||
2. Turn on the windowing feature flag.
|
||||
|
||||
See: https://github.com/flutter/flutter/issues/30701.
|
||||
''';
|
||||
testWidgets(windowingDisabledErrorMessage, (WidgetTester tester) async {
|
||||
// No-op test to avoid "no tests found" error.
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
testWidgets('Multiple windows smoke test', (WidgetTester tester) async {
|
||||
multiple_windows.main();
|
||||
await tester.pump(); // triggers a frame
|
||||
|
||||
expect(
|
||||
find.widgetWithText(AppBar, 'Multi Window Reference App'),
|
||||
findsOneWidget,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Can create a regular window', (WidgetTester tester) async {
|
||||
multiple_windows.main();
|
||||
await tester.pump(); // triggers a frame
|
||||
|
||||
final toTap = find.widgetWithText(OutlinedButton, 'Regular');
|
||||
expect(toTap, findsOneWidget);
|
||||
await tester.tap(toTap);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.widgetWithText(AppBar, 'Regular Window'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Can create a modeless dialog', (WidgetTester tester) async {
|
||||
multiple_windows.main();
|
||||
await tester.pump(); // triggers a frame
|
||||
|
||||
final toTap = find.widgetWithText(OutlinedButton, 'Modeless Dialog');
|
||||
expect(toTap, findsOneWidget);
|
||||
await tester.tap(toTap);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.widgetWithText(AppBar, 'Dialog'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Can create a modal dialog of a regular window', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
multiple_windows.main();
|
||||
await tester.pump(); // triggers a frame
|
||||
|
||||
final toTap = find.widgetWithText(OutlinedButton, 'Regular');
|
||||
expect(toTap, findsOneWidget);
|
||||
await tester.tap(toTap);
|
||||
await tester.pump();
|
||||
|
||||
final createModalButton = find.widgetWithText(
|
||||
ElevatedButton,
|
||||
'Create Modal Dialog',
|
||||
);
|
||||
expect(createModalButton, findsOneWidget);
|
||||
await tester.tap(createModalButton);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.widgetWithText(AppBar, 'Dialog'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Can close a modal dialog of a regular window', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
multiple_windows.main();
|
||||
await tester.pump(); // triggers a frame
|
||||
|
||||
final toTap = find.widgetWithText(OutlinedButton, 'Regular');
|
||||
expect(toTap, findsOneWidget);
|
||||
await tester.tap(toTap);
|
||||
await tester.pump();
|
||||
|
||||
final createModalButton = find.widgetWithText(
|
||||
ElevatedButton,
|
||||
'Create Modal Dialog',
|
||||
);
|
||||
expect(createModalButton, findsOneWidget);
|
||||
await tester.tap(createModalButton);
|
||||
await tester.pump();
|
||||
|
||||
final closeModalButton = find.widgetWithText(ElevatedButton, 'Close');
|
||||
expect(closeModalButton, findsOneWidget);
|
||||
await tester.tap(closeModalButton);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.widgetWithText(AppBar, 'Dialog'), findsNothing);
|
||||
});
|
||||
}
|
||||
@ -2,6 +2,9 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member
|
||||
// ignore_for_file: implementation_imports
|
||||
|
||||
/// @docImport 'dart:io';
|
||||
///
|
||||
/// @docImport 'controller.dart';
|
||||
@ -19,6 +22,9 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/src/foundation/_features.dart' show isWindowingEnabled;
|
||||
import 'package:flutter/src/widgets/_window.dart';
|
||||
import 'package:flutter/src/widgets/_window_positioner.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:matcher/expect.dart' show fail;
|
||||
import 'package:stack_trace/stack_trace.dart' as stack_trace;
|
||||
@ -148,6 +154,482 @@ class CapturedAccessibilityAnnouncement {
|
||||
final Assertiveness assertiveness;
|
||||
}
|
||||
|
||||
class _TestFlutterView implements FlutterView {
|
||||
_TestFlutterView({required this.controller, required TestPlatformDispatcher platformDispatcher})
|
||||
: _platformDispatcher = platformDispatcher,
|
||||
_viewId = _nextViewId++ {
|
||||
platformDispatcher.addTestView(this);
|
||||
}
|
||||
|
||||
static int _nextViewId = 1;
|
||||
final BaseWindowController controller;
|
||||
final TestPlatformDispatcher _platformDispatcher;
|
||||
final int _viewId;
|
||||
|
||||
@override
|
||||
double get devicePixelRatio => display.devicePixelRatio;
|
||||
|
||||
@override
|
||||
ui.Display get display => platformDispatcher.displays.first;
|
||||
|
||||
@override
|
||||
List<ui.DisplayFeature> get displayFeatures => List<ui.DisplayFeature>.empty();
|
||||
|
||||
@override
|
||||
ui.GestureSettings get gestureSettings => const ui.GestureSettings();
|
||||
|
||||
@override
|
||||
ui.ViewPadding get padding => ui.ViewPadding.zero;
|
||||
|
||||
@override
|
||||
ui.ViewConstraints get physicalConstraints => ui.ViewConstraints.tight(physicalSize);
|
||||
|
||||
@override
|
||||
ui.Size get physicalSize => controller.contentSize * devicePixelRatio;
|
||||
|
||||
@override
|
||||
ui.PlatformDispatcher get platformDispatcher => _platformDispatcher;
|
||||
|
||||
@override
|
||||
ui.ViewPadding get systemGestureInsets => ui.ViewPadding.zero;
|
||||
|
||||
@override
|
||||
int get viewId => _viewId;
|
||||
|
||||
@override
|
||||
ui.ViewPadding get viewInsets => ui.ViewPadding.zero;
|
||||
|
||||
@override
|
||||
ui.ViewPadding get viewPadding => ui.ViewPadding.zero;
|
||||
|
||||
@override
|
||||
void render(ui.Scene scene, {ui.Size? size}) {}
|
||||
|
||||
@override
|
||||
void updateSemantics(ui.SemanticsUpdate update) {}
|
||||
}
|
||||
|
||||
mixin _ChildWindowHierarchyMixin {
|
||||
final List<BaseWindowController> _children = <BaseWindowController>[];
|
||||
|
||||
/// Tracks a child window controller.
|
||||
void addChild(BaseWindowController child) {
|
||||
_children.add(child);
|
||||
}
|
||||
|
||||
/// Stops tracking a child window controller.
|
||||
void removeChild(BaseWindowController child) {
|
||||
_children.remove(child);
|
||||
}
|
||||
|
||||
/// Removes and destroys all child window controllers.
|
||||
void removeAllChildren() {
|
||||
for (final BaseWindowController child in _children) {
|
||||
child.destroy();
|
||||
}
|
||||
_children.clear();
|
||||
}
|
||||
|
||||
/// Returns the first activateable window in this window's hierarchy.
|
||||
BaseWindowController getFirstActivatableChild() {
|
||||
// If there are no children, this window is the first activateable window.
|
||||
if (_children.isEmpty) {
|
||||
return this as BaseWindowController;
|
||||
}
|
||||
|
||||
// Otherwise, traverse the children to find the first activateable window.
|
||||
//
|
||||
// Regular windows are only activated if there are no dialog children.
|
||||
// Dialog windows are activated before regular windows.
|
||||
// Tooltips cannot be activated, so they are skipped.
|
||||
var activateable = this as BaseWindowController;
|
||||
for (final BaseWindowController child in _children) {
|
||||
switch (child) {
|
||||
case final RegularWindowController regularChild:
|
||||
activateable = (regularChild as _TestRegularWindowController).getFirstActivatableChild();
|
||||
case final DialogWindowController dialogChild:
|
||||
return (dialogChild as _TestDialogWindowController).getFirstActivatableChild();
|
||||
case final TooltipWindowController _:
|
||||
// Tooltips cannot be activated.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return activateable;
|
||||
}
|
||||
}
|
||||
|
||||
class _TestRegularWindowController extends RegularWindowController with _ChildWindowHierarchyMixin {
|
||||
_TestRegularWindowController({
|
||||
required RegularWindowControllerDelegate delegate,
|
||||
required TestPlatformDispatcher platformDispatcher,
|
||||
required this.windowingOwner,
|
||||
Size? preferredSize,
|
||||
BoxConstraints? preferredConstraints,
|
||||
String? title,
|
||||
}) : _delegate = delegate,
|
||||
_size = preferredSize ?? const Size(800, 600),
|
||||
_constraints = preferredConstraints ?? BoxConstraints.loose(const Size(1920, 1080)),
|
||||
_title = title ?? 'Test Window',
|
||||
super.empty() {
|
||||
_constrainToBounds();
|
||||
rootView = _TestFlutterView(controller: this, platformDispatcher: platformDispatcher);
|
||||
|
||||
// Automatically activate the window when created.
|
||||
activate();
|
||||
}
|
||||
|
||||
final RegularWindowControllerDelegate _delegate;
|
||||
final _TestWindowingOwner windowingOwner;
|
||||
Size _size;
|
||||
BoxConstraints _constraints;
|
||||
String _title;
|
||||
bool _isMaximized = false;
|
||||
bool _isMinimized = false;
|
||||
bool _isFullscreen = false;
|
||||
|
||||
@override
|
||||
Size get contentSize => isFullscreen || isMaximized ? rootView.display.size : _size;
|
||||
|
||||
@override
|
||||
String get title => _title;
|
||||
|
||||
@override
|
||||
bool get isActivated => windowingOwner.isWindowControllerActive(this);
|
||||
|
||||
@override
|
||||
bool get isMaximized => _isMaximized;
|
||||
|
||||
@override
|
||||
bool get isMinimized => _isMinimized;
|
||||
|
||||
@override
|
||||
bool get isFullscreen => _isFullscreen;
|
||||
|
||||
@override
|
||||
void setSize(Size size) {
|
||||
_size = size;
|
||||
_constrainToBounds();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void setConstraints(BoxConstraints constraints) {
|
||||
_constraints = constraints;
|
||||
_constrainToBounds();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void setTitle(String title) {
|
||||
_title = title;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void activate() {
|
||||
final BaseWindowController activated = windowingOwner.activateWindowController(this);
|
||||
activated.notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void setMaximized(bool maximized) {
|
||||
_isMaximized = maximized;
|
||||
if (_isMaximized) {
|
||||
_isMinimized = false;
|
||||
_isFullscreen = false;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void setMinimized(bool minimized) {
|
||||
_isMinimized = minimized;
|
||||
if (_isMinimized) {
|
||||
windowingOwner.deactivateWindowController(this);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void setFullscreen(bool fullscreen, {ui.Display? display}) {
|
||||
_isFullscreen = fullscreen;
|
||||
if (_isFullscreen) {
|
||||
_isMaximized = false;
|
||||
_isMinimized = false;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _constrainToBounds() {
|
||||
final double width = _constraints.constrainWidth(_size.width);
|
||||
final double height = _constraints.constrainHeight(_size.height);
|
||||
_size = Size(width, height);
|
||||
}
|
||||
|
||||
@override
|
||||
void destroy() {
|
||||
_delegate.onWindowDestroyed();
|
||||
removeAllChildren();
|
||||
windowingOwner.deactivateWindowController(this);
|
||||
}
|
||||
}
|
||||
|
||||
class _TestDialogWindowController extends DialogWindowController with _ChildWindowHierarchyMixin {
|
||||
_TestDialogWindowController({
|
||||
required DialogWindowControllerDelegate delegate,
|
||||
required TestPlatformDispatcher platformDispatcher,
|
||||
required this.windowingOwner,
|
||||
BaseWindowController? parent,
|
||||
Size? preferredSize,
|
||||
BoxConstraints? preferredConstraints,
|
||||
String? title,
|
||||
}) : _delegate = delegate,
|
||||
_parent = parent,
|
||||
_size = preferredSize ?? const Size(800, 600),
|
||||
_constraints = preferredConstraints ?? BoxConstraints.loose(const Size(1920, 1080)),
|
||||
_title = title ?? 'Test Window',
|
||||
super.empty() {
|
||||
_constrainToBounds();
|
||||
rootView = _TestFlutterView(controller: this, platformDispatcher: platformDispatcher);
|
||||
|
||||
if (parent != null) {
|
||||
switch (parent) {
|
||||
case final _TestDialogWindowController testParent:
|
||||
testParent.addChild(this);
|
||||
case final _TestRegularWindowController testParent:
|
||||
testParent.addChild(this);
|
||||
default:
|
||||
fail('Unknown window controller type: ${parent.runtimeType}');
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically activate the window when created.
|
||||
activate();
|
||||
}
|
||||
|
||||
final DialogWindowControllerDelegate _delegate;
|
||||
final BaseWindowController? _parent;
|
||||
final _TestWindowingOwner windowingOwner;
|
||||
Size _size;
|
||||
BoxConstraints _constraints;
|
||||
String _title;
|
||||
bool _isMinimized = false;
|
||||
|
||||
@override
|
||||
Size get contentSize => _size;
|
||||
|
||||
@override
|
||||
BaseWindowController? get parent => _parent;
|
||||
|
||||
@override
|
||||
String get title => _title;
|
||||
|
||||
@override
|
||||
bool get isActivated => windowingOwner.isWindowControllerActive(this);
|
||||
|
||||
@override
|
||||
bool get isMinimized => _isMinimized;
|
||||
|
||||
@override
|
||||
void setSize(Size size) {
|
||||
_size = size;
|
||||
_constrainToBounds();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void setConstraints(BoxConstraints constraints) {
|
||||
_constraints = constraints;
|
||||
_constrainToBounds();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void setTitle(String title) {
|
||||
_title = title;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void activate() {
|
||||
final BaseWindowController activated = windowingOwner.activateWindowController(this);
|
||||
activated.notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void setMinimized(bool minimized) {
|
||||
if (_parent != null && minimized) {
|
||||
fail('Cannot minimize a modal dialog window.');
|
||||
}
|
||||
|
||||
_isMinimized = minimized;
|
||||
if (_isMinimized) {
|
||||
windowingOwner.deactivateWindowController(this);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _constrainToBounds() {
|
||||
final double width = _constraints.constrainWidth(_size.width);
|
||||
final double height = _constraints.constrainHeight(_size.height);
|
||||
_size = Size(width, height);
|
||||
}
|
||||
|
||||
@override
|
||||
void destroy() {
|
||||
_delegate.onWindowDestroyed();
|
||||
removeAllChildren();
|
||||
windowingOwner.deactivateWindowController(this);
|
||||
|
||||
if (_parent != null) {
|
||||
switch (_parent) {
|
||||
case final RegularWindowController regularParent:
|
||||
(regularParent as _TestRegularWindowController).removeChild(this);
|
||||
case final DialogWindowController dialogParent:
|
||||
(dialogParent as _TestDialogWindowController).removeChild(this);
|
||||
case TooltipWindowController _:
|
||||
fail('TooltipWindowController cannot be a parent of DialogWindowController.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [WindowingOwner] used for tests.
|
||||
///
|
||||
/// This windowing owner will behave as a perfect windowing system, with no
|
||||
/// delays or failures.
|
||||
///
|
||||
/// See also:
|
||||
/// * [TestWidgetsFlutterBinding], which uses this class to create window controllers
|
||||
/// for tests.
|
||||
/// * [WindowingOwner], the base class.
|
||||
class _TestWindowingOwner extends WindowingOwner {
|
||||
_TestWindowingOwner({required TestPlatformDispatcher platformDispatcher})
|
||||
: _platformDispatcher = platformDispatcher;
|
||||
|
||||
final TestPlatformDispatcher _platformDispatcher;
|
||||
BaseWindowController? _activeWindowController;
|
||||
|
||||
/// Activates the given [controller].
|
||||
///
|
||||
/// If the controller has children, the first activateable window in its hierarchy
|
||||
/// will be activated.
|
||||
///
|
||||
/// Tooltips cannot be activated, so if a [TooltipWindowController] is passed in,
|
||||
/// this method will throw an error.
|
||||
///
|
||||
/// Returns the activated [BaseWindowController].
|
||||
BaseWindowController activateWindowController(BaseWindowController controller) {
|
||||
switch (controller) {
|
||||
case final RegularWindowController regularController:
|
||||
final BaseWindowController leaf = (regularController as _TestRegularWindowController)
|
||||
.getFirstActivatableChild();
|
||||
_activeWindowController = leaf;
|
||||
return _activeWindowController!;
|
||||
case final DialogWindowController dialogController:
|
||||
final BaseWindowController leaf = (dialogController as _TestDialogWindowController)
|
||||
.getFirstActivatableChild();
|
||||
_activeWindowController = leaf;
|
||||
return _activeWindowController!;
|
||||
case final TooltipWindowController _:
|
||||
fail('Tooltips cannot be activated. Activate the parent window instead.');
|
||||
}
|
||||
}
|
||||
|
||||
/// Deactivates the given [controller] if it is currently active.
|
||||
///
|
||||
/// If the controller is not currently active, this method does nothing.
|
||||
///
|
||||
/// If the controller is a [DialogWindowController] with a parent, the parent
|
||||
/// will be activated upon deactivation of the dialog.
|
||||
///
|
||||
/// If the controller is a [TooltipWindowController], this method will throw
|
||||
/// an error, as tooltips cannot be deactivated because they cannot be activated.
|
||||
void deactivateWindowController(BaseWindowController controller) {
|
||||
if (_activeWindowController == controller) {
|
||||
switch (controller) {
|
||||
case final RegularWindowController _:
|
||||
_activeWindowController = null;
|
||||
case final DialogWindowController dialogController:
|
||||
if (dialogController.parent == null) {
|
||||
_activeWindowController = null;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (dialogController.parent!) {
|
||||
case final RegularWindowController regularParent:
|
||||
regularParent.activate();
|
||||
case final DialogWindowController dialogParent:
|
||||
dialogParent.activate();
|
||||
case final TooltipWindowController _:
|
||||
fail('TooltipWindowController cannot be a parent of DialogWindowController.');
|
||||
}
|
||||
case final TooltipWindowController tooltipController:
|
||||
fail(
|
||||
'Tooltips cannot be deactivated. Deactivate the parent window instead: '
|
||||
'${tooltipController.parent}.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the given [controller] is the currently active window.
|
||||
bool isWindowControllerActive(BaseWindowController controller) {
|
||||
return _activeWindowController == controller;
|
||||
}
|
||||
|
||||
@internal
|
||||
@override
|
||||
RegularWindowController createRegularWindowController({
|
||||
required RegularWindowControllerDelegate delegate,
|
||||
Size? preferredSize,
|
||||
BoxConstraints? preferredConstraints,
|
||||
String? title,
|
||||
}) {
|
||||
return _TestRegularWindowController(
|
||||
delegate: delegate,
|
||||
platformDispatcher: _platformDispatcher,
|
||||
windowingOwner: this,
|
||||
preferredSize: preferredSize,
|
||||
preferredConstraints: preferredConstraints,
|
||||
title: title,
|
||||
);
|
||||
}
|
||||
|
||||
@internal
|
||||
@override
|
||||
DialogWindowController createDialogWindowController({
|
||||
required DialogWindowControllerDelegate delegate,
|
||||
Size? preferredSize,
|
||||
BoxConstraints? preferredConstraints,
|
||||
BaseWindowController? parent,
|
||||
String? title,
|
||||
}) {
|
||||
return _TestDialogWindowController(
|
||||
delegate: delegate,
|
||||
platformDispatcher: _platformDispatcher,
|
||||
windowingOwner: this,
|
||||
parent: parent,
|
||||
preferredSize: preferredSize,
|
||||
preferredConstraints: preferredConstraints,
|
||||
title: title,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TooltipWindowController createTooltipWindowController({
|
||||
required TooltipWindowControllerDelegate delegate,
|
||||
required BoxConstraints preferredConstraints,
|
||||
required ui.Rect anchorRect,
|
||||
required WindowPositioner positioner,
|
||||
required BaseWindowController parent,
|
||||
}) {
|
||||
// TODO(mattkae): implement createTooltipWindowController
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
// Examples can assume:
|
||||
// late TestWidgetsFlutterBinding binding;
|
||||
// late Size someSize;
|
||||
@ -332,6 +814,20 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
||||
@protected
|
||||
bool get registerTestTextInput => true;
|
||||
|
||||
/// Determines whether the binding automatically registers [windowingOwner] to
|
||||
/// the fake windowing owner implementation.
|
||||
///
|
||||
/// Unit tests make use of this to mock out windowing system communication for
|
||||
/// widgets. An integration test would set this to false, to test real windowing
|
||||
/// system input.
|
||||
///
|
||||
/// This property should not change the value it returns during the lifetime
|
||||
/// of the binding. Changing the value of this property risks very confusing
|
||||
/// behavior as the [WindowingOwner] may be inconsistently registered or
|
||||
/// unregistered.
|
||||
@protected
|
||||
bool get registerTestWindowingOwner => true;
|
||||
|
||||
/// Delay for `duration` of time.
|
||||
///
|
||||
/// In the automated test environment ([AutomatedTestWidgetsFlutterBinding],
|
||||
@ -400,6 +896,10 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
||||
binding.setupHttpOverrides();
|
||||
}
|
||||
_testTextInput = TestTextInput(onCleared: _resetFocusedEditable);
|
||||
|
||||
if (isWindowingEnabled && registerTestWindowingOwner) {
|
||||
windowingOwner = _TestWindowingOwner(platformDispatcher: platformDispatcher);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -874,6 +874,20 @@ class TestPlatformDispatcher implements PlatformDispatcher {
|
||||
extraViewKeys.forEach(_testViews.remove);
|
||||
}
|
||||
|
||||
/// Adds a [TestFlutterView] that wraps the given [view] to the list of views
|
||||
/// managed by this [TestPlatformDispatcher].
|
||||
///
|
||||
/// The added view will be associated with the first display in the list of
|
||||
/// displays managed by this [TestPlatformDispatcher].
|
||||
void addTestView(FlutterView view) {
|
||||
_testViews[view.viewId] = TestFlutterView(
|
||||
view: view,
|
||||
platformDispatcher: this,
|
||||
display: displays.first,
|
||||
);
|
||||
_updateViewsAndDisplays();
|
||||
}
|
||||
|
||||
@override
|
||||
ErrorCallback? get onError => _platformDispatcher.onError;
|
||||
@override
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user