/// Widget tests for PaneKeyNav (T-406): the per-pane vim-normal key handler /// that runs its own SequenceMatcher and dispatches nav.* intents — proven /// end-to-end against the real vim preset and scope flags. library; import 'package:clide/clide.dart'; import 'package:clide/extension/src/contribution.dart'; import 'package:clide/kernel/kernel.dart'; import 'package:flutter/services.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()); tearDown(() => f.dispose()); Future> pump(WidgetTester tester, {required Map scope}) async { // setPreset does real asset + keybindings-file I/O; run it outside the // fake-async zone or the testWidgets body hangs (the T-122 lesson). await tester.runAsync(() => f.services.keymap.setPreset('vim')); for (final e in scope.entries) { f.services.keymap.setScopeFlag(e.key, e.value); } final got = []; final node = FocusNode(); addTearDown(node.dispose); await tester.pumpWidget( harness(f, PaneKeyNav(focusNode: node, autofocus: true, onNav: (i, _) => got.add(i), child: const SizedBox(width: 100, height: 100))), ); node.requestFocus(); await tester.pump(); return got; } testWidgets('bare motions dispatch nav.* under vim.normal (pane focused)', (tester) async { final got = await pump(tester, scope: {'vim.normal': true}); await tester.sendKeyEvent(LogicalKeyboardKey.keyJ); await tester.sendKeyEvent(LogicalKeyboardKey.keyK); await tester.sendKeyEvent(LogicalKeyboardKey.keyH); await tester.sendKeyEvent(LogicalKeyboardKey.keyL); expect(got, [isA(), isA(), isA(), isA()]); }); testWidgets('gg sequence resolves to nav.top', (tester) async { final got = await pump(tester, scope: {'vim.normal': true}); await tester.sendKeyEvent(LogicalKeyboardKey.keyG); await tester.sendKeyEvent(LogicalKeyboardKey.keyG); expect(got, [isA()]); }); testWidgets('ctrl+d / ctrl+u are claimed as half-page nav', (tester) async { final got = await pump(tester, scope: {'vim.normal': true}); await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); await tester.sendKeyEvent(LogicalKeyboardKey.keyD); await tester.sendKeyEvent(LogicalKeyboardKey.keyU); await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); expect(got, [isA(), isA()]); }); testWidgets('the editor.focused guard suppresses nav (keys go to the editor)', (tester) async { final got = await pump(tester, scope: {'vim.normal': true, 'editor.focused': true}); await tester.sendKeyEvent(LogicalKeyboardKey.keyJ); await tester.sendKeyEvent(LogicalKeyboardKey.keyK); // j/k now resolve to editor.vim.* — not NavIntents — so onNav never fires. expect(got, isEmpty); }); testWidgets('gt executes a non-editor command (workspace.tab.next) from a focused pane (T-405)', (tester) async { var ran = 0; f.services.commands.register( CommandContribution( id: 'workspace.tab.next', command: 'workspace.tab.next', title: 'Next Workspace Tab', run: (_) async { ran++; return IpcResponse.ok(id: '', data: const {}); }, ), ); await pump(tester, scope: {'vim.normal': true}); await tester.sendKeyEvent(LogicalKeyboardKey.keyG); await tester.sendKeyEvent(LogicalKeyboardKey.keyT); await tester.pump(); expect(ran, 1); }); testWidgets('editor.vim.* buffer edits never execute from a pane (T-405 guard)', (tester) async { var ran = 0; // If the pane wrongly ran editor.vim.*, this would fire on `d d`. f.services.commands.register( CommandContribution( id: 'editor.vim.deleteLine', command: 'editor.vim.deleteLine', title: 'vim: delete line', run: (_) async { ran++; return IpcResponse.ok(id: '', data: const {}); }, ), ); await pump(tester, scope: {'vim.normal': true}); await tester.sendKeyEvent(LogicalKeyboardKey.keyD); await tester.sendKeyEvent(LogicalKeyboardKey.keyD); await tester.pump(); expect(ran, 0); }); testWidgets('keys pass through outside vim normal mode', (tester) async { final got = await pump(tester, scope: {'vim.insert': true}); await tester.sendKeyEvent(LogicalKeyboardKey.keyJ); expect(got, isEmpty); }); testWidgets('an unbound bare key is swallowed without dispatching nav', (tester) async { final got = await pump(tester, scope: {'vim.normal': true}); await tester.sendKeyEvent(LogicalKeyboardKey.keyZ); expect(got, isEmpty); }); }