harden ClideAnchoredOverlay positioning + add an anchored test harness (T-288)

Root-cause of the theme-picker friction: the primitive's focus model is fine
(keyboard nav reaches a ClideMenu through the overlay), but the shared harness()
uses Overlay(canSizeOverlay) + a zero-size MediaQuery, which mispositions an
anchored follower off-screen and defeats autoFlip.

- autoFlip now reads the real view size (View.physicalSize) instead of
  MediaQuery.size, so it flips correctly even when MediaQuery is overridden.
- Drop the inner Align in the follower — it pegged the panel to a corner of the
  full-screen follower box and broke hit-testing for non-top-left anchors, so
  end-aligned menu items weren't mouse-tappable.
- Add anchoredHarness() — a properly-sized Overlay tree for testing popover
  content (the remaining migrations will use it).

Tests: keyboard nav through the overlay, an end-aligned item is mouse-tappable,
and autoFlip flips below->above near the bottom edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-08 22:17:58 +02:00
co-authored by Claude Opus 4.8
parent 020b29f9b7
commit 318e09e748
3 changed files with 124 additions and 8 deletions
+35
View File
@@ -35,6 +35,41 @@ Widget harness(KernelFixture fixture, Widget child) {
);
}
/// Harness for **anchored-overlay content** (ClideAnchoredOverlay popovers).
///
/// The shared [harness] wraps its child in `Overlay(canSizeOverlay)` + a
/// zero-size `MediaQuery` — fine for plain widgets, but it mispositions an
/// anchored follower off-screen and defeats `autoFlip`, so popover items aren't
/// reliably hit-testable. This builds a properly-sized Overlay tree instead:
/// `Directionality → ClideKernel → ClideTheme → MediaQuery(size) → Overlay`,
/// with [child] (the trigger) placed at [alignment]. Set
/// `tester.view.physicalSize = size` to match (the default 800×600 matches the
/// default test view, so no setup is needed unless you change [size]).
Widget anchoredHarness(
KernelFixture fixture,
Widget child, {
Size size = const Size(800, 600),
Alignment alignment = Alignment.topLeft,
}) {
return Directionality(
textDirection: TextDirection.ltr,
child: ClideKernel(
services: fixture.services,
child: ClideTheme(
controller: fixture.services.theme,
child: MediaQuery(
data: MediaQueryData(size: size),
child: Overlay(
initialEntries: [
OverlayEntry(builder: (_) => Align(alignment: alignment, child: child)),
],
),
),
),
),
);
}
/// Settle async-driven UI in a widget test WITHOUT the two patterns that have
/// repeatedly wedged this suite:
///