
Reverts flutter/flutter#142339 In the original change one of the tests included the same view twice which resulted in a different failure than the expected one. The second commit contains the fix for this. I don't understand how this wasn't caught presubmit on CI.
551 lines
18 KiB
Dart
551 lines
18 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 'multi_view_testing.dart';
|
|
|
|
void main() {
|
|
testWidgets('Widgets running with runApp can find View', (WidgetTester tester) async {
|
|
FlutterView? viewOf;
|
|
FlutterView? viewMaybeOf;
|
|
|
|
runApp(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
viewOf = View.of(context);
|
|
viewMaybeOf = View.maybeOf(context);
|
|
return Container();
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(viewOf, isNotNull);
|
|
expect(viewOf, isA<FlutterView>());
|
|
expect(viewMaybeOf, isNotNull);
|
|
expect(viewMaybeOf, isA<FlutterView>());
|
|
});
|
|
|
|
testWidgets('Widgets running with pumpWidget can find View', (WidgetTester tester) async {
|
|
FlutterView? view;
|
|
FlutterView? viewMaybeOf;
|
|
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
view = View.of(context);
|
|
viewMaybeOf = View.maybeOf(context);
|
|
return Container();
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(view, isNotNull);
|
|
expect(view, isA<FlutterView>());
|
|
expect(viewMaybeOf, isNotNull);
|
|
expect(viewMaybeOf, isA<FlutterView>());
|
|
});
|
|
|
|
testWidgets('cannot find View behind a LookupBoundary', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
LookupBoundary(
|
|
child: Container(),
|
|
),
|
|
);
|
|
|
|
final BuildContext context = tester.element(find.byType(Container));
|
|
|
|
expect(View.maybeOf(context), isNull);
|
|
expect(
|
|
() => View.of(context),
|
|
throwsA(isA<FlutterError>().having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
contains('The context provided to View.of() does have a View widget ancestor, but it is hidden by a LookupBoundary.'),
|
|
)),
|
|
);
|
|
});
|
|
|
|
testWidgets('child of view finds view, parentPipelineOwner, mediaQuery', (WidgetTester tester) async {
|
|
FlutterView? outsideView;
|
|
FlutterView? insideView;
|
|
PipelineOwner? outsideParent;
|
|
PipelineOwner? insideParent;
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
outsideView = View.maybeOf(context);
|
|
outsideParent = View.pipelineOwnerOf(context);
|
|
return View(
|
|
view: tester.view,
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
insideView = View.maybeOf(context);
|
|
insideParent = View.pipelineOwnerOf(context);
|
|
return const SizedBox();
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
expect(outsideView, isNull);
|
|
expect(insideView, equals(tester.view));
|
|
|
|
expect(outsideParent, isNotNull);
|
|
expect(insideParent, isNotNull);
|
|
expect(outsideParent, isNot(equals(insideParent)));
|
|
|
|
expect(outsideParent, tester.binding.rootPipelineOwner);
|
|
expect(insideParent, equals(tester.renderObject(find.byType(SizedBox)).owner));
|
|
|
|
final List<PipelineOwner> pipelineOwners = <PipelineOwner> [];
|
|
tester.binding.rootPipelineOwner.visitChildren((PipelineOwner child) {
|
|
pipelineOwners.add(child);
|
|
});
|
|
expect(pipelineOwners.single, equals(insideParent));
|
|
});
|
|
|
|
testWidgets('cannot have multiple views with same FlutterView', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
view: tester.view,
|
|
child: const SizedBox(),
|
|
),
|
|
View(
|
|
view: tester.view,
|
|
child: const SizedBox(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isFlutterError.having(
|
|
(FlutterError e) => e.message,
|
|
'message',
|
|
contains('Multiple widgets used the same GlobalKey'),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('ViewCollection may start with zero views', (WidgetTester tester) async {
|
|
expect(() => const ViewCollection(views: <Widget>[]), returnsNormally);
|
|
});
|
|
|
|
testWidgets('ViewAnchor.child does not see surrounding view', (WidgetTester tester) async {
|
|
FlutterView? inside;
|
|
FlutterView? outside;
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
outside = View.maybeOf(context);
|
|
return ViewAnchor(
|
|
view: Builder(
|
|
builder: (BuildContext context) {
|
|
inside = View.maybeOf(context);
|
|
return View(view: FakeView(tester.view), child: const SizedBox());
|
|
},
|
|
),
|
|
child: const SizedBox(),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
expect(inside, isNull);
|
|
expect(outside, isNotNull);
|
|
});
|
|
|
|
testWidgets('ViewAnchor layout order', (WidgetTester tester) async {
|
|
Finder findSpyWidget(int label) {
|
|
return find.byWidgetPredicate((Widget w) => w is SpyRenderWidget && w.label == label);
|
|
}
|
|
|
|
final List<String> log = <String>[];
|
|
await tester.pumpWidget(
|
|
SpyRenderWidget(
|
|
label: 1,
|
|
log: log,
|
|
child: ViewAnchor(
|
|
view: View(
|
|
view: FakeView(tester.view),
|
|
child: SpyRenderWidget(label: 2, log: log),
|
|
),
|
|
child: SpyRenderWidget(label: 3, log: log),
|
|
),
|
|
),
|
|
);
|
|
log.clear();
|
|
tester.renderObject(findSpyWidget(3)).markNeedsLayout();
|
|
tester.renderObject(findSpyWidget(2)).markNeedsLayout();
|
|
tester.renderObject(findSpyWidget(1)).markNeedsLayout();
|
|
await tester.pump();
|
|
expect(log, <String>['layout 1', 'layout 3', 'layout 2']);
|
|
});
|
|
|
|
testWidgets('visitChildren of ViewAnchor visits both children', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
ViewAnchor(
|
|
view: View(
|
|
view: FakeView(tester.view),
|
|
child: const ColoredBox(color: Colors.green),
|
|
),
|
|
child: const SizedBox(),
|
|
),
|
|
);
|
|
final Element viewAnchorElement = tester.element(find.byElementPredicate((Element e) => e.runtimeType.toString() == '_MultiChildComponentElement'));
|
|
final List<Element> children = <Element>[];
|
|
viewAnchorElement.visitChildren((Element element) {
|
|
children.add(element);
|
|
});
|
|
expect(children, hasLength(2));
|
|
|
|
await tester.pumpWidget(
|
|
const ViewAnchor(
|
|
child: SizedBox(),
|
|
),
|
|
);
|
|
children.clear();
|
|
viewAnchorElement.visitChildren((Element element) {
|
|
children.add(element);
|
|
});
|
|
expect(children, hasLength(1));
|
|
});
|
|
|
|
testWidgets('visitChildren of ViewCollection visits all children', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
view: tester.view,
|
|
child: const SizedBox(),
|
|
),
|
|
View(
|
|
view: FakeView(tester.view),
|
|
child: const SizedBox(),
|
|
),
|
|
View(
|
|
view: FakeView(tester.view, viewId: 423),
|
|
child: const SizedBox(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
final Element viewAnchorElement = tester.element(find.byElementPredicate((Element e) => e.runtimeType.toString() == '_MultiChildComponentElement'));
|
|
final List<Element> children = <Element>[];
|
|
viewAnchorElement.visitChildren((Element element) {
|
|
children.add(element);
|
|
});
|
|
expect(children, hasLength(3));
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
view: tester.view,
|
|
child: const SizedBox(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
children.clear();
|
|
viewAnchorElement.visitChildren((Element element) {
|
|
children.add(element);
|
|
});
|
|
expect(children, hasLength(1));
|
|
});
|
|
|
|
group('renderObject getter', () {
|
|
testWidgets('ancestors of view see RenderView as renderObject', (WidgetTester tester) async {
|
|
late BuildContext builderContext;
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
builderContext = context;
|
|
return View(
|
|
view: tester.view,
|
|
child: const SizedBox(),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
final RenderObject? renderObject = builderContext.findRenderObject();
|
|
expect(renderObject, isNotNull);
|
|
expect(renderObject, isA<RenderView>());
|
|
expect(renderObject, tester.renderObject(find.byType(View)));
|
|
expect(tester.element(find.byType(Builder)).renderObject, renderObject);
|
|
});
|
|
|
|
testWidgets('ancestors of ViewCollection get null for renderObject', (WidgetTester tester) async {
|
|
late BuildContext builderContext;
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
builderContext = context;
|
|
return ViewCollection(
|
|
views: <Widget>[
|
|
View(
|
|
view: tester.view,
|
|
child: const SizedBox(),
|
|
),
|
|
View(
|
|
view: FakeView(tester.view),
|
|
child: const SizedBox(),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
final RenderObject? renderObject = builderContext.findRenderObject();
|
|
expect(renderObject, isNull);
|
|
expect(tester.element(find.byType(Builder)).renderObject, isNull);
|
|
});
|
|
|
|
testWidgets('ancestors of a ViewAnchor see the right RenderObject', (WidgetTester tester) async {
|
|
late BuildContext builderContext;
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
builderContext = context;
|
|
return ViewAnchor(
|
|
view: View(
|
|
view: FakeView(tester.view),
|
|
child: const ColoredBox(color: Colors.green),
|
|
),
|
|
child: const SizedBox(),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
final RenderObject? renderObject = builderContext.findRenderObject();
|
|
expect(renderObject, isNotNull);
|
|
expect(renderObject, isA<RenderConstrainedBox>());
|
|
expect(renderObject, tester.renderObject(find.byType(SizedBox)));
|
|
expect(tester.element(find.byType(Builder)).renderObject, renderObject);
|
|
});
|
|
});
|
|
|
|
testWidgets('correctly switches between view configurations', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
View(
|
|
view: tester.view,
|
|
deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: tester.binding.pipelineOwner,
|
|
deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: tester.binding.renderView,
|
|
child: const SizedBox(),
|
|
),
|
|
);
|
|
RenderObject renderView = tester.renderObject(find.byType(View));
|
|
expect(renderView, same(tester.binding.renderView));
|
|
expect(renderView.owner, same(tester.binding.pipelineOwner));
|
|
expect(tester.renderObject(find.byType(SizedBox)).owner, same(tester.binding.pipelineOwner));
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
View(
|
|
view: tester.view,
|
|
child: const SizedBox(),
|
|
),
|
|
);
|
|
renderView = tester.renderObject(find.byType(View));
|
|
expect(renderView, isNot(same(tester.binding.renderView)));
|
|
expect(renderView.owner, isNot(same(tester.binding.pipelineOwner)));
|
|
expect(tester.renderObject(find.byType(SizedBox)).owner, isNot(same(tester.binding.pipelineOwner)));
|
|
|
|
await tester.pumpWidget(
|
|
wrapWithView: false,
|
|
View(
|
|
view: tester.view,
|
|
deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: tester.binding.pipelineOwner,
|
|
deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: tester.binding.renderView,
|
|
child: const SizedBox(),
|
|
),
|
|
);
|
|
renderView = tester.renderObject(find.byType(View));
|
|
expect(renderView, same(tester.binding.renderView));
|
|
expect(renderView.owner, same(tester.binding.pipelineOwner));
|
|
expect(tester.renderObject(find.byType(SizedBox)).owner, same(tester.binding.pipelineOwner));
|
|
|
|
expect(() => View(
|
|
view: tester.view,
|
|
deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: tester.binding.pipelineOwner,
|
|
child: const SizedBox(),
|
|
), throwsAssertionError);
|
|
expect(() => View(
|
|
view: tester.view,
|
|
deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: tester.binding.renderView,
|
|
child: const SizedBox(),
|
|
), throwsAssertionError);
|
|
expect(() => View(
|
|
view: FakeView(tester.view),
|
|
deprecatedDoNotUseWillBeRemovedWithoutNoticeRenderView: tester.binding.renderView,
|
|
deprecatedDoNotUseWillBeRemovedWithoutNoticePipelineOwner: tester.binding.pipelineOwner,
|
|
child: const SizedBox(),
|
|
), throwsAssertionError);
|
|
});
|
|
|
|
testWidgets('attaches itself correctly', (WidgetTester tester) async {
|
|
final Key viewKey = UniqueKey();
|
|
late final PipelineOwner parentPipelineOwner;
|
|
await tester.pumpWidget(
|
|
ViewAnchor(
|
|
view: Builder(
|
|
builder: (BuildContext context) {
|
|
parentPipelineOwner = View.pipelineOwnerOf(context);
|
|
return View(
|
|
key: viewKey,
|
|
view: FakeView(tester.view),
|
|
child: const SizedBox(),
|
|
);
|
|
},
|
|
),
|
|
child: const ColoredBox(color: Colors.green),
|
|
),
|
|
);
|
|
|
|
expect(parentPipelineOwner, isNot(RendererBinding.instance.rootPipelineOwner));
|
|
|
|
final RenderView rawView = tester.renderObject<RenderView>(find.byKey(viewKey));
|
|
expect(RendererBinding.instance.renderViews, contains(rawView));
|
|
|
|
final List<PipelineOwner> children = <PipelineOwner>[];
|
|
parentPipelineOwner.visitChildren((PipelineOwner child) {
|
|
children.add(child);
|
|
});
|
|
final PipelineOwner rawViewOwner = rawView.owner!;
|
|
expect(children, contains(rawViewOwner));
|
|
|
|
// Remove that View from the tree.
|
|
await tester.pumpWidget(
|
|
const ViewAnchor(
|
|
child: ColoredBox(color: Colors.green),
|
|
),
|
|
);
|
|
|
|
expect(rawView.owner, isNull);
|
|
expect(RendererBinding.instance.renderViews, isNot(contains(rawView)));
|
|
children.clear();
|
|
parentPipelineOwner.visitChildren((PipelineOwner child) {
|
|
children.add(child);
|
|
});
|
|
expect(children, isNot(contains(rawViewOwner)));
|
|
});
|
|
|
|
testWidgets('RenderView does not use size of child if constraints are tight', (WidgetTester tester) async {
|
|
const Size physicalSize = Size(300, 600);
|
|
final Size logicalSize = physicalSize / tester.view.devicePixelRatio;
|
|
tester.view.physicalConstraints = ViewConstraints.tight(physicalSize);
|
|
await tester.pumpWidget(const Placeholder());
|
|
|
|
final RenderView renderView = tester.renderObject<RenderView>(find.byType(View));
|
|
expect(renderView.constraints, BoxConstraints.tight(logicalSize));
|
|
expect(renderView.size, logicalSize);
|
|
|
|
final RenderBox child = renderView.child!;
|
|
expect(child.constraints, BoxConstraints.tight(logicalSize));
|
|
expect(child.debugCanParentUseSize, isFalse);
|
|
expect(child.size, logicalSize);
|
|
});
|
|
|
|
testWidgets('RenderView sizes itself to child if constraints allow it (unconstrained)', (WidgetTester tester) async {
|
|
const Size size = Size(300, 600);
|
|
tester.view.physicalConstraints = const ViewConstraints(); // unconstrained
|
|
await tester.pumpWidget(SizedBox.fromSize(size: size));
|
|
|
|
final RenderView renderView = tester.renderObject<RenderView>(find.byType(View));
|
|
expect(renderView.constraints, const BoxConstraints());
|
|
expect(renderView.size, size);
|
|
|
|
final RenderBox child = renderView.child!;
|
|
expect(child.constraints, const BoxConstraints());
|
|
expect(child.debugCanParentUseSize, isTrue);
|
|
expect(child.size, size);
|
|
});
|
|
|
|
testWidgets('RenderView sizes itself to child if constraints allow it (constrained)', (WidgetTester tester) async {
|
|
const Size size = Size(30, 60);
|
|
const ViewConstraints viewConstraints = ViewConstraints(maxWidth: 333, maxHeight: 666);
|
|
final BoxConstraints boxConstraints = BoxConstraints.fromViewConstraints(viewConstraints / tester.view.devicePixelRatio);
|
|
tester.view.physicalConstraints = viewConstraints;
|
|
await tester.pumpWidget(SizedBox.fromSize(size: size));
|
|
|
|
final RenderView renderView = tester.renderObject<RenderView>(find.byType(View));
|
|
expect(renderView.constraints, boxConstraints);
|
|
expect(renderView.size, size);
|
|
|
|
final RenderBox child = renderView.child!;
|
|
expect(child.constraints, boxConstraints);
|
|
expect(child.debugCanParentUseSize, isTrue);
|
|
expect(child.size, size);
|
|
});
|
|
|
|
testWidgets('RenderView respects constraints when child wants to be bigger than allowed', (WidgetTester tester) async {
|
|
const Size size = Size(3000, 6000);
|
|
const ViewConstraints viewConstraints = ViewConstraints(maxWidth: 300, maxHeight: 600);
|
|
tester.view.physicalConstraints = viewConstraints;
|
|
await tester.pumpWidget(SizedBox.fromSize(size: size));
|
|
|
|
final RenderView renderView = tester.renderObject<RenderView>(find.byType(View));
|
|
expect(renderView.size, const Size(100, 200)); // viewConstraints.biggest / devicePixelRatio
|
|
|
|
final RenderBox child = renderView.child!;
|
|
expect(child.debugCanParentUseSize, isTrue);
|
|
expect(child.size, const Size(100, 200));
|
|
});
|
|
}
|
|
|
|
class SpyRenderWidget extends SizedBox {
|
|
const SpyRenderWidget({super.key, required this.label, required this.log, super.child});
|
|
|
|
final int label;
|
|
final List<String> log;
|
|
|
|
@override
|
|
RenderSpy createRenderObject(BuildContext context) {
|
|
return RenderSpy(
|
|
additionalConstraints: const BoxConstraints(),
|
|
label: label,
|
|
log: log,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void updateRenderObject(BuildContext context, RenderSpy renderObject) {
|
|
renderObject
|
|
..label = label
|
|
..log = log;
|
|
}
|
|
}
|
|
|
|
class RenderSpy extends RenderConstrainedBox {
|
|
RenderSpy({required super.additionalConstraints, required this.label, required this.log});
|
|
|
|
int label;
|
|
List<String> log;
|
|
|
|
@override
|
|
void performLayout() {
|
|
log.add('layout $label');
|
|
super.performLayout();
|
|
}
|
|
}
|