Skip to content

Commit ae61a4f

Browse files
committed
add more tests
1 parent 58c74ea commit ae61a4f

File tree

1 file changed

+316
-6
lines changed

1 file changed

+316
-6
lines changed

cli/src/state/atoms/__tests__/shell.test.ts

Lines changed: 316 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,18 @@ import { createStore } from "jotai"
33
import { shellModeActiveAtom, toggleShellModeAtom, executeShellCommandAtom, keyboardHandlerAtom } from "../keyboard.js"
44
import { inputModeAtom } from "../ui.js"
55
import type { Key } from "../../../types/keyboard.js"
6-
import { shellHistoryAtom, shellHistoryIndexAtom } from "../shell.js"
6+
import {
7+
shellHistoryAtom,
8+
shellHistoryIndexAtom,
9+
navigateShellHistoryUpAtom,
10+
navigateShellHistoryDownAtom,
11+
addToShellHistoryAtom,
12+
} from "../shell.js"
13+
import { textBufferStringAtom } from "../textBuffer.js"
714

815
// Mock child_process to avoid actual command execution
916
vi.mock("child_process", () => ({
10-
exec: vi.fn((command, callback) => {
17+
exec: vi.fn((command) => {
1118
// Simulate successful command execution
1219
const stdout = `Mock output for: ${command}`
1320
const stderr = ""
@@ -32,14 +39,11 @@ vi.mock("child_process", () => ({
3239
}
3340
}),
3441
}
35-
if (callback) {
36-
callback(process)
37-
}
3842
return process
3943
}),
4044
}))
4145

42-
describe("shell mode - essential tests", () => {
46+
describe("shell mode - comprehensive tests", () => {
4347
let store: ReturnType<typeof createStore>
4448

4549
beforeEach(() => {
@@ -48,6 +52,7 @@ describe("shell mode - essential tests", () => {
4852
store.set(shellHistoryAtom, [])
4953
store.set(shellModeActiveAtom, false)
5054
store.set(inputModeAtom, "normal" as const)
55+
store.set(shellHistoryIndexAtom, -1)
5156
})
5257

5358
describe("shell mode activation", () => {
@@ -67,6 +72,50 @@ describe("shell mode - essential tests", () => {
6772
expect(store.get(inputModeAtom)).toBe("normal")
6873
})
6974

75+
it("should reset history index when toggling on", () => {
76+
// Set a non-default history index
77+
store.set(shellHistoryIndexAtom, 5)
78+
79+
// Toggle on
80+
store.set(toggleShellModeAtom)
81+
82+
// Index should be reset
83+
expect(store.get(shellHistoryIndexAtom)).toBe(-1)
84+
})
85+
86+
it("should reset history index when toggling off", () => {
87+
// Activate shell mode and set history index
88+
store.set(toggleShellModeAtom)
89+
store.set(shellHistoryIndexAtom, 3)
90+
91+
// Toggle off
92+
store.set(toggleShellModeAtom)
93+
94+
// Index should be reset
95+
expect(store.get(shellHistoryIndexAtom)).toBe(-1)
96+
})
97+
98+
it("should handle multiple rapid toggles", () => {
99+
// Toggle multiple times
100+
store.set(toggleShellModeAtom)
101+
expect(store.get(shellModeActiveAtom)).toBe(true)
102+
103+
store.set(toggleShellModeAtom)
104+
expect(store.get(shellModeActiveAtom)).toBe(false)
105+
106+
store.set(toggleShellModeAtom)
107+
expect(store.get(shellModeActiveAtom)).toBe(true)
108+
109+
store.set(toggleShellModeAtom)
110+
expect(store.get(shellModeActiveAtom)).toBe(false)
111+
112+
// Final state should be consistent
113+
expect(store.get(inputModeAtom)).toBe("normal")
114+
expect(store.get(shellHistoryIndexAtom)).toBe(-1)
115+
})
116+
})
117+
118+
describe("shell command execution", () => {
70119
it("should add commands to history", async () => {
71120
const command = "echo 'test'"
72121
await store.set(executeShellCommandAtom, command)
@@ -84,6 +133,23 @@ describe("shell mode - essential tests", () => {
84133
expect(history).toHaveLength(0)
85134
})
86135

136+
it("should trim whitespace from commands before adding to history", async () => {
137+
const command = " echo 'test' "
138+
await store.set(executeShellCommandAtom, command)
139+
140+
const history = store.get(shellHistoryAtom)
141+
expect(history[0]).toBe("echo 'test'")
142+
})
143+
144+
it("should add multiple unique commands to history", async () => {
145+
await store.set(executeShellCommandAtom, "ls")
146+
await store.set(executeShellCommandAtom, "pwd")
147+
await store.set(executeShellCommandAtom, "echo test")
148+
149+
const history = store.get(shellHistoryAtom)
150+
expect(history).toEqual(["ls", "pwd", "echo test"])
151+
})
152+
87153
it("should reset history navigation index after command execution", async () => {
88154
// Add a few commands to history
89155
await store.set(executeShellCommandAtom, "echo 'test1'")
@@ -99,6 +165,189 @@ describe("shell mode - essential tests", () => {
99165
// History index should be reset to -1
100166
expect(store.get(shellHistoryIndexAtom)).toBe(-1)
101167
})
168+
169+
it("should allow duplicate commands in history", async () => {
170+
await store.set(executeShellCommandAtom, "echo test")
171+
await store.set(executeShellCommandAtom, "ls")
172+
await store.set(executeShellCommandAtom, "echo test")
173+
174+
const history = store.get(shellHistoryAtom)
175+
expect(history).toEqual(["echo test", "ls", "echo test"])
176+
})
177+
})
178+
179+
describe("shell history management", () => {
180+
it("should limit history to 100 commands", async () => {
181+
// Add 105 commands
182+
for (let i = 0; i < 105; i++) {
183+
await store.set(executeShellCommandAtom, `command${i}`)
184+
}
185+
186+
const history = store.get(shellHistoryAtom)
187+
expect(history).toHaveLength(100)
188+
// Should keep the most recent 100
189+
expect(history[0]).toBe("command5")
190+
expect(history[99]).toBe("command104")
191+
})
192+
193+
it("should add command to history with addToShellHistoryAtom", () => {
194+
store.set(addToShellHistoryAtom, "test command")
195+
const history = store.get(shellHistoryAtom)
196+
expect(history).toContain("test command")
197+
})
198+
199+
it("should maintain history order (newest last)", async () => {
200+
await store.set(executeShellCommandAtom, "first")
201+
await store.set(executeShellCommandAtom, "second")
202+
await store.set(executeShellCommandAtom, "third")
203+
204+
const history = store.get(shellHistoryAtom)
205+
expect(history).toEqual(["first", "second", "third"])
206+
})
207+
})
208+
209+
describe("history navigation - up", () => {
210+
it("should navigate to most recent command on first up", () => {
211+
// Add commands to history
212+
store.set(shellHistoryAtom, ["cmd1", "cmd2", "cmd3"])
213+
214+
// Navigate up
215+
store.set(navigateShellHistoryUpAtom)
216+
217+
expect(store.get(shellHistoryIndexAtom)).toBe(2)
218+
expect(store.get(textBufferStringAtom)).toBe("cmd3")
219+
})
220+
221+
it("should navigate to older commands with successive up presses", () => {
222+
store.set(shellHistoryAtom, ["cmd1", "cmd2", "cmd3"])
223+
224+
// First up - most recent
225+
store.set(navigateShellHistoryUpAtom)
226+
expect(store.get(shellHistoryIndexAtom)).toBe(2)
227+
expect(store.get(textBufferStringAtom)).toBe("cmd3")
228+
229+
// Second up - older
230+
store.set(navigateShellHistoryUpAtom)
231+
expect(store.get(shellHistoryIndexAtom)).toBe(1)
232+
expect(store.get(textBufferStringAtom)).toBe("cmd2")
233+
234+
// Third up - oldest
235+
store.set(navigateShellHistoryUpAtom)
236+
expect(store.get(shellHistoryIndexAtom)).toBe(0)
237+
expect(store.get(textBufferStringAtom)).toBe("cmd1")
238+
})
239+
240+
it("should stop at oldest command", () => {
241+
store.set(shellHistoryAtom, ["cmd1", "cmd2"])
242+
243+
// Navigate to oldest
244+
store.set(navigateShellHistoryUpAtom)
245+
store.set(navigateShellHistoryUpAtom)
246+
expect(store.get(shellHistoryIndexAtom)).toBe(0)
247+
248+
// Try to go further up
249+
store.set(navigateShellHistoryUpAtom)
250+
expect(store.get(shellHistoryIndexAtom)).toBe(0)
251+
expect(store.get(textBufferStringAtom)).toBe("cmd1")
252+
})
253+
254+
it("should do nothing when history is empty", () => {
255+
store.set(shellHistoryAtom, [])
256+
257+
store.set(navigateShellHistoryUpAtom)
258+
259+
expect(store.get(shellHistoryIndexAtom)).toBe(-1)
260+
expect(store.get(textBufferStringAtom)).toBe("")
261+
})
262+
263+
it("should handle single command history", () => {
264+
store.set(shellHistoryAtom, ["only-cmd"])
265+
266+
store.set(navigateShellHistoryUpAtom)
267+
expect(store.get(shellHistoryIndexAtom)).toBe(0)
268+
expect(store.get(textBufferStringAtom)).toBe("only-cmd")
269+
270+
// Try to go up again
271+
store.set(navigateShellHistoryUpAtom)
272+
expect(store.get(shellHistoryIndexAtom)).toBe(0)
273+
})
274+
})
275+
276+
describe("history navigation - down", () => {
277+
it("should do nothing when at default index", () => {
278+
store.set(shellHistoryAtom, ["cmd1", "cmd2", "cmd3"])
279+
expect(store.get(shellHistoryIndexAtom)).toBe(-1)
280+
281+
store.set(navigateShellHistoryDownAtom)
282+
283+
expect(store.get(shellHistoryIndexAtom)).toBe(-1)
284+
})
285+
286+
it("should navigate to newer commands", () => {
287+
store.set(shellHistoryAtom, ["cmd1", "cmd2", "cmd3", "cmd4"])
288+
289+
// Go to oldest
290+
store.set(shellHistoryIndexAtom, 0)
291+
292+
// Navigate down to newer
293+
store.set(navigateShellHistoryDownAtom)
294+
expect(store.get(shellHistoryIndexAtom)).toBe(1)
295+
expect(store.get(textBufferStringAtom)).toBe("cmd2")
296+
297+
store.set(navigateShellHistoryDownAtom)
298+
expect(store.get(shellHistoryIndexAtom)).toBe(2)
299+
expect(store.get(textBufferStringAtom)).toBe("cmd3")
300+
})
301+
302+
it("should clear input when reaching most recent", () => {
303+
store.set(shellHistoryAtom, ["cmd1", "cmd2"])
304+
305+
// Navigate up and then back down
306+
store.set(navigateShellHistoryUpAtom) // index 1 (cmd2)
307+
store.set(navigateShellHistoryUpAtom) // index 0 (cmd1)
308+
store.set(navigateShellHistoryDownAtom) // index 1 (cmd2)
309+
store.set(navigateShellHistoryDownAtom) // index -1 (clear)
310+
311+
expect(store.get(shellHistoryIndexAtom)).toBe(-1)
312+
expect(store.get(textBufferStringAtom)).toBe("")
313+
})
314+
315+
it("should handle navigation cycle: up then all the way down", () => {
316+
store.set(shellHistoryAtom, ["cmd1", "cmd2", "cmd3"])
317+
318+
// Go up to recent
319+
store.set(navigateShellHistoryUpAtom)
320+
expect(store.get(textBufferStringAtom)).toBe("cmd3")
321+
322+
// Go all the way down to clear
323+
store.set(navigateShellHistoryDownAtom)
324+
expect(store.get(shellHistoryIndexAtom)).toBe(-1)
325+
expect(store.get(textBufferStringAtom)).toBe("")
326+
})
327+
})
328+
329+
describe("history navigation - combined up/down", () => {
330+
it("should handle mixed up/down navigation", () => {
331+
store.set(shellHistoryAtom, ["cmd1", "cmd2", "cmd3", "cmd4"])
332+
333+
// Up twice
334+
store.set(navigateShellHistoryUpAtom) // cmd4
335+
store.set(navigateShellHistoryUpAtom) // cmd3
336+
expect(store.get(textBufferStringAtom)).toBe("cmd3")
337+
338+
// Down once
339+
store.set(navigateShellHistoryDownAtom) // cmd4
340+
expect(store.get(textBufferStringAtom)).toBe("cmd4")
341+
342+
// Up once
343+
store.set(navigateShellHistoryUpAtom) // cmd3
344+
expect(store.get(textBufferStringAtom)).toBe("cmd3")
345+
346+
// Up to oldest
347+
store.set(navigateShellHistoryUpAtom) // cmd2
348+
store.set(navigateShellHistoryUpAtom) // cmd1
349+
expect(store.get(textBufferStringAtom)).toBe("cmd1")
350+
})
102351
})
103352

104353
describe("Shift+1 key detection", () => {
@@ -119,5 +368,66 @@ describe("shell mode - essential tests", () => {
119368
expect(store.get(shellModeActiveAtom)).toBe(true)
120369
expect(store.get(inputModeAtom)).toBe("shell")
121370
})
371+
372+
it("should toggle shell mode off with second Shift+1", async () => {
373+
const shift1Key: Key = {
374+
name: "shift-1",
375+
sequence: "!",
376+
ctrl: false,
377+
meta: false,
378+
shift: true,
379+
paste: false,
380+
}
381+
382+
// Activate
383+
await store.set(keyboardHandlerAtom, shift1Key)
384+
expect(store.get(shellModeActiveAtom)).toBe(true)
385+
386+
// Deactivate
387+
await store.set(keyboardHandlerAtom, shift1Key)
388+
expect(store.get(shellModeActiveAtom)).toBe(false)
389+
expect(store.get(inputModeAtom)).toBe("normal")
390+
})
391+
})
392+
393+
describe("edge cases", () => {
394+
it("should handle empty string command gracefully", async () => {
395+
await store.set(executeShellCommandAtom, "")
396+
const history = store.get(shellHistoryAtom)
397+
expect(history).toHaveLength(0)
398+
})
399+
400+
it("should handle only whitespace command", async () => {
401+
await store.set(executeShellCommandAtom, " \t\n ")
402+
const history = store.get(shellHistoryAtom)
403+
expect(history).toHaveLength(0)
404+
})
405+
406+
it("should preserve history when toggling shell mode", () => {
407+
store.set(shellHistoryAtom, ["cmd1", "cmd2"])
408+
409+
// Toggle on and off
410+
store.set(toggleShellModeAtom)
411+
store.set(toggleShellModeAtom)
412+
413+
// History should be preserved
414+
const history = store.get(shellHistoryAtom)
415+
expect(history).toEqual(["cmd1", "cmd2"])
416+
})
417+
418+
it("should handle history navigation after clearing history", () => {
419+
store.set(shellHistoryAtom, ["cmd1", "cmd2"])
420+
store.set(navigateShellHistoryUpAtom)
421+
expect(store.get(textBufferStringAtom)).toBe("cmd2")
422+
const indexBeforeClear = store.get(shellHistoryIndexAtom)
423+
424+
// Clear history
425+
store.set(shellHistoryAtom, [])
426+
427+
// Try to navigate - should return early and not change index
428+
store.set(navigateShellHistoryUpAtom)
429+
// Index should remain unchanged when history is empty
430+
expect(store.get(shellHistoryIndexAtom)).toBe(indexBeforeClear)
431+
})
122432
})
123433
})

0 commit comments

Comments
 (0)