Files
clide/test/kernel/quick_open_test.dart
jpmschweitzerandClaude Opus 4.8 0c7a6e86d5 add quick-open fuzzy file finder (Ctrl/Cmd+P)
A file picker overlay over the whole workspace, distinct from the
command palette. QuickOpenController holds the file list + a
subsequence fuzzy filter; the overlay loads the list via files.walk on
open, shows RecentFilesService entries on an empty query, and opens the
selection through a shared openWorkspaceFile helper (.md → markdown
reader bus, else editor.open) that the files panel now also routes
through, so recents stay in sync from every open site.

Bound to ctrl+p / meta+p with `when: !palette.open` so it never
collides with the palette's ctrl+p navigation; in-overlay arrows/enter/
escape reuse the palette's keymap-driven model via quickOpen.* intents.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:19:21 +02:00

101 lines
3.0 KiB
Dart

/// Unit tests for [QuickOpenController] — state, fuzzy ranking, recents
/// fallback, and selection wrapping (T-51).
library;
import 'package:clide/kernel/src/quick_open.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
late List<String> recents;
QuickOpenController make() => QuickOpenController(recentPaths: () => recents);
setUp(() => recents = ['recent_a.dart', 'recent_b.dart']);
test('open/close/toggle track isOpen and reset the filter', () {
final c = make();
expect(c.isOpen, isFalse);
c.open();
expect(c.isOpen, isTrue);
c.setFilter('main');
c.close();
expect(c.isOpen, isFalse);
expect(c.filter, isEmpty);
c.toggle();
expect(c.isOpen, isTrue);
});
test('empty query shows recents', () {
final c = make()..open();
c.setFiles(['lib/main.dart', 'lib/app.dart']);
expect(c.filtered(), recents);
expect(c.selectedPath, 'recent_a.dart');
});
test('non-empty query fuzzy-matches the file list, not recents', () {
final c = make()..open();
c.setFiles(['lib/main.dart', 'lib/app.dart', 'README.md']);
c.setFilter('main');
expect(c.filtered(), contains('lib/main.dart'));
expect(c.filtered(), isNot(contains('README.md')));
});
test('subsequence match ranks contiguous/early hits above scattered', () {
final c = make()..open();
c.setFiles(['x/abc_extra.dart', 'abc.dart', 'a_b_c.dart']);
c.setFilter('abc');
// 'abc.dart' (contiguous, earliest) should rank first.
expect(c.filtered().first, 'abc.dart');
});
test('non-matching query yields an empty result list', () {
final c = make()..open();
c.setFiles(['lib/main.dart']);
c.setFilter('zzzzz');
expect(c.filtered(), isEmpty);
expect(c.selectedPath, isNull);
});
test('selectNext/Previous wrap around the result list', () {
final c = make()..open();
c.setFiles(['a.dart', 'b.dart', 'c.dart']);
c.setFilter('dart'); // matches all three
expect(c.selectedIndex, 0);
c.selectPrevious(); // wraps to last
expect(c.selectedIndex, c.filtered().length - 1);
c.selectNext(); // wraps back to 0
expect(c.selectedIndex, 0);
});
test('selection is a no-op with fewer than two results', () {
final c = make()..open();
c.setFiles(['only.dart']);
c.setFilter('only');
c.selectNext();
expect(c.selectedIndex, 0);
});
test('setFiles records truncation', () {
final c = make()..open();
c.setFiles(['a.dart'], truncated: true);
expect(c.truncated, isTrue);
});
test('result list is capped at resultCap', () {
final c = make()..open();
c.setFiles([for (var i = 0; i < QuickOpenController.resultCap + 50; i++) 'f$i.dart']);
c.setFilter('dart');
expect(c.filtered(), hasLength(QuickOpenController.resultCap));
});
test('setLoading toggles the flag and notifies', () {
final c = make();
var n = 0;
c.addListener(() => n++);
c.setLoading(true);
expect(c.isLoading, isTrue);
expect(n, 1);
c.setLoading(true); // no change → no notify
expect(n, 1);
});
}