Files
clide/test/helpers/widget_harness.dart
T
jpmschweitzerandClaude Opus 4.8 6d0ebab721 chore: adopt Dart 3.9 toolchain — honest floor + tall-style reformat (T-353)
Raise the declared minimums in pubspec.yaml to what our deps already
require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist
0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is
the binding floor. Pin the exact build toolchain in .fvmrc (Flutter
3.44.1).

Moving to the Dart 3.9 language level switches `dart format` to the new
"tall" style and enables two new lints. This commit is the resulting
mechanical churn, isolated from any behaviour change:
  - whole-tree `dart format` reformat (tall style)
  - `dart fix` for unnecessary_underscores + use_null_aware_elements

No runtime behaviour change; `make test` green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:11:53 +02:00

96 lines
4.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:clide/kernel/kernel.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'kernel_fixture.dart';
/// Wraps a widget in the minimum tree a primitive needs to resolve
/// theme + i18n + Overlay (for Draggable feedback / Tooltip / etc.):
/// `Directionality → ClideKernel → ClideTheme → MediaQuery →
/// Overlay → child`.
///
/// The Overlay is sized by the test view's bounds via the surrounding
/// MediaQuery; no extra SizedBox is added so existing tests that
/// query `find.byType(SizedBox).first` still find their target.
Widget harness(KernelFixture fixture, Widget child) {
return Directionality(
textDirection: TextDirection.ltr,
child: ClideKernel(
services: fixture.services,
child: ClideTheme(
controller: fixture.services.theme,
child: MediaQuery(
data: const MediaQueryData(),
child: Overlay(initialEntries: [OverlayEntry(canSizeOverlay: true, builder: (_) => 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:
///
/// - **Never `pumpAndSettle()`** — it loops until the frame queue is quiescent,
/// so a perpetual animation or overlapping async loads hang it for its
/// ~10-minute default timeout (which wedged the pre-push gate).
/// - **Never `await Future.delayed(Duration.zero)`** — inside the fake-async
/// `testWidgets` zone a real timer never fires unless fake time is advanced,
/// so that line wedges the test until timeout (and even defeats `--timeout`).
///
/// Instead: pump one frame (draining the microtask queue — broadcast-stream and
/// async-IPC deliveries resolve here), then advance a tiny fake-time tick to
/// flush any follow-up `setState`. Bounded by construction — it cannot hang.
/// Use this after publishing a message / triggering a load in a reader/panel
/// widget test, in place of `pumpAndSettle`.
Future<void> pumpAsync(WidgetTester tester) async {
await tester.pump();
await tester.pump(const Duration(milliseconds: 20));
}
/// Size the test surface to [width]×[height] for width-sensitive layout checks
/// (T-241). A wide SizedBox under the default 800px surface is CLAMPED, so the
/// view's physicalSize must be set directly. Auto-resets after the test. Pass an
/// ultrawide width (e.g. 3440) alongside a normal one to catch width-PROPORTIONAL
/// bugs that hide at 800px (the T-239 class of bug).
void setSurfaceSize(WidgetTester tester, double width, {double height = 800}) {
tester.view.physicalSize = Size(width, height);
tester.view.devicePixelRatio = 1.0;
addTearDown(() {
tester.view.resetPhysicalSize();
tester.view.resetDevicePixelRatio();
});
}