ep_vim 0.12.0 → 0.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,363 @@
1
+ "use strict";
2
+
3
+ const { describe, it, beforeEach } = require("node:test");
4
+ const assert = require("node:assert/strict");
5
+
6
+ // Mock navigator for clipboard operations
7
+ global.navigator = {
8
+ clipboard: {
9
+ writeText: () => Promise.resolve(),
10
+ },
11
+ };
12
+
13
+ const {
14
+ _state: state,
15
+ _handleKey: handleKey,
16
+ _commands: commands,
17
+ _parameterized: parameterized,
18
+ _setVimEnabled: setVimEnabled,
19
+ _setUseCtrlKeys: setUseCtrlKeys,
20
+ aceKeyEvent,
21
+ } = require("./index.js");
22
+
23
+ const makeRep = (lines) => ({
24
+ lines: {
25
+ length: () => lines.length,
26
+ atIndex: (n) => ({ text: lines[n] }),
27
+ },
28
+ });
29
+
30
+ const makeMockEditorInfo = () => {
31
+ const calls = [];
32
+ return {
33
+ editorInfo: {
34
+ ace_inCallStackIfNecessary: (_name, fn) => fn(),
35
+ ace_performSelectionChange: (start, end, _flag) => {
36
+ calls.push({ type: "select", start, end });
37
+ },
38
+ ace_updateBrowserSelectionFromRep: () => {},
39
+ ace_performDocumentReplaceRange: (start, end, newText) => {
40
+ calls.push({ type: "replace", start, end, newText });
41
+ },
42
+ },
43
+ calls,
44
+ };
45
+ };
46
+
47
+ // ---------------------------------------------------------------------------
48
+
49
+ const resetState = () => {
50
+ state.mode = "normal";
51
+ state.pendingKey = null;
52
+ state.pendingCount = null;
53
+ state.countBuffer = "";
54
+ state.register = null;
55
+ state.namedRegisters = {};
56
+ state.pendingRegister = null;
57
+ state.awaitingRegister = false;
58
+ state.marks = {};
59
+ state.lastCharSearch = null;
60
+ state.visualAnchor = null;
61
+ state.visualCursor = null;
62
+ state.editorDoc = null;
63
+ state.currentRep = null;
64
+ state.desiredColumn = null;
65
+ state.lastCommand = null;
66
+ state.searchMode = false;
67
+ state.searchBuffer = "";
68
+ state.searchDirection = null;
69
+ state.lastSearch = null;
70
+ state.lastVisualSelection = null;
71
+ };
72
+
73
+ describe("undo command", () => {
74
+ beforeEach(() => {
75
+ state.mode = "normal";
76
+ state.pendingKey = null;
77
+ state.pendingCount = null;
78
+ state.countBuffer = "";
79
+ state.register = null;
80
+ state.marks = {};
81
+ state.lastCharSearch = null;
82
+ state.visualAnchor = null;
83
+ state.visualCursor = null;
84
+ state.editorDoc = null;
85
+ state.currentRep = null;
86
+ state.desiredColumn = null;
87
+ state.lastCommand = null;
88
+ state.searchMode = false;
89
+ state.searchBuffer = "";
90
+ state.searchDirection = null;
91
+ state.lastSearch = null;
92
+ });
93
+
94
+ it("u calls undo", () => {
95
+ const rep = makeRep(["hello"]);
96
+ const { editorInfo: baseEditorInfo } = makeMockEditorInfo();
97
+
98
+ const editorInfo = {
99
+ ...baseEditorInfo,
100
+ ace_doUndoRedo: () => {},
101
+ };
102
+
103
+ const ctx = { rep, editorInfo, line: 0, char: 0, lineText: "hello" };
104
+ commands.normal["u"](ctx);
105
+ });
106
+ });
107
+
108
+ describe("repeat command", () => {
109
+ beforeEach(() => {
110
+ state.mode = "normal";
111
+ state.pendingKey = null;
112
+ state.pendingCount = null;
113
+ state.countBuffer = "";
114
+ state.register = null;
115
+ state.marks = {};
116
+ state.lastCharSearch = null;
117
+ state.visualAnchor = null;
118
+ state.visualCursor = null;
119
+ state.editorDoc = null;
120
+ state.currentRep = null;
121
+ state.desiredColumn = null;
122
+ state.lastCommand = null;
123
+ state.searchMode = false;
124
+ state.searchBuffer = "";
125
+ state.searchDirection = null;
126
+ state.lastSearch = null;
127
+ });
128
+
129
+ it(". repeats last command", () => {
130
+ const rep = makeRep(["hello"]);
131
+ const { editorInfo, calls } = makeMockEditorInfo();
132
+
133
+ state.lastCommand = { key: "h", count: 1, param: null };
134
+
135
+ const ctx = {
136
+ rep,
137
+ editorInfo,
138
+ line: 0,
139
+ char: 3,
140
+ lineText: "hello",
141
+ };
142
+ commands.normal["."](ctx);
143
+
144
+ assert.equal(calls.length, 1);
145
+ });
146
+
147
+ it(". does nothing with no last command", () => {
148
+ const rep = makeRep(["hello"]);
149
+ const { editorInfo, calls } = makeMockEditorInfo();
150
+
151
+ state.lastCommand = null;
152
+
153
+ const ctx = { rep, editorInfo, line: 0, char: 0, lineText: "hello" };
154
+ commands.normal["."](ctx);
155
+
156
+ assert.equal(calls.length, 0);
157
+ });
158
+
159
+ it(". repeats parameterized command", () => {
160
+ const rep = makeRep(["hello world"]);
161
+ const { editorInfo } = makeMockEditorInfo();
162
+
163
+ state.lastCommand = { key: "m", count: 1, param: "a" };
164
+
165
+ const ctx = {
166
+ rep,
167
+ editorInfo,
168
+ line: 0,
169
+ char: 2,
170
+ lineText: "hello world",
171
+ };
172
+ commands.normal["."](ctx);
173
+
174
+ assert.deepEqual(state.marks["a"], [0, 2]);
175
+ });
176
+ });
177
+
178
+ describe("missing feature: zz center screen", () => {
179
+ beforeEach(resetState);
180
+
181
+ it("zz does nothing when editorDoc is null", () => {
182
+ state.editorDoc = null;
183
+ const rep = makeRep(["hello"]);
184
+ const { editorInfo } = makeMockEditorInfo();
185
+ const ctx = {
186
+ rep,
187
+ editorInfo,
188
+ line: 0,
189
+ char: 0,
190
+ lineText: "hello",
191
+ count: 1,
192
+ hasCount: false,
193
+ };
194
+
195
+ assert.doesNotThrow(() => commands.normal["zz"](ctx));
196
+ });
197
+ });
198
+
199
+ describe("zt and zb scroll commands", () => {
200
+ beforeEach(resetState);
201
+
202
+ it("zt does nothing when editorDoc is null", () => {
203
+ state.editorDoc = null;
204
+ const rep = makeRep(["hello"]);
205
+ const { editorInfo } = makeMockEditorInfo();
206
+ const ctx = {
207
+ rep,
208
+ editorInfo,
209
+ line: 0,
210
+ char: 0,
211
+ lineText: "hello",
212
+ count: 1,
213
+ hasCount: false,
214
+ };
215
+ assert.doesNotThrow(() => commands.normal["zt"](ctx));
216
+ });
217
+
218
+ it("zb does nothing when editorDoc is null", () => {
219
+ state.editorDoc = null;
220
+ const rep = makeRep(["hello"]);
221
+ const { editorInfo } = makeMockEditorInfo();
222
+ const ctx = {
223
+ rep,
224
+ editorInfo,
225
+ line: 0,
226
+ char: 0,
227
+ lineText: "hello",
228
+ count: 1,
229
+ hasCount: false,
230
+ };
231
+ assert.doesNotThrow(() => commands.normal["zb"](ctx));
232
+ });
233
+
234
+ it("zt calls scrollIntoView with block: start", () => {
235
+ const scrollCalls = [];
236
+ const mockLineDiv = {
237
+ scrollIntoView: (opts) => scrollCalls.push(opts),
238
+ };
239
+ state.editorDoc = {
240
+ body: {
241
+ querySelectorAll: () => [mockLineDiv],
242
+ },
243
+ };
244
+ const rep = makeRep(["hello"]);
245
+ const { editorInfo } = makeMockEditorInfo();
246
+ const ctx = {
247
+ rep,
248
+ editorInfo,
249
+ line: 0,
250
+ char: 0,
251
+ lineText: "hello",
252
+ count: 1,
253
+ hasCount: false,
254
+ };
255
+ commands.normal["zt"](ctx);
256
+ assert.equal(scrollCalls.length, 1);
257
+ assert.deepEqual(scrollCalls[0], { block: "start" });
258
+ });
259
+
260
+ it("zb calls scrollIntoView with block: end", () => {
261
+ const scrollCalls = [];
262
+ const mockLineDiv = {
263
+ scrollIntoView: (opts) => scrollCalls.push(opts),
264
+ };
265
+ state.editorDoc = {
266
+ body: {
267
+ querySelectorAll: () => [mockLineDiv],
268
+ },
269
+ };
270
+ const rep = makeRep(["hello"]);
271
+ const { editorInfo } = makeMockEditorInfo();
272
+ const ctx = {
273
+ rep,
274
+ editorInfo,
275
+ line: 0,
276
+ char: 0,
277
+ lineText: "hello",
278
+ count: 1,
279
+ hasCount: false,
280
+ };
281
+ commands.normal["zb"](ctx);
282
+ assert.equal(scrollCalls.length, 1);
283
+ assert.deepEqual(scrollCalls[0], { block: "end" });
284
+ });
285
+ });
286
+
287
+ describe("Ctrl+f and Ctrl+b page scroll", () => {
288
+ beforeEach(() => {
289
+ resetState();
290
+ setVimEnabled(true);
291
+ setUseCtrlKeys(true);
292
+ });
293
+
294
+ const makeCtrlEvt = (key) => ({
295
+ type: "keydown",
296
+ key,
297
+ ctrlKey: true,
298
+ metaKey: false,
299
+ target: { ownerDocument: null },
300
+ preventDefault: () => {},
301
+ });
302
+
303
+ it("Ctrl+f moves forward one full page", () => {
304
+ const lines = Array.from({ length: 50 }, (_, i) => `line${i}`);
305
+ const rep = makeRep(lines);
306
+ rep.selStart = [0, 0];
307
+ const { editorInfo, calls } = makeMockEditorInfo();
308
+ aceKeyEvent("aceKeyEvent", {
309
+ evt: makeCtrlEvt("f"),
310
+ rep,
311
+ editorInfo,
312
+ });
313
+ const selectCall = calls.find((c) => c.type === "select");
314
+ assert.ok(selectCall, "expected a select call");
315
+ assert.equal(selectCall.start[0], 30);
316
+ });
317
+
318
+ it("Ctrl+b moves backward one full page", () => {
319
+ const lines = Array.from({ length: 50 }, (_, i) => `line${i}`);
320
+ const rep = makeRep(lines);
321
+ rep.selStart = [40, 0];
322
+ state.mode = "normal";
323
+ const { editorInfo, calls } = makeMockEditorInfo();
324
+ aceKeyEvent("aceKeyEvent", {
325
+ evt: makeCtrlEvt("b"),
326
+ rep,
327
+ editorInfo,
328
+ });
329
+ const selectCall = calls.find((c) => c.type === "select");
330
+ assert.ok(selectCall, "expected a select call");
331
+ assert.equal(selectCall.start[0], 10);
332
+ });
333
+
334
+ it("Ctrl+f clamps at end of document", () => {
335
+ const lines = Array.from({ length: 10 }, (_, i) => `line${i}`);
336
+ const rep = makeRep(lines);
337
+ rep.selStart = [5, 0];
338
+ const { editorInfo, calls } = makeMockEditorInfo();
339
+ aceKeyEvent("aceKeyEvent", {
340
+ evt: makeCtrlEvt("f"),
341
+ rep,
342
+ editorInfo,
343
+ });
344
+ const selectCall = calls.find((c) => c.type === "select");
345
+ assert.ok(selectCall, "expected a select call");
346
+ assert.equal(selectCall.start[0], 9);
347
+ });
348
+
349
+ it("Ctrl+b clamps at start of document", () => {
350
+ const lines = Array.from({ length: 10 }, (_, i) => `line${i}`);
351
+ const rep = makeRep(lines);
352
+ rep.selStart = [3, 0];
353
+ const { editorInfo, calls } = makeMockEditorInfo();
354
+ aceKeyEvent("aceKeyEvent", {
355
+ evt: makeCtrlEvt("b"),
356
+ rep,
357
+ editorInfo,
358
+ });
359
+ const selectCall = calls.find((c) => c.type === "select");
360
+ assert.ok(selectCall, "expected a select call");
361
+ assert.equal(selectCall.start[0], 0);
362
+ });
363
+ });