add multi-file editor tabs
The editor pane showed a single buffer — opening a second file replaced the first, even though the daemon's EditorRegistry has always been multi-buffer (editor.list / activate / close). This wires the UI up to that: EditorController now tracks the full open-buffer list (via editor.list on hydrate, kept in sync by editor.opened / closed / saved / edited events), and EditorView renders the buffers as tabs through the shared MultitabPane — the same strip the Claude pane uses. The daemon stays the source of truth: the local tab controller is reconciled from it, and tab select / close route back as editor.activate / editor.close. Reorder is disabled for now (daemon order is authoritative). Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
/// Widget tests for the multi-tab editor view: a tab per open buffer
|
||||
/// (filename + dirty marker), the empty-state hint, and tab taps
|
||||
/// routing to `editor.activate`. Buffer-list logic itself is covered
|
||||
/// in editor_controller_test.dart.
|
||||
library;
|
||||
|
||||
import 'package:clide/builtin/editor/src/editor_view.dart';
|
||||
import 'package:clide/clide.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../../helpers/kernel_fixture.dart';
|
||||
import '../../helpers/widget_harness.dart';
|
||||
|
||||
IpcResponse _ok(Map<String, Object?> data) => IpcResponse.ok(id: '', data: data);
|
||||
|
||||
Map<String, Object?> _buf(String id, String path, {bool dirty = false}) => {'id': id, 'path': path, 'dirty': dirty};
|
||||
|
||||
Map<String, Object?> _read(String id, String path) => {
|
||||
'id': id,
|
||||
'path': path,
|
||||
'content': 'content of $path',
|
||||
'selection': {'start': 0, 'end': 0},
|
||||
'dirty': false,
|
||||
};
|
||||
|
||||
void main() {
|
||||
group('EditorView tabs', () {
|
||||
late KernelFixture f;
|
||||
setUp(() async => f = await KernelFixture.create());
|
||||
tearDown(() => f.dispose());
|
||||
|
||||
void stubBuffers(List<Map<String, Object?>> buffers, {String? active}) {
|
||||
f.ipc.stub('editor.list', (_) async => _ok({'buffers': buffers}));
|
||||
f.ipc.stub(
|
||||
'editor.active',
|
||||
(_) async => active == null
|
||||
? _ok(const {})
|
||||
: _ok({
|
||||
'active': {'id': active}
|
||||
}));
|
||||
f.ipc.stub('editor.read', (a) async {
|
||||
final id = a['id'] as String;
|
||||
final b = buffers.firstWhere((b) => b['id'] == id);
|
||||
return _ok(_read(id, b['path'] as String));
|
||||
});
|
||||
}
|
||||
|
||||
testWidgets('renders one tab per open buffer, by filename', (tester) async {
|
||||
stubBuffers([_buf('b_1', 'lib/a.dart'), _buf('b_2', 'src/b.dart')], active: 'b_1');
|
||||
await tester.pumpWidget(harness(f, const EditorView()));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('a.dart'), findsOneWidget);
|
||||
expect(find.text('b.dart'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('a dirty buffer carries a marker in its tab title', (tester) async {
|
||||
stubBuffers([_buf('b_1', 'lib/a.dart', dirty: true)], active: 'b_1');
|
||||
await tester.pumpWidget(harness(f, const EditorView()));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('a.dart •'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('no open buffers shows the open-a-file hint', (tester) async {
|
||||
stubBuffers(const [], active: null);
|
||||
await tester.pumpWidget(harness(f, const EditorView()));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Open a file to begin editing.'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('tapping an inactive tab routes to editor.activate', (tester) async {
|
||||
stubBuffers([_buf('b_1', 'lib/a.dart'), _buf('b_2', 'src/b.dart')], active: 'b_1');
|
||||
String? activated;
|
||||
f.ipc.stub('editor.activate', (a) async {
|
||||
activated = a['id'] as String?;
|
||||
return _ok({'active': a['id']});
|
||||
});
|
||||
await tester.pumpWidget(harness(f, const EditorView()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('b.dart'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(activated, 'b_2');
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user