mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] 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 `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- 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
375 lines
12 KiB
Dart
375 lines
12 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 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
final controller1 = OverlayPortalController(debugLabel: 'controller1');
|
|
setUp(controller1.show);
|
|
|
|
testWidgets('Basic test', (WidgetTester tester) async {
|
|
late StateSetter setState;
|
|
var transform = Matrix4.identity();
|
|
late final OverlayEntry overlayEntry;
|
|
addTearDown(() {
|
|
overlayEntry
|
|
..remove()
|
|
..dispose();
|
|
});
|
|
|
|
late Matrix4 paintTransform;
|
|
late Size regularChildSize;
|
|
late Rect regularChildRectInTheater;
|
|
late Size theaterSize;
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
overlayEntry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return Positioned(
|
|
left: 10,
|
|
top: 20,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setter) {
|
|
setState = setter;
|
|
return Transform(
|
|
transform: transform,
|
|
// RenderTransform uses size in its applyPaintTransform
|
|
// implementation if alignment is set.
|
|
alignment: Alignment.topLeft,
|
|
child: OverlayPortal.overlayChildLayoutBuilder(
|
|
controller: controller1,
|
|
overlayChildBuilder:
|
|
(BuildContext context, OverlayChildLayoutInfo layoutInfo) {
|
|
paintTransform = layoutInfo.childPaintTransform;
|
|
regularChildSize = layoutInfo.childSize;
|
|
regularChildRectInTheater = MatrixUtils.transformRect(
|
|
paintTransform,
|
|
Offset.zero & layoutInfo.childSize,
|
|
);
|
|
theaterSize = layoutInfo.overlaySize;
|
|
return const SizedBox();
|
|
},
|
|
child: const SizedBox(width: 40, height: 50),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
// Does not schedule a new frame by itself.
|
|
expect(tester.binding.hasScheduledFrame, isFalse);
|
|
expect(paintTransform, Matrix4.translationValues(10.0, 20.0, 0.0));
|
|
expect(regularChildSize, const Size(40, 50));
|
|
expect(theaterSize, const Size(800, 600));
|
|
expect(regularChildRectInTheater, const Offset(10.0, 20.0) & regularChildSize);
|
|
|
|
setState(() => transform = Matrix4.diagonal3Values(2.0, 4.0, 1.0));
|
|
assert(tester.binding.hasScheduledFrame);
|
|
await tester.pump();
|
|
|
|
expect(paintTransform, Matrix4.translationValues(10.0, 20.0, 0.0) * transform);
|
|
expect(regularChildSize, const Size(40, 50));
|
|
expect(theaterSize, const Size(800, 600));
|
|
expect(regularChildRectInTheater, const Offset(10.0, 20.0) & const Size(80.0, 200.0));
|
|
});
|
|
|
|
testWidgets('child changes size', (WidgetTester tester) async {
|
|
late StateSetter setState;
|
|
late final OverlayEntry overlayEntry;
|
|
addTearDown(
|
|
() => overlayEntry
|
|
..remove()
|
|
..dispose(),
|
|
);
|
|
|
|
late Size regularChildSize;
|
|
var childSize = const Size(40, 50);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
overlayEntry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return Positioned(
|
|
left: 10,
|
|
top: 20,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setter) {
|
|
setState = setter;
|
|
return OverlayPortal.overlayChildLayoutBuilder(
|
|
controller: controller1,
|
|
overlayChildBuilder:
|
|
(BuildContext context, OverlayChildLayoutInfo layoutInfo) {
|
|
regularChildSize = layoutInfo.childSize;
|
|
return const SizedBox();
|
|
},
|
|
child: SizedBox.fromSize(size: childSize),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(regularChildSize, childSize);
|
|
|
|
setState(() => childSize = const Size(123.0, 321.0));
|
|
|
|
await tester.pump();
|
|
expect(regularChildSize, childSize);
|
|
});
|
|
|
|
testWidgets('builder callback is called when OverlayPortal rebuilds', (
|
|
WidgetTester tester,
|
|
) async {
|
|
late StateSetter setState;
|
|
var color = const Color(0x12345678);
|
|
late final OverlayEntry overlayEntry;
|
|
addTearDown(
|
|
() => overlayEntry
|
|
..remove()
|
|
..dispose(),
|
|
);
|
|
|
|
Widget builder(BuildContext _, OverlayChildLayoutInfo _) => ColoredBox(color: color);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
overlayEntry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setter) {
|
|
setState = setter;
|
|
return OverlayPortal.overlayChildLayoutBuilder(
|
|
controller: controller1,
|
|
overlayChildBuilder: builder,
|
|
child: const SizedBox(),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(find.byType(ColoredBox), paints..rect(color: color));
|
|
|
|
setState(() => color = const Color(0x87654321));
|
|
await tester.pump();
|
|
expect(find.byType(ColoredBox), paints..rect(color: color));
|
|
});
|
|
|
|
testWidgets('Positioned works in the builder', (WidgetTester tester) async {
|
|
late final OverlayEntry overlayEntry;
|
|
addTearDown(
|
|
() => overlayEntry
|
|
..remove()
|
|
..dispose(),
|
|
);
|
|
|
|
final GlobalKey key = GlobalKey();
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
overlayEntry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return OverlayPortal.overlayChildLayoutBuilder(
|
|
controller: controller1,
|
|
overlayChildBuilder: (_, _) {
|
|
return Positioned(
|
|
left: 123.0,
|
|
top: 37.0,
|
|
width: 12.0,
|
|
height: 23.0,
|
|
child: SizedBox(key: key),
|
|
);
|
|
},
|
|
child: const SizedBox(width: 10.0, height: 20.0),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
final Rect rect = tester.getRect(find.byKey(key));
|
|
expect(rect, const Rect.fromLTWH(123.0, 37.0, 12.0, 23.0));
|
|
});
|
|
|
|
testWidgets('Rebuilds when the layout info changes', (WidgetTester tester) async {
|
|
late StateSetter setState;
|
|
var transform = Matrix4.identity();
|
|
late final OverlayEntry overlayEntry;
|
|
addTearDown(
|
|
() => overlayEntry
|
|
..remove()
|
|
..dispose(),
|
|
);
|
|
|
|
late Matrix4 paintTransform;
|
|
|
|
Widget buildOverlayChild(BuildContext context, OverlayChildLayoutInfo layoutInfo) {
|
|
paintTransform = layoutInfo.childPaintTransform;
|
|
return const SizedBox();
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
overlayEntry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return Positioned(
|
|
left: 10,
|
|
top: 20,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setter) {
|
|
setState = setter;
|
|
return Transform(
|
|
transform: transform,
|
|
child: OverlayPortal.overlayChildLayoutBuilder(
|
|
controller: controller1,
|
|
overlayChildBuilder: buildOverlayChild,
|
|
child: const SizedBox(width: 40, height: 50),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(paintTransform, Matrix4.translationValues(10.0, 20.0, 0.0));
|
|
setState(() => transform = Matrix4.diagonal3Values(2.0, 4.0, 1.0));
|
|
await tester.pump();
|
|
expect(paintTransform, Matrix4.translationValues(10.0, 20.0, 0.0) * transform);
|
|
});
|
|
|
|
testWidgets('Still works if child and overlay child are null', (WidgetTester tester) async {
|
|
late final OverlayEntry overlayEntry;
|
|
addTearDown(
|
|
() => overlayEntry
|
|
..remove()
|
|
..dispose(),
|
|
);
|
|
|
|
late Size regularChildSize;
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
overlayEntry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return Positioned(
|
|
left: 10,
|
|
top: 20,
|
|
child: OverlayPortal.overlayChildLayoutBuilder(
|
|
controller: controller1,
|
|
overlayChildBuilder: (BuildContext context, OverlayChildLayoutInfo layoutInfo) {
|
|
regularChildSize = layoutInfo.childSize;
|
|
return const _NullLeaf();
|
|
},
|
|
child: null,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(regularChildSize, Size.zero);
|
|
});
|
|
|
|
testWidgets('Screams if RenderFollower is spotted in path', (WidgetTester tester) async {
|
|
late final OverlayEntry overlayEntry;
|
|
addTearDown(
|
|
() => overlayEntry
|
|
..remove()
|
|
..dispose(),
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
overlayEntry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return CompositedTransformFollower(
|
|
link: LayerLink(),
|
|
child: OverlayPortal.overlayChildLayoutBuilder(
|
|
controller: controller1,
|
|
overlayChildBuilder: (_, _) => const SizedBox(),
|
|
child: null,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
phase: EnginePhase.layout,
|
|
);
|
|
|
|
expect(
|
|
tester.takeException(),
|
|
isA<FlutterError>().having(
|
|
(FlutterError error) => error.message,
|
|
'message',
|
|
contains('RenderFollowerLayer'),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
class _NullLeaf extends Widget {
|
|
const _NullLeaf();
|
|
@override
|
|
Element createElement() => _NullElement(this);
|
|
}
|
|
|
|
class _NullElement extends Element {
|
|
_NullElement(super.widget);
|
|
|
|
@override
|
|
void mount(Element? parent, Object? newSlot) {
|
|
super.mount(parent, newSlot);
|
|
rebuild(force: true);
|
|
}
|
|
|
|
@override
|
|
bool get debugDoingBuild => throw UnimplementedError();
|
|
}
|