flutter_flutter/packages/flutter/test/widgets/window_positioner_test.dart
Kate Lovett 9d96df2364
Modernize framework lints (#179089)
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
2025-11-26 01:10:39 +00:00

504 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.
// ignore_for_file: invalid_use_of_internal_member
import 'dart:ui';
import 'package:flutter/src/widgets/_window_positioner.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('WindowPlacementTest', () {
const clientDisplayArea = Rect.fromLTWH(0, 0, 800, 600);
const clientParentSize = Size(400, 300);
const clientChildSize = Size(100, 50);
final clientParentPosition = Offset(
(clientDisplayArea.width - clientParentSize.width) / 2,
(clientDisplayArea.height - clientParentSize.height) / 2,
);
final clientParentRect = Rect.fromLTWH(
clientParentPosition.dx,
clientParentPosition.dy,
clientParentSize.width,
clientParentSize.height,
);
const displayArea = Rect.fromLTWH(0, 0, 640, 480);
const parentSize = Size(600, 400);
const childSize = Size(300, 300);
const rectangleNearRhs = Rect.fromLTWH(590, 20, 10, 20);
const rectangleNearLeftSide = Rect.fromLTWH(0, 20, 20, 20);
const rectangleNearAllSides = Rect.fromLTWH(0, 20, 600, 380);
const rectangleNearBottom = Rect.fromLTWH(20, 380, 20, 20);
const rectangleNearBothBottomRight = Rect.fromLTWH(400, 380, 200, 20);
final parentPosition = Offset(
(displayArea.width - parentSize.width) / 2,
(displayArea.height - parentSize.height) / 2,
);
final parentRect = Rect.fromLTWH(
parentPosition.dx,
parentPosition.dy,
parentSize.width,
parentSize.height,
);
Rect anchorRectFor(Rect rect) => rect.translate(parentPosition.dx, parentPosition.dy);
Offset onTopEdge(Rect rect, Size childSize) => rect.topLeft - Offset(0, childSize.height);
Offset onLeftEdge(Rect rect, Size childSize) => rect.topLeft - Offset(childSize.width, 0);
test('Client anchors to parent given anchor rectangle right of parent', () {
const rectSize = 10.0;
final overlappingRight = Rect.fromCenter(
center: clientParentRect.topRight.translate(-rectSize / 2, clientParentRect.height / 2),
width: rectSize,
height: rectSize,
);
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topRight,
childAnchor: WindowPositionerAnchor.topLeft,
constraintAdjustment: WindowPositionerConstraintAdjustment(slideY: true, resizeX: true),
);
final Rect childRect = positioner.placeWindow(
childSize: clientChildSize,
anchorRect: overlappingRight,
parentRect: clientParentRect,
displayRect: clientDisplayArea,
);
final Offset expectedPosition = overlappingRight.topRight;
expect(childRect.topLeft, expectedPosition);
expect(childRect.size, clientChildSize);
});
test('Client anchors to parent given anchor rectangle above parent', () {
const rectSize = 10.0;
final overlappingAbove = Rect.fromCenter(
center: clientParentRect.topCenter.translate(0, -rectSize / 2),
width: rectSize,
height: rectSize,
);
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topRight,
childAnchor: WindowPositionerAnchor.bottomRight,
constraintAdjustment: WindowPositionerConstraintAdjustment(slideX: true),
);
final Rect childRect = positioner.placeWindow(
childSize: clientChildSize,
anchorRect: overlappingAbove,
parentRect: clientParentRect,
displayRect: clientDisplayArea,
);
final Offset expectedPosition =
overlappingAbove.bottomRight - Offset(clientChildSize.width, clientChildSize.height);
expect(childRect.topLeft, expectedPosition);
expect(childRect.size, clientChildSize);
});
test('Client anchors to parent given offset right of parent', () {
const rectSize = 10.0;
final midRight = Rect.fromLTWH(
clientParentRect.right - rectSize,
clientParentRect.center.dy,
rectSize,
rectSize,
);
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topRight,
childAnchor: WindowPositionerAnchor.topLeft,
offset: Offset(rectSize, 0),
constraintAdjustment: WindowPositionerConstraintAdjustment(slideY: true, resizeX: true),
);
final Rect childRect = positioner.placeWindow(
childSize: clientChildSize,
anchorRect: midRight,
parentRect: clientParentRect,
displayRect: clientDisplayArea,
);
final Offset expectedPosition = midRight.topRight;
expect(childRect.topLeft, expectedPosition);
expect(childRect.size, clientChildSize);
});
test('Client anchors to parent given offset above parent', () {
const rectSize = 10.0;
final midTop = Rect.fromLTWH(
clientParentRect.center.dx,
clientParentRect.top,
rectSize,
rectSize,
);
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topRight,
childAnchor: WindowPositionerAnchor.bottomRight,
offset: Offset(0, -rectSize),
constraintAdjustment: WindowPositionerConstraintAdjustment(slideX: true),
);
final Rect childRect = positioner.placeWindow(
childSize: clientChildSize,
anchorRect: midTop,
parentRect: clientParentRect,
displayRect: clientDisplayArea,
);
final Offset expectedPosition =
clientParentPosition +
Offset(clientParentSize.width / 2 + rectSize, 0) -
Offset(clientChildSize.width, clientChildSize.height);
expect(childRect.topLeft, expectedPosition);
expect(childRect.size, clientChildSize);
});
test('Client anchors to parent given anchor rectangle and offset below left parent', () {
const rectSize = 10.0;
final belowLeft = Rect.fromLTWH(
clientParentRect.left - rectSize,
clientParentRect.bottom,
rectSize,
rectSize,
);
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.bottomLeft,
childAnchor: WindowPositionerAnchor.topRight,
offset: Offset(-rectSize, rectSize),
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeX: true, resizeY: true),
);
final Rect childRect = positioner.placeWindow(
childSize: clientChildSize,
anchorRect: belowLeft,
parentRect: clientParentRect,
displayRect: clientDisplayArea,
);
final Offset expectedPosition =
clientParentRect.bottomLeft - Offset(clientChildSize.width, 0);
expect(childRect.topLeft, expectedPosition);
expect(childRect.size, clientChildSize);
});
group('Can attach by every anchor given no constraint adjustment', () {
Offset anchorPositionFor(WindowPositionerAnchor anchor, Rect rect) {
return switch (anchor) {
WindowPositionerAnchor.center => rect.center,
WindowPositionerAnchor.top => rect.topCenter,
WindowPositionerAnchor.bottom => rect.bottomCenter,
WindowPositionerAnchor.left => rect.centerLeft,
WindowPositionerAnchor.right => rect.centerRight,
WindowPositionerAnchor.topLeft => rect.topLeft,
WindowPositionerAnchor.bottomLeft => rect.bottomLeft,
WindowPositionerAnchor.topRight => rect.topRight,
WindowPositionerAnchor.bottomRight => rect.bottomRight,
};
}
for (final WindowPositionerAnchor parentAnchor in WindowPositionerAnchor.values) {
for (final WindowPositionerAnchor childAnchor in WindowPositionerAnchor.values) {
test('parent: $parentAnchor, child: $childAnchor', () {
final Rect anchorRect = anchorRectFor(const Rect.fromLTWH(100, 50, 20, 20));
final positioner = WindowPositioner(
parentAnchor: parentAnchor,
childAnchor: childAnchor,
);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
expect(
anchorPositionFor(childAnchor, childRect),
anchorPositionFor(parentAnchor, anchorRect),
);
});
}
}
});
test('Placement is flipped given anchor rectangle near right side and offset', () {
const xOffset = 42.0;
const yOffset = 13.0;
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topRight,
childAnchor: WindowPositionerAnchor.topLeft,
offset: Offset(xOffset, yOffset),
constraintAdjustment: WindowPositionerConstraintAdjustment(flipX: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearRhs);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
final Offset expectedPosition =
onLeftEdge(anchorRect, childSize) + const Offset(-xOffset, yOffset);
expect(childRect.topLeft, expectedPosition);
});
test('Placement is flipped given anchor rectangle near bottom and offset', () {
const xOffset = 42.0;
const yOffset = 13.0;
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.bottomLeft,
childAnchor: WindowPositionerAnchor.topLeft,
offset: Offset(xOffset, yOffset),
constraintAdjustment: WindowPositionerConstraintAdjustment(flipY: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearBottom);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
final Offset expectedPosition =
onTopEdge(anchorRect, childSize) + const Offset(xOffset, -yOffset);
expect(childRect.topLeft, expectedPosition);
});
test('Placement is flipped both ways given anchor rectangle near bottom right and offset', () {
const xOffset = 42.0;
const yOffset = 13.0;
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.bottomRight,
childAnchor: WindowPositionerAnchor.topLeft,
offset: Offset(xOffset, yOffset),
constraintAdjustment: WindowPositionerConstraintAdjustment(flipX: true, flipY: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearBothBottomRight);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
final Offset expectedPosition =
anchorRect.topLeft -
Offset(childSize.width, childSize.height) -
const Offset(xOffset, yOffset);
expect(childRect.topLeft, expectedPosition);
});
test('Placement can slide in X given anchor rectangle near right side', () {
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topRight,
childAnchor: WindowPositionerAnchor.topLeft,
constraintAdjustment: WindowPositionerConstraintAdjustment(slideX: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearRhs);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
expect(childRect.topLeft.dx, displayArea.right - childSize.width);
});
test('Placement can slide in X given anchor rectangle near left side', () {
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topLeft,
childAnchor: WindowPositionerAnchor.topRight,
constraintAdjustment: WindowPositionerConstraintAdjustment(slideX: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearLeftSide);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
expect(childRect.topLeft.dx, displayArea.left);
});
test('Placement can slide in Y given anchor rectangle near bottom', () {
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.bottomLeft,
childAnchor: WindowPositionerAnchor.topLeft,
constraintAdjustment: WindowPositionerConstraintAdjustment(slideY: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearBottom);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
expect(childRect.topLeft.dy, displayArea.bottom - childSize.height);
});
test('Placement can slide in Y given anchor rectangle near top', () {
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topLeft,
childAnchor: WindowPositionerAnchor.bottomLeft,
constraintAdjustment: WindowPositionerConstraintAdjustment(slideY: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearAllSides);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
expect(childRect.topLeft.dy, displayArea.top);
});
test('Placement can slide in X and Y given anchor rectangle near bottom right and offset', () {
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.bottomLeft,
childAnchor: WindowPositionerAnchor.topLeft,
constraintAdjustment: WindowPositionerConstraintAdjustment(slideX: true, slideY: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearBothBottomRight);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
final expectedPosition = Offset(
displayArea.right - childSize.width,
displayArea.bottom - childSize.height,
);
expect(childRect.topLeft, expectedPosition);
});
test('Placement can resize in X given anchor rectangle near right side', () {
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topRight,
childAnchor: WindowPositionerAnchor.topLeft,
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeX: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearRhs);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
expect(childRect.width, displayArea.right - (anchorRect.left + anchorRect.width));
});
test('Placement can resize in X given anchor rectangle near left side', () {
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topLeft,
childAnchor: WindowPositionerAnchor.topRight,
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeX: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearLeftSide);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
expect(childRect.width, anchorRect.left - displayArea.left);
});
test('Placement can resize in Y given anchor rectangle near bottom', () {
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.bottomLeft,
childAnchor: WindowPositionerAnchor.topLeft,
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeY: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearAllSides);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
expect(childRect.height, displayArea.bottom - (anchorRect.top + anchorRect.height));
});
test('Placement can resize in Y given anchor rectangle near top', () {
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.topLeft,
childAnchor: WindowPositionerAnchor.bottomLeft,
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeY: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearAllSides);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
expect(childRect.height, anchorRect.top - displayArea.top);
});
test('Placement can resize in X and Y given anchor rectangle near bottom right and offset', () {
const positioner = WindowPositioner(
parentAnchor: WindowPositionerAnchor.bottomRight,
childAnchor: WindowPositionerAnchor.topLeft,
constraintAdjustment: WindowPositionerConstraintAdjustment(resizeX: true, resizeY: true),
);
final Rect anchorRect = anchorRectFor(rectangleNearBothBottomRight);
final Rect childRect = positioner.placeWindow(
childSize: childSize,
anchorRect: anchorRect,
parentRect: parentRect,
displayRect: displayArea,
);
final expectedSize = Size(
displayArea.right - (anchorRect.left + anchorRect.width),
displayArea.bottom - (anchorRect.top + anchorRect.height),
);
expect(childRect.size, expectedSize);
});
});
}