mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Bumps the Dart version to 3.8 across the repo (excluding engine/src/flutter/third_party) and applies formatting updates from Dart 3.8. ## 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]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] 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. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
963 lines
29 KiB
Dart
963 lines
29 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:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
|
|
|
import 'multi_view_testing.dart';
|
|
|
|
void main() {
|
|
testWidgets('Providing a RenderObjectWidget directly to the RootWidget fails', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// No render tree exists to attach the RenderObjectWidget to.
|
|
await tester.pumpWidget(wrapWithView: false, const ColoredBox(color: Colors.red));
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
startsWith(
|
|
'The render object for ColoredBox cannot find ancestor render object to attach to.',
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Moving a RenderObjectWidget to the RootWidget via GlobalKey fails', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget globalKeyedWidget = ColoredBox(key: GlobalKey(), color: Colors.red);
|
|
|
|
await tester.pumpWidget(wrapWithView: false, View(view: tester.view, child: globalKeyedWidget));
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await tester.pumpWidget(wrapWithView: false, globalKeyedWidget);
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
contains('cannot find ancestor render object to attach to.'),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'A View cannot be a child of a render object widget',
|
|
experimentalLeakTesting: LeakTesting.settings
|
|
.withIgnoredAll(), // leaking by design because of exception
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Center(
|
|
child: View(view: FakeView(tester.view), child: Container()),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
contains('cannot maintain an independent render tree at its current location.'),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'The child of a ViewAnchor cannot be a View',
|
|
experimentalLeakTesting: LeakTesting.settings
|
|
.withIgnoredAll(), // leaking by design because of exception
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
ViewAnchor(
|
|
child: View(view: FakeView(tester.view), child: Container()),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
contains('cannot maintain an independent render tree at its current location.'),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('A View can not be moved via GlobalKey to be a child of a RenderObject', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget globalKeyedView = View(
|
|
key: GlobalKey(),
|
|
view: FakeView(tester.view),
|
|
child: const ColoredBox(color: Colors.red),
|
|
);
|
|
|
|
await tester.pumpWidget(wrapWithView: false, globalKeyedView);
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await tester.pumpWidget(wrapWithView: false, View(view: tester.view, child: globalKeyedView));
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
contains('cannot maintain an independent render tree at its current location.'),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('The view property of a ViewAnchor cannot be a render object widget', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
ViewAnchor(
|
|
view: const ColoredBox(color: Colors.red),
|
|
child: Container(),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
startsWith(
|
|
'The render object for ColoredBox cannot find ancestor render object to attach to.',
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'A RenderObject cannot be moved into the view property of a ViewAnchor via GlobalKey',
|
|
(WidgetTester tester) async {
|
|
final Widget globalKeyedWidget = ColoredBox(key: GlobalKey(), color: Colors.red);
|
|
|
|
await tester.pumpWidget(ViewAnchor(child: globalKeyedWidget));
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await tester.pumpWidget(ViewAnchor(view: globalKeyedWidget, child: const SizedBox()));
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
contains('cannot find ancestor render object to attach to.'),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('ViewAnchor cannot be used at the top of the widget tree (outside of View)', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(wrapWithView: false, const ViewAnchor(child: SizedBox()));
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
startsWith(
|
|
'The render object for SizedBox cannot find ancestor render object to attach to.',
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'ViewAnchor cannot be moved to the top of the widget tree (outside of View) via GlobalKey',
|
|
(WidgetTester tester) async {
|
|
final Widget globalKeyedViewAnchor = ViewAnchor(key: GlobalKey(), child: const SizedBox());
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
View(view: tester.view, child: globalKeyedViewAnchor),
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await tester.pumpWidget(wrapWithView: false, globalKeyedViewAnchor);
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
contains('cannot find ancestor render object to attach to.'),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('View can be used at the top of the widget tree', (WidgetTester tester) async {
|
|
await tester.pumpWidget(wrapWithView: false, View(view: tester.view, child: Container()));
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('View can be moved to the top of the widget tree view GlobalKey', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget globalKeyView = View(
|
|
view: FakeView(tester.view),
|
|
child: const ColoredBox(color: Colors.red),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
View(
|
|
view: tester.view,
|
|
child: ViewAnchor(
|
|
view: globalKeyView, // This one has trouble when deactivating
|
|
child: const SizedBox(),
|
|
),
|
|
),
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
expect(find.byType(SizedBox), findsOneWidget);
|
|
expect(find.byType(ColoredBox), findsOneWidget);
|
|
|
|
await tester.pumpWidget(wrapWithView: false, globalKeyView);
|
|
expect(tester.takeException(), isNull);
|
|
expect(find.byType(SizedBox), findsNothing);
|
|
expect(find.byType(ColoredBox), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('ViewCollection can be used at the top of the widget tree', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[View(view: tester.view, child: Container())],
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('ViewCollection cannot be used inside a View', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
ViewCollection(
|
|
views: <Widget>[View(view: FakeView(tester.view), child: Container())],
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
startsWith(
|
|
'The Element for ViewCollection cannot be inserted into slot "null" of its ancestor.',
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('ViewCollection can be used as ViewAnchor.view', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
ViewAnchor(
|
|
view: ViewCollection(
|
|
views: <Widget>[View(view: FakeView(tester.view), child: Container())],
|
|
),
|
|
child: Container(),
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('ViewCollection cannot have render object widgets as children', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
const ViewCollection(views: <Widget>[ColoredBox(color: Colors.red)]),
|
|
);
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
startsWith(
|
|
'The render object for ColoredBox cannot find ancestor render object to attach to.',
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Views can be moved in and out of ViewCollections via GlobalKey', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget greenView = View(
|
|
key: GlobalKey(debugLabel: 'green'),
|
|
view: tester.view,
|
|
child: const ColoredBox(color: Colors.green),
|
|
);
|
|
final Widget redView = View(
|
|
key: GlobalKey(debugLabel: 'red'),
|
|
view: FakeView(tester.view),
|
|
child: const ColoredBox(color: Colors.red),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
greenView,
|
|
ViewCollection(views: <Widget>[redView]),
|
|
],
|
|
),
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
expect(find.byType(ColoredBox), findsNWidgets(2));
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
redView,
|
|
ViewCollection(views: <Widget>[greenView]),
|
|
],
|
|
),
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
expect(find.byType(ColoredBox), findsNWidgets(2));
|
|
});
|
|
|
|
testWidgets('Can move stuff between views via global key: viewA -> viewB', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FlutterView greenView = tester.view;
|
|
final FlutterView redView = FakeView(tester.view);
|
|
final Widget globalKeyChild = SizedBox(key: GlobalKey());
|
|
|
|
Map<int, RenderObject> collectLeafRenderObjects() {
|
|
final Map<int, RenderObject> result = <int, RenderObject>{};
|
|
for (final RenderView renderView in RendererBinding.instance.renderViews) {
|
|
void visit(RenderObject object) {
|
|
result[renderView.flutterView.viewId] = object;
|
|
object.visitChildren(visit);
|
|
}
|
|
|
|
visit(renderView);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
view: greenView,
|
|
child: ColoredBox(color: Colors.green, child: globalKeyChild),
|
|
),
|
|
View(
|
|
view: redView,
|
|
child: const ColoredBox(color: Colors.red),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
|
|
findsNothing,
|
|
);
|
|
final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
|
|
|
|
Map<int, RenderObject> leafRenderObject = collectLeafRenderObjects();
|
|
expect(leafRenderObject[greenView.viewId], isA<RenderConstrainedBox>());
|
|
expect(leafRenderObject[redView.viewId], isNot(isA<RenderConstrainedBox>()));
|
|
|
|
// Move the child.
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
view: greenView,
|
|
child: const ColoredBox(color: Colors.green),
|
|
),
|
|
View(
|
|
view: redView,
|
|
child: ColoredBox(color: Colors.red, child: globalKeyChild),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
|
|
findsNothing,
|
|
);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
|
|
findsOneWidget,
|
|
);
|
|
expect(tester.renderObject(find.byKey(globalKeyChild.key!)), equals(boxWithGlobalKey));
|
|
|
|
leafRenderObject = collectLeafRenderObjects();
|
|
expect(leafRenderObject[greenView.viewId], isNot(isA<RenderConstrainedBox>()));
|
|
expect(leafRenderObject[redView.viewId], isA<RenderConstrainedBox>());
|
|
});
|
|
|
|
testWidgets('Can move stuff between views via global key: viewB -> viewA', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FlutterView greenView = tester.view;
|
|
final FlutterView redView = FakeView(tester.view);
|
|
final Widget globalKeyChild = SizedBox(key: GlobalKey());
|
|
|
|
Map<int, RenderObject> collectLeafRenderObjects() {
|
|
final Map<int, RenderObject> result = <int, RenderObject>{};
|
|
for (final RenderView renderView in RendererBinding.instance.renderViews) {
|
|
void visit(RenderObject object) {
|
|
result[renderView.flutterView.viewId] = object;
|
|
object.visitChildren(visit);
|
|
}
|
|
|
|
visit(renderView);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
view: greenView,
|
|
child: const ColoredBox(color: Colors.green),
|
|
),
|
|
View(
|
|
view: redView,
|
|
child: ColoredBox(color: Colors.red, child: globalKeyChild),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
|
|
findsNothing,
|
|
);
|
|
final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
|
|
|
|
Map<int, RenderObject> leafRenderObject = collectLeafRenderObjects();
|
|
expect(leafRenderObject[redView.viewId], isA<RenderConstrainedBox>());
|
|
expect(leafRenderObject[greenView.viewId], isNot(isA<RenderConstrainedBox>()));
|
|
|
|
// Move the child.
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
view: greenView,
|
|
child: ColoredBox(color: Colors.green, child: globalKeyChild),
|
|
),
|
|
View(
|
|
view: redView,
|
|
child: const ColoredBox(color: Colors.red),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
|
|
findsNothing,
|
|
);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
|
|
findsOneWidget,
|
|
);
|
|
expect(tester.renderObject(find.byKey(globalKeyChild.key!)), equals(boxWithGlobalKey));
|
|
|
|
leafRenderObject = collectLeafRenderObjects();
|
|
expect(leafRenderObject[redView.viewId], isNot(isA<RenderConstrainedBox>()));
|
|
expect(leafRenderObject[greenView.viewId], isA<RenderConstrainedBox>());
|
|
});
|
|
|
|
testWidgets('Can move stuff out of a view that is going away, viewA -> ViewB', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FlutterView greenView = tester.view;
|
|
final Key greenKey = UniqueKey();
|
|
final FlutterView redView = FakeView(tester.view);
|
|
final Key redKey = UniqueKey();
|
|
final Widget globalKeyChild = SizedBox(key: GlobalKey());
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
key: greenKey,
|
|
view: greenView,
|
|
child: const ColoredBox(color: Colors.green),
|
|
),
|
|
View(
|
|
key: redKey,
|
|
view: redView,
|
|
child: ColoredBox(color: Colors.red, child: globalKeyChild),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
|
|
findsNothing,
|
|
);
|
|
final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
|
|
|
|
// Move the child and remove its view.
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
key: greenKey,
|
|
view: greenView,
|
|
child: ColoredBox(color: Colors.green, child: globalKeyChild),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
expect(findsColoredBox(Colors.red), findsNothing);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
|
|
findsOneWidget,
|
|
);
|
|
expect(tester.renderObject(find.byKey(globalKeyChild.key!)), equals(boxWithGlobalKey));
|
|
});
|
|
|
|
testWidgets('Can move stuff out of a view that is going away, viewB -> ViewA', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FlutterView greenView = tester.view;
|
|
final Key greenKey = UniqueKey();
|
|
final FlutterView redView = FakeView(tester.view);
|
|
final Key redKey = UniqueKey();
|
|
final Widget globalKeyChild = SizedBox(key: GlobalKey());
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
key: greenKey,
|
|
view: greenView,
|
|
child: ColoredBox(color: Colors.green, child: globalKeyChild),
|
|
),
|
|
View(
|
|
key: redKey,
|
|
view: redView,
|
|
child: const ColoredBox(color: Colors.red),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.green), matching: find.byType(SizedBox)),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
|
|
findsNothing,
|
|
);
|
|
final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
|
|
|
|
// Move the child and remove its view.
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
key: redKey,
|
|
view: redView,
|
|
child: ColoredBox(color: Colors.red, child: globalKeyChild),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
expect(findsColoredBox(Colors.green), findsNothing);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
|
|
findsOneWidget,
|
|
);
|
|
expect(tester.renderObject(find.byKey(globalKeyChild.key!)), equals(boxWithGlobalKey));
|
|
});
|
|
|
|
testWidgets('Can move stuff out of a view that is moving itself, stuff ends up before view', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Key key1 = UniqueKey();
|
|
final Key key2 = UniqueKey();
|
|
final Key key3 = UniqueKey();
|
|
final Key key4 = UniqueKey();
|
|
|
|
final GlobalKey viewKey = GlobalKey();
|
|
final GlobalKey childKey = GlobalKey();
|
|
|
|
await tester.pumpWidget(
|
|
Column(
|
|
children: <Widget>[
|
|
SizedBox(key: key1),
|
|
ViewAnchor(
|
|
key: key2,
|
|
view: View(
|
|
key: viewKey,
|
|
view: FakeView(tester.view),
|
|
child: SizedBox(
|
|
child: ColoredBox(key: childKey, color: Colors.green),
|
|
),
|
|
),
|
|
child: const SizedBox(),
|
|
),
|
|
ViewAnchor(key: key3, child: const SizedBox()),
|
|
SizedBox(key: key4),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Column(
|
|
children: <Widget>[
|
|
SizedBox(
|
|
key: key1,
|
|
child: ColoredBox(key: childKey, color: Colors.green),
|
|
),
|
|
ViewAnchor(key: key2, child: const SizedBox()),
|
|
ViewAnchor(
|
|
key: key3,
|
|
view: View(key: viewKey, view: FakeView(tester.view), child: const SizedBox()),
|
|
child: const SizedBox(),
|
|
),
|
|
SizedBox(key: key4),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Column(
|
|
children: <Widget>[
|
|
SizedBox(key: key1),
|
|
ViewAnchor(
|
|
key: key2,
|
|
view: View(
|
|
key: viewKey,
|
|
view: FakeView(tester.view),
|
|
child: SizedBox(
|
|
child: ColoredBox(key: childKey, color: Colors.green),
|
|
),
|
|
),
|
|
child: const SizedBox(),
|
|
),
|
|
ViewAnchor(key: key3, child: const SizedBox()),
|
|
SizedBox(key: key4),
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Can move stuff out of a view that is moving itself, stuff ends up after view', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Key key1 = UniqueKey();
|
|
final Key key2 = UniqueKey();
|
|
final Key key3 = UniqueKey();
|
|
final Key key4 = UniqueKey();
|
|
|
|
final GlobalKey viewKey = GlobalKey();
|
|
final GlobalKey childKey = GlobalKey();
|
|
|
|
await tester.pumpWidget(
|
|
Column(
|
|
children: <Widget>[
|
|
SizedBox(key: key1),
|
|
ViewAnchor(
|
|
key: key2,
|
|
view: View(
|
|
key: viewKey,
|
|
view: FakeView(tester.view),
|
|
child: SizedBox(
|
|
child: ColoredBox(key: childKey, color: Colors.green),
|
|
),
|
|
),
|
|
child: const SizedBox(),
|
|
),
|
|
ViewAnchor(key: key3, child: const SizedBox()),
|
|
SizedBox(key: key4),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Column(
|
|
children: <Widget>[
|
|
SizedBox(key: key1),
|
|
ViewAnchor(key: key2, child: const SizedBox()),
|
|
ViewAnchor(
|
|
key: key3,
|
|
view: View(key: viewKey, view: FakeView(tester.view), child: const SizedBox()),
|
|
child: const SizedBox(),
|
|
),
|
|
SizedBox(
|
|
key: key4,
|
|
child: ColoredBox(key: childKey, color: Colors.green),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Column(
|
|
children: <Widget>[
|
|
SizedBox(key: key1),
|
|
ViewAnchor(
|
|
key: key2,
|
|
view: View(
|
|
key: viewKey,
|
|
view: FakeView(tester.view),
|
|
child: SizedBox(
|
|
child: ColoredBox(key: childKey, color: Colors.green),
|
|
),
|
|
),
|
|
child: const SizedBox(),
|
|
),
|
|
ViewAnchor(key: key3, child: const SizedBox()),
|
|
SizedBox(key: key4),
|
|
],
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Can globalkey move down the tree from a view that is going away', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FlutterView anchorView = FakeView(tester.view);
|
|
final Widget globalKeyChild = SizedBox(key: GlobalKey());
|
|
|
|
await tester.pumpWidget(
|
|
ColoredBox(
|
|
color: Colors.green,
|
|
child: ViewAnchor(
|
|
view: View(
|
|
view: anchorView,
|
|
child: ColoredBox(color: Colors.yellow, child: globalKeyChild),
|
|
),
|
|
child: const ColoredBox(color: Colors.red),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(findsColoredBox(Colors.green), findsOneWidget);
|
|
expect(findsColoredBox(Colors.yellow), findsOneWidget);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.yellow), matching: find.byType(SizedBox)),
|
|
findsOneWidget,
|
|
);
|
|
expect(findsColoredBox(Colors.red), findsOneWidget);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
|
|
findsNothing,
|
|
);
|
|
expect(find.byType(SizedBox), findsOneWidget);
|
|
final RenderObject boxWithGlobalKey = tester.renderObject(find.byKey(globalKeyChild.key!));
|
|
|
|
await tester.pumpWidget(
|
|
ColoredBox(
|
|
color: Colors.green,
|
|
child: ViewAnchor(
|
|
child: ColoredBox(color: Colors.red, child: globalKeyChild),
|
|
),
|
|
),
|
|
);
|
|
expect(findsColoredBox(Colors.green), findsOneWidget);
|
|
expect(findsColoredBox(Colors.yellow), findsNothing);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.yellow), matching: find.byType(SizedBox)),
|
|
findsNothing,
|
|
);
|
|
expect(findsColoredBox(Colors.red), findsOneWidget);
|
|
expect(
|
|
find.descendant(of: findsColoredBox(Colors.red), matching: find.byType(SizedBox)),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.byType(SizedBox), findsOneWidget);
|
|
expect(tester.renderObject(find.byKey(globalKeyChild.key!)), boxWithGlobalKey);
|
|
});
|
|
|
|
testWidgets('RenderObjects are disposed when a view goes away from a ViewAnchor', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FlutterView anchorView = FakeView(tester.view);
|
|
|
|
await tester.pumpWidget(
|
|
ColoredBox(
|
|
color: Colors.green,
|
|
child: ViewAnchor(
|
|
view: View(
|
|
view: anchorView,
|
|
child: const ColoredBox(color: Colors.yellow),
|
|
),
|
|
child: const ColoredBox(color: Colors.red),
|
|
),
|
|
),
|
|
);
|
|
|
|
final RenderObject box = tester.renderObject(findsColoredBox(Colors.yellow));
|
|
|
|
await tester.pumpWidget(
|
|
const ColoredBox(
|
|
color: Colors.green,
|
|
child: ViewAnchor(child: ColoredBox(color: Colors.red)),
|
|
),
|
|
);
|
|
|
|
expect(box.debugDisposed, isTrue);
|
|
});
|
|
|
|
testWidgets('RenderObjects are disposed when a view goes away from a ViewCollection', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final FlutterView redView = tester.view;
|
|
final FlutterView greenView = FakeView(tester.view);
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
view: redView,
|
|
child: const ColoredBox(color: Colors.red),
|
|
),
|
|
View(
|
|
view: greenView,
|
|
child: const ColoredBox(color: Colors.green),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
expect(findsColoredBox(Colors.green), findsOneWidget);
|
|
expect(findsColoredBox(Colors.red), findsOneWidget);
|
|
final RenderObject box = tester.renderObject(findsColoredBox(Colors.green));
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
view: redView,
|
|
child: const ColoredBox(color: Colors.red),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
expect(findsColoredBox(Colors.green), findsNothing);
|
|
expect(findsColoredBox(Colors.red), findsOneWidget);
|
|
expect(box.debugDisposed, isTrue);
|
|
});
|
|
|
|
testWidgets('View can be wrapped and unwrapped', (WidgetTester tester) async {
|
|
final Widget view = View(view: tester.view, child: const SizedBox());
|
|
|
|
await tester.pumpWidget(wrapWithView: false, view);
|
|
|
|
final RenderObject renderView = tester.renderObject(find.byType(View));
|
|
final RenderObject renderSizedBox = tester.renderObject(find.byType(SizedBox));
|
|
|
|
await tester.pumpWidget(wrapWithView: false, ViewCollection(views: <Widget>[view]));
|
|
|
|
expect(tester.renderObject(find.byType(View)), same(renderView));
|
|
expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox));
|
|
|
|
await tester.pumpWidget(wrapWithView: false, view);
|
|
|
|
expect(tester.renderObject(find.byType(View)), same(renderView));
|
|
expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox));
|
|
});
|
|
|
|
testWidgets('ViewAnchor with View can be wrapped and unwrapped', (WidgetTester tester) async {
|
|
final Widget viewAnchor = ViewAnchor(
|
|
view: View(view: FakeView(tester.view), child: const SizedBox()),
|
|
child: const ColoredBox(color: Colors.green),
|
|
);
|
|
|
|
await tester.pumpWidget(viewAnchor);
|
|
|
|
final List<RenderObject> renderViews = tester.renderObjectList(find.byType(View)).toList();
|
|
final RenderObject renderSizedBox = tester.renderObject(find.byType(SizedBox));
|
|
|
|
await tester.pumpWidget(ColoredBox(color: Colors.yellow, child: viewAnchor));
|
|
|
|
expect(tester.renderObjectList(find.byType(View)), renderViews);
|
|
expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox));
|
|
|
|
await tester.pumpWidget(viewAnchor);
|
|
|
|
expect(tester.renderObjectList(find.byType(View)), renderViews);
|
|
expect(tester.renderObject(find.byType(SizedBox)), same(renderSizedBox));
|
|
});
|
|
|
|
testWidgets('Moving a View keeps its semantics tree stable', (WidgetTester tester) async {
|
|
final Widget view = View(
|
|
// No explicit key, we rely on the implicit key of the underlying RawView.
|
|
view: tester.view,
|
|
child: Semantics(textDirection: TextDirection.ltr, label: 'Hello', child: const SizedBox()),
|
|
);
|
|
await tester.pumpWidget(wrapWithView: false, view);
|
|
|
|
final RenderObject renderSemantics = tester.renderObject(find.bySemanticsLabel('Hello'));
|
|
final SemanticsNode semantics = tester.getSemantics(find.bySemanticsLabel('Hello'));
|
|
expect(semantics.id, 1);
|
|
expect(renderSemantics.debugSemantics, same(semantics));
|
|
|
|
await tester.pumpWidget(wrapWithView: false, ViewCollection(views: <Widget>[view]));
|
|
|
|
final RenderObject renderSemanticsAfterMove = tester.renderObject(
|
|
find.bySemanticsLabel('Hello'),
|
|
);
|
|
final SemanticsNode semanticsAfterMove = tester.getSemantics(find.bySemanticsLabel('Hello'));
|
|
expect(renderSemanticsAfterMove, same(renderSemantics));
|
|
expect(semanticsAfterMove.id, 1);
|
|
expect(semanticsAfterMove, same(semantics));
|
|
});
|
|
}
|
|
|
|
Finder findsColoredBox(Color color) {
|
|
return find.byWidgetPredicate((Widget widget) => widget is ColoredBox && widget.color == color);
|
|
}
|