cover Vim edit-ops and extension edge branches

Lift line coverage back over the 95% floor after the Flutter 3.44.1
merge nudged it to 94.99%. Adds tests for the changeWord/no-op-edit/
empty-paste/word-motion-edge branches in vim_edit_ops, the lone-key
flush path in SequenceMatcher, and the keybindings-ui / vim extension
identity + deactivate paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 10:48:27 +02:00
co-authored by Claude Opus 4.8
parent ec67f3323a
commit adcc79c9a5
4 changed files with 65 additions and 0 deletions
@@ -149,6 +149,43 @@ void main() {
expect(r.enterInsert, isTrue);
expect(r.register?.linewise, isTrue);
});
test('cw deletes to the next word and enters insert', () {
final r = applyVim(VimAction.changeWord, _tev('hello world', 0));
expect(r.value.text, 'world');
expect(r.enterInsert, isTrue);
expect(r.register?.text, 'hello ');
});
test('x at the line end is a no-op', () {
final r = applyVim(VimAction.deleteChar, _tev('ab\ncd', 2)); // caret on the newline
expect(r.value.text, 'ab\ncd');
expect(r.register, isNull);
});
test('D at the line end is a no-op', () {
final r = applyVim(VimAction.deleteToEnd, _tev('ab', 2));
expect(r.value.text, 'ab');
});
test('p with an empty register does nothing', () {
final r = applyVim(VimAction.paste, _tev('ab', 0));
expect(r.value.text, 'ab');
});
});
group('word-motion edges', () {
test('w / b cross punctuation and underscores as their own class', () {
// foo_bar is one word (underscore is a word char); the dot is punct.
const t = 'foo_bar.baz';
expect(applyVim(VimAction.wordForward, _tev(t, 0)).value.selection.extentOffset, 7); // the '.'
expect(applyVim(VimAction.wordBackward, _tev(t, 8)).value.selection.extentOffset, 7);
});
test('e on the last word clamps to the final char', () {
final r = applyVim(VimAction.wordEnd, _tev('hi', 1));
expect(r.value.selection.extentOffset, 1);
});
});
group('insert entry', () {
@@ -17,6 +17,20 @@ void main() {
});
tearDown(() => f.dispose());
test('identifies itself', () {
final ext = KeybindingsUiExtension();
expect(ext.id, 'builtin.keybindings-ui');
expect(ext.title, 'Keybindings UI');
expect(ext.version, '0.1.0');
});
test('deactivate drops the keymap reference', () async {
await f.services.extensions.deactivate('builtin.keybindings-ui');
// Re-activating is clean (no retained state).
await f.services.extensions.activate('builtin.keybindings-ui');
expect(f.services.commands.get('keymap.preset.vim'), isNotNull);
});
test('contributes a switch command per shipped preset', () {
expect(f.services.commands.get('keymap.preset.vim'), isNotNull);
expect(f.services.commands.get('keymap.preset.default'), isNotNull);
+6
View File
@@ -22,6 +22,12 @@ void main() {
bool flag(String name) => f.services.keymap.scope[name] ?? false;
test('identifies itself', () {
expect(ext.id, 'builtin.vim');
expect(ext.title, 'Vim');
expect(ext.version, '0.1.0');
});
test('inert under the default preset', () async {
await f.services.extensions.activate('builtin.vim');
expect(ext.modeService!.enabled, isFalse);
@@ -171,5 +171,13 @@ void main() {
test('flush with nothing buffered is a no-op', () {
expect(matcher.flush().outcome, SeqOutcome.pending);
});
test('flush of a lone prefix key (no exact) passes it through', () {
// `g` is a pure prefix of `g g` with no single-`g` binding.
expect(feed('g').outcome, SeqOutcome.pending);
final r = matcher.flush();
expect(r.outcome, SeqOutcome.unmatched);
expect(r.passKey, KeyChord.parse('g'));
});
});
}