Files
clide/test/builtin/claude/pane_context_status_test.dart
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

119 lines
4.8 KiB
Dart

/// Tests for the focus-driven status-bar slot item (T-150 / T-160): it
/// renders the focused pane's status widget and clears when focus moves
/// to a pane with no status; at a constrained width the slot never
/// overflows and ClideMarquee gets a bounded viewport.
library;
import 'package:clide/builtin/claude/src/pane_context_status.dart';
import 'package:clide/kernel/kernel.dart';
import 'package:clide/widgets/widgets.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../helpers/kernel_fixture.dart';
import '../../helpers/widget_harness.dart';
/// Wraps [child] in a Row with Flexible(loose), mirroring the layout that
/// StatusbarHost applies to left-side flex items (T-160), inside a
/// [SizedBox] of the given [width].
///
/// Deliberately does NOT use the shared [harness] here: that one mounts an
/// `Overlay(canSizeOverlay: true)` over a zero-size MediaQuery, which runs an
/// intrinsic-sizing pass and hands children unbounded width — so a `Flexible`
/// never bounds the marquee and this test could not observe the fix. We mount
/// the minimum theme/kernel tree under a tight, [Align]ed [SizedBox] so the
/// `Flexible` gets a real bounded width from the surrounding Row.
Widget _narrowRow(KernelFixture f, double width, Widget child) => Directionality(
textDirection: TextDirection.ltr,
child: ClideKernel(
services: f.services,
child: ClideTheme(
controller: f.services.theme,
child: MediaQuery(
data: const MediaQueryData(),
child: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: width,
height: 24,
child: Row(
children: [
Flexible(flex: 1, fit: FlexFit.loose, child: child),
const SizedBox(width: 20), // simulated right-side items
],
),
),
),
),
),
),
);
void main() {
late KernelFixture f;
setUp(() async => f = await KernelFixture.create());
tearDown(() => f.dispose());
testWidgets('renders the focused pane status, clears on focus change', (tester) async {
await tester.pumpWidget(harness(f, const PaneContextStatusItem()));
await tester.pump();
expect(find.text('S'), findsNothing); // nothing focused
final focus = f.services.focus;
focus.setActive(slot: Slots.workspace, contributionId: 'pane.x');
focus.setStatusWidget('pane.x', const Text('S', textDirection: TextDirection.ltr));
await tester.pump();
expect(find.text('S'), findsOneWidget);
// Focus a pane with no status → the slot clears.
focus.setActive(slot: Slots.workspace, contributionId: 'pane.y');
await tester.pump();
expect(find.text('S'), findsNothing);
});
// T-160: at a narrow bar width the slot must never overflow and
// ClideMarquee must receive a bounded (scrollable) viewport.
testWidgets('no RenderFlex overflow at narrow width — ClideMarquee is bounded', (tester) async {
// A long status line similar to what T-154 added:
// "opus 4.7 · default · 21k ctx · 10 skills" — roughly 280 px of text.
const longStatus = Text('opus 4.7 · default · 21k ctx · 10 skills', textDirection: TextDirection.ltr, softWrap: false);
// Pump at 200 px wide — narrower than the status content so the marquee
// must receive a bounded viewport < content width and start scrolling.
const barWidth = 200.0;
await tester.pumpWidget(_narrowRow(f, barWidth, const PaneContextStatusItem()));
await tester.pump(); // first frame — nothing focused yet
final focus = f.services.focus;
focus.setActive(slot: Slots.workspace, contributionId: 'pane.z');
focus.setStatusWidget('pane.z', longStatus);
await tester.pump(); // status widget appears
await tester.pump(); // ClideMarquee _measure post-frame callback
// 1. No overflow exception.
expect(tester.takeException(), isNull);
// 2. The ClideMarquee is in the tree.
expect(find.byType(ClideMarquee), findsOneWidget);
// 3. The ClideMarquee render box is bounded — its width must be less than
// barWidth (the slot shrinks to fit, not past the container).
final marqueeSize = tester.getSize(find.byType(ClideMarquee));
expect(marqueeSize.width, lessThan(barWidth));
expect(marqueeSize.width, greaterThan(0));
// Dispose the ticker by tearing down the widget tree.
await tester.pumpWidget(const SizedBox());
});
testWidgets('no overflow when no status is active — slot is shrunk', (tester) async {
const barWidth = 200.0;
await tester.pumpWidget(_narrowRow(f, barWidth, const PaneContextStatusItem()));
await tester.pump();
// No focused pane → SizedBox.shrink path. No overflow, no marquee.
expect(tester.takeException(), isNull);
expect(find.byType(ClideMarquee), findsNothing);
});
}