Files
clide/test/widgets/quick_open_overlay_test.dart
T
jpmschweitzerandClaude Opus 4.8 1411e19ab4 ultrawide layout test coverage + shared setSurfaceSize helper (T-241)
Add setSurfaceSize(tester, width) to the widget harness — the reusable way to
test width-sensitive layout at an ultrawide surface (a wide SizedBox under the
default 800px surface is clamped, so the view's physicalSize must be set), the
foundation T-241 asked for.

Ultrawide cases on the surfaces most prone to width-proportional bugs (the T-239
class): ClideMarquee stays static when a line fits a 3440 slot (it was only
tested narrow); the quick-open palette stays width-capped, not stretched edge to
edge. The status bar already covers 600+3440 (T-239).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 22:34:16 +02:00

178 lines
6.7 KiB
Dart

/// Widget tests for [QuickOpenOverlay] — loads the file list via the
/// stubbed `files.walk`, routes a selection through [openWorkspaceFile]
/// (.md → markdown reader bus, else editor.open), and records recents
/// (T-51).
library;
import 'package:clide/clide.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';
void main() {
late KernelFixture f;
setUp(() async {
f = await KernelFixture.create();
f.ipc.stub(
'files.walk',
(_) async => IpcResponse.ok(id: '1', data: const {
'files': ['lib/main.dart', 'lib/app.dart', 'README.md'],
'truncated': false,
}));
f.ipc.stub('editor.open', (args) async => IpcResponse.ok(id: '1', data: {'path': args['path']}));
});
tearDown(() => f.dispose());
testWidgets('hidden until opened, then loads files and filters', (tester) async {
await tester.pumpWidget(harness(f, const QuickOpenOverlay()));
expect(find.byType(EditableText), findsNothing);
f.services.quickOpen.open();
await pumpAsync(tester);
// File list loaded from files.walk.
expect(f.services.quickOpen.truncated, isFalse);
await tester.enterText(find.byType(EditableText), 'app');
await pumpAsync(tester);
expect(find.text('app.dart'), findsOneWidget);
expect(find.text('main.dart'), findsNothing);
});
testWidgets('tapping a non-md result opens the editor and records a recent', (tester) async {
String? opened;
f.ipc.stub('editor.open', (args) async {
opened = args['path'] as String?;
return IpcResponse.ok(id: '1', data: {'path': args['path']});
});
await tester.pumpWidget(harness(f, const QuickOpenOverlay()));
f.services.quickOpen.open();
await pumpAsync(tester);
await tester.enterText(find.byType(EditableText), 'main');
await pumpAsync(tester);
await tester.tap(find.text('main.dart'));
await pumpAsync(tester);
expect(opened, 'lib/main.dart');
expect(f.services.quickOpen.isOpen, isFalse);
expect(f.services.recentFiles.paths, contains('lib/main.dart'));
});
testWidgets('tapping an md result publishes to the markdown reader bus', (tester) async {
final published = <Message>[];
final sub = f.services.messages.subscribe(publisher: 'builtin.markdown', channel: 'selection').listen(published.add);
addTearDown(sub.cancel);
await tester.pumpWidget(harness(f, const QuickOpenOverlay()));
f.services.quickOpen.open();
await pumpAsync(tester);
await tester.enterText(find.byType(EditableText), 'readme');
await pumpAsync(tester);
await tester.tap(find.text('README.md'));
await pumpAsync(tester);
expect(published, hasLength(1));
expect(published.first.data['path'], 'README.md');
expect(f.services.recentFiles.paths, contains('README.md'));
});
testWidgets('empty query shows recents once any file has been opened', (tester) async {
f.services.recentFiles.push('lib/app.dart');
await tester.pumpWidget(harness(f, const QuickOpenOverlay()));
f.services.quickOpen.open();
await pumpAsync(tester);
// Empty query → recents listed.
expect(find.text('app.dart'), findsOneWidget);
});
testWidgets('a non-matching query shows the no-match hint', (tester) async {
await tester.pumpWidget(harness(f, const QuickOpenOverlay()));
f.services.quickOpen.open();
await pumpAsync(tester);
await tester.enterText(find.byType(EditableText), 'zzzzz-nope');
await pumpAsync(tester);
expect(find.text('No matching files'), findsOneWidget);
});
testWidgets('truncated walk surfaces the limited-results hint', (tester) async {
f.ipc.stub(
'files.walk',
(_) async => IpcResponse.ok(id: '1', data: const {
'files': ['lib/main.dart'],
'truncated': true,
}));
await tester.pumpWidget(harness(f, const QuickOpenOverlay()));
f.services.quickOpen.open();
await pumpAsync(tester);
await tester.enterText(find.byType(EditableText), 'main');
await pumpAsync(tester);
expect(find.text('Results limited — large workspace'), findsOneWidget);
});
testWidgets('keymap intents drive nav, accept and dismiss', (tester) async {
String? opened;
f.ipc.stub('editor.open', (args) async {
opened = args['path'] as String?;
return IpcResponse.ok(id: '1', data: {'path': args['path']});
});
await tester.pumpWidget(harness(f, const QuickOpenOverlay()));
f.services.quickOpen.open();
await pumpAsync(tester);
await tester.enterText(find.byType(EditableText), 'lib');
await pumpAsync(tester);
final ctx = tester.element(find.byType(EditableText));
Actions.invoke(ctx, const QuickOpenSelectNextIntent());
await pumpAsync(tester);
Actions.invoke(ctx, const QuickOpenSelectPreviousIntent());
await pumpAsync(tester);
// Accept opens the highlighted result in the editor.
Actions.invoke(ctx, const QuickOpenAcceptIntent());
await pumpAsync(tester);
expect(opened, isNotNull);
expect(f.services.quickOpen.isOpen, isFalse);
});
testWidgets('dismiss intent closes the overlay', (tester) async {
await tester.pumpWidget(harness(f, const QuickOpenOverlay()));
f.services.quickOpen.open();
await pumpAsync(tester);
final ctx = tester.element(find.byType(EditableText));
Actions.invoke(ctx, const DismissIntent());
await pumpAsync(tester);
expect(f.services.quickOpen.isOpen, isFalse);
});
testWidgets('files.walk failure leaves the list empty (no crash)', (tester) async {
f.ipc.stub(
'files.walk',
(_) async => IpcResponse.err(
id: '1',
error: IpcError(code: IpcExitCode.toolError, kind: IpcErrorKind.toolError, message: 'boom'),
));
await tester.pumpWidget(harness(f, const QuickOpenOverlay()));
f.services.quickOpen.open();
await pumpAsync(tester);
await tester.enterText(find.byType(EditableText), 'main');
await pumpAsync(tester);
expect(find.text('No matching files'), findsOneWidget);
});
testWidgets('the palette is width-capped (not stretched) on an ultrawide surface (T-241)', (tester) async {
setSurfaceSize(tester, 3440);
await tester.pumpWidget(harness(f, const QuickOpenOverlay()));
f.services.quickOpen.open();
await pumpAsync(tester);
// The panel is a fixed 480 — the filter field must stay capped, not span the
// full 3440 (a Row/Expanded regression would stretch it edge to edge).
final field = tester.getRect(find.byType(EditableText));
expect(field.width, lessThan(600), reason: 'capped to the panel, not stretched across the ultrawide surface');
});
}