Skip to content

Commit d217219

Browse files
[fix]: lots of cli input improvements (#379)
1 parent 98310db commit d217219

File tree

11 files changed

+1781
-184
lines changed

11 files changed

+1781
-184
lines changed
Lines changed: 397 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,397 @@
1+
import { describe, test, expect, beforeEach, mock } from 'bun:test'
2+
3+
/**
4+
* Tests for bash mode functionality in the CLI.
5+
*
6+
* Bash mode is entered when user types '!' and allows running terminal commands.
7+
* The '!' is displayed in a red column but not stored in the input value.
8+
*
9+
* Key behaviors:
10+
* 1. Typing '!' enters bash mode and clears input to ''
11+
* 2. In bash mode, input is stored WITHOUT '!' prefix
12+
* 3. Backspace at cursor position 0 exits bash mode (even with input)
13+
* 4. Submission prepends '!' to the command
14+
*/
15+
16+
describe('bash-mode', () => {
17+
describe('entering bash mode', () => {
18+
test('typing exactly "!" enters bash mode and clears input', () => {
19+
const setBashMode = mock(() => {})
20+
const setInputValue = mock((value: any) => {})
21+
22+
// Simulate user typing '!'
23+
const inputValue = { text: '!', cursorPosition: 1, lastEditDueToNav: false }
24+
const isBashMode = false
25+
26+
// This simulates the handleInputChange logic
27+
const userTypedBang = !isBashMode && inputValue.text === '!'
28+
29+
if (userTypedBang) {
30+
setBashMode()
31+
const newValue = {
32+
text: '',
33+
cursorPosition: 0,
34+
lastEditDueToNav: inputValue.lastEditDueToNav,
35+
}
36+
setInputValue(newValue)
37+
}
38+
39+
expect(setBashMode).toHaveBeenCalled()
40+
expect(setInputValue).toHaveBeenCalled()
41+
})
42+
43+
test('typing "!ls" does NOT enter bash mode (not exactly "!")', () => {
44+
const setBashMode = mock(() => {})
45+
const setInputValue = mock((value: any) => {})
46+
47+
// Simulate user typing '!ls'
48+
const inputValue = { text: '!ls', cursorPosition: 3, lastEditDueToNav: false }
49+
const isBashMode = false
50+
51+
const userTypedBang = !isBashMode && inputValue.text === '!'
52+
53+
if (userTypedBang) {
54+
setBashMode()
55+
const newValue = {
56+
text: '',
57+
cursorPosition: 0,
58+
lastEditDueToNav: inputValue.lastEditDueToNav,
59+
}
60+
setInputValue(newValue)
61+
}
62+
63+
expect(setBashMode).not.toHaveBeenCalled()
64+
expect(setInputValue).not.toHaveBeenCalled()
65+
})
66+
67+
test('typing "!" when already in bash mode does nothing special', () => {
68+
const setBashMode = mock(() => {})
69+
const setInputValue = mock((value: any) => {})
70+
71+
const inputValue = { text: '!', cursorPosition: 1, lastEditDueToNav: false }
72+
const isBashMode = true
73+
74+
const userTypedBang = !isBashMode && inputValue.text === '!'
75+
76+
if (userTypedBang) {
77+
setBashMode()
78+
const newValue = {
79+
text: '',
80+
cursorPosition: 0,
81+
lastEditDueToNav: inputValue.lastEditDueToNav,
82+
}
83+
setInputValue(newValue)
84+
}
85+
86+
// Should not trigger because already in bash mode
87+
expect(setBashMode).not.toHaveBeenCalled()
88+
expect(setInputValue).not.toHaveBeenCalled()
89+
})
90+
})
91+
92+
describe('exiting bash mode', () => {
93+
test('backspace at cursor position 0 exits bash mode', () => {
94+
const setBashMode = mock(() => {})
95+
96+
// Simulate backspace key press in bash mode at cursor position 0
97+
const isBashMode = true
98+
const cursorPosition = 0
99+
const key = { name: 'backspace' }
100+
101+
// This simulates the handleSuggestionMenuKey logic
102+
if (isBashMode && cursorPosition === 0 && key.name === 'backspace') {
103+
setBashMode()
104+
}
105+
106+
expect(setBashMode).toHaveBeenCalled()
107+
})
108+
109+
test('backspace at cursor position 0 with non-empty input DOES exit bash mode', () => {
110+
const setBashMode = mock(() => {})
111+
112+
const isBashMode = true
113+
const inputValue: string = 'ls'
114+
const cursorPosition = 0
115+
const key = { name: 'backspace' }
116+
117+
if (isBashMode && cursorPosition === 0 && key.name === 'backspace') {
118+
setBashMode()
119+
}
120+
121+
// Should exit even though input is not empty, because cursor is at position 0
122+
expect(setBashMode).toHaveBeenCalled()
123+
})
124+
125+
test('backspace at cursor position > 0 does NOT exit bash mode', () => {
126+
const setBashMode = mock(() => {})
127+
128+
const isBashMode = true
129+
const cursorPosition: number = 2
130+
const key = { name: 'backspace' }
131+
132+
if (isBashMode && cursorPosition === 0 && key.name === 'backspace') {
133+
setBashMode()
134+
}
135+
136+
// Should not exit because cursor is not at position 0
137+
expect(setBashMode).not.toHaveBeenCalled()
138+
})
139+
140+
test('other keys at cursor position 0 do NOT exit bash mode', () => {
141+
const setBashMode = mock(() => {})
142+
143+
const isBashMode = true
144+
const cursorPosition = 0
145+
const key = { name: 'a' } // Regular key press
146+
147+
if (isBashMode && cursorPosition === 0 && key.name === 'backspace') {
148+
setBashMode()
149+
}
150+
151+
// Should not exit because key is not backspace
152+
expect(setBashMode).not.toHaveBeenCalled()
153+
})
154+
155+
test('backspace when NOT in bash mode does nothing to bash mode', () => {
156+
const setBashMode = mock(() => {})
157+
158+
const isBashMode = false
159+
const cursorPosition = 0
160+
const key = { name: 'backspace' }
161+
162+
if (isBashMode && cursorPosition === 0 && key.name === 'backspace') {
163+
setBashMode()
164+
}
165+
166+
// Should not trigger because not in bash mode
167+
expect(setBashMode).not.toHaveBeenCalled()
168+
})
169+
})
170+
171+
describe('bash mode input storage', () => {
172+
test('input value does NOT include "!" prefix while in bash mode', () => {
173+
// When user types "ls" in bash mode, inputValue.text should be "ls", not "!ls"
174+
const isBashMode = true
175+
const inputValue = 'ls -la'
176+
177+
// The stored value should NOT have the '!' prefix
178+
expect(inputValue).toBe('ls -la')
179+
expect(inputValue).not.toContain('!')
180+
})
181+
182+
test('normal mode input can contain "!" anywhere', () => {
183+
const isBashMode = false
184+
const inputValue = 'fix this bug!'
185+
186+
// In normal mode, '!' is just a regular character
187+
expect(inputValue).toContain('!')
188+
})
189+
})
190+
191+
describe('bash mode submission', () => {
192+
test('submitting bash command prepends "!" to the stored value', () => {
193+
const isBashMode = true
194+
const trimmedInput = 'ls -la' // The stored value WITHOUT '!'
195+
196+
// Router logic prepends '!' when in bash mode
197+
const commandWithBang = '!' + trimmedInput
198+
199+
expect(commandWithBang).toBe('!ls -la')
200+
})
201+
202+
test('submission displays "!" in user message', () => {
203+
const isBashMode = true
204+
const trimmedInput = 'pwd'
205+
const commandWithBang = '!' + trimmedInput
206+
207+
// The user message should show the command WITH '!'
208+
const userMessage = { content: commandWithBang }
209+
210+
expect(userMessage.content).toBe('!pwd')
211+
})
212+
213+
test('submission saves command WITH "!" to history', () => {
214+
const saveToHistory = mock((cmd: string) => {})
215+
const isBashMode = true
216+
const trimmedInput = 'git status'
217+
const commandWithBang = '!' + trimmedInput
218+
219+
// History should save the full command with '!'
220+
saveToHistory(commandWithBang)
221+
222+
expect(saveToHistory).toHaveBeenCalled()
223+
})
224+
225+
test('submission exits bash mode after running command', () => {
226+
const setBashMode = mock(() => {})
227+
const isBashMode = true
228+
229+
// After submission, bash mode should be exited
230+
setBashMode()
231+
232+
expect(setBashMode).toHaveBeenCalled()
233+
})
234+
235+
test('terminal command receives value WITHOUT "!" prefix', () => {
236+
const runTerminalCommand = mock((params: any) => Promise.resolve([{ value: { stdout: 'output' } }]))
237+
const isBashMode = true
238+
const trimmedInput = 'echo hello'
239+
240+
// The actual terminal command should NOT include the '!'
241+
runTerminalCommand({
242+
command: trimmedInput,
243+
process_type: 'SYNC',
244+
cwd: process.cwd(),
245+
timeout_seconds: -1,
246+
env: process.env,
247+
})
248+
249+
expect(runTerminalCommand).toHaveBeenCalled()
250+
})
251+
})
252+
253+
describe('bash mode UI state', () => {
254+
test('bash mode flag is stored separately from input value', () => {
255+
// The isBashMode flag is independent of the input text
256+
const state1 = { isBashMode: true, inputValue: 'ls' }
257+
const state2 = { isBashMode: false, inputValue: 'hello' }
258+
259+
expect(state1.isBashMode).toBe(true)
260+
expect(state1.inputValue).not.toContain('!')
261+
262+
expect(state2.isBashMode).toBe(false)
263+
expect(state2.inputValue).not.toContain('!')
264+
})
265+
266+
test('input width is adjusted in bash mode for "!" column', () => {
267+
const baseInputWidth = 100
268+
const isBashMode = true
269+
270+
// Width should be reduced by 2 to account for '!' and spacing
271+
const adjustedInputWidth = isBashMode ? baseInputWidth - 2 : baseInputWidth
272+
273+
expect(adjustedInputWidth).toBe(98)
274+
})
275+
276+
test('input width is NOT adjusted when not in bash mode', () => {
277+
const baseInputWidth = 100
278+
const isBashMode = false
279+
280+
const adjustedInputWidth = isBashMode ? baseInputWidth - 2 : baseInputWidth
281+
282+
expect(adjustedInputWidth).toBe(100)
283+
})
284+
285+
test('placeholder changes in bash mode', () => {
286+
const normalPlaceholder = 'Ask Buffy anything...'
287+
const bashPlaceholder = 'enter bash command...'
288+
const isBashMode = true
289+
290+
const effectivePlaceholder = isBashMode ? bashPlaceholder : normalPlaceholder
291+
292+
expect(effectivePlaceholder).toBe('enter bash command...')
293+
})
294+
295+
test('placeholder is normal when not in bash mode', () => {
296+
const normalPlaceholder = 'Ask Buffy anything...'
297+
const bashPlaceholder = 'enter bash command...'
298+
const isBashMode = false
299+
300+
const effectivePlaceholder = isBashMode ? bashPlaceholder : normalPlaceholder
301+
302+
expect(effectivePlaceholder).toBe('Ask Buffy anything...')
303+
})
304+
})
305+
306+
describe('edge cases', () => {
307+
test('empty string is NOT the same as "!"', () => {
308+
const isBashMode = false
309+
const inputValue: string = ''
310+
const exclamation = '!'
311+
const inputEqualsExclamation = inputValue === exclamation
312+
313+
expect(inputEqualsExclamation).toBe(false)
314+
})
315+
316+
test('whitespace around "!" prevents bash mode entry', () => {
317+
const isBashMode = false
318+
const exclamation = '!'
319+
const inputValue1: string = ' !'
320+
const inputValue2: string = '! '
321+
const inputValue3: string = ' ! '
322+
323+
const match1 = inputValue1 === exclamation
324+
const match2 = inputValue2 === exclamation
325+
const match3 = inputValue3 === exclamation
326+
327+
expect(match1).toBe(false)
328+
expect(match2).toBe(false)
329+
expect(match3).toBe(false)
330+
})
331+
332+
test('multiple "!" characters do not enter bash mode', () => {
333+
const isBashMode = false
334+
const inputValue: string = '!!'
335+
const exclamation = '!'
336+
const inputEqualsExclamation = inputValue === exclamation
337+
338+
expect(inputEqualsExclamation).toBe(false)
339+
})
340+
341+
test('bash mode can be entered, exited, and re-entered', () => {
342+
let isBashMode = false
343+
const exclamation = '!'
344+
const empty = ''
345+
346+
// Enter bash mode
347+
if (exclamation === exclamation) {
348+
isBashMode = true
349+
}
350+
expect(isBashMode).toBe(true)
351+
352+
// Exit bash mode
353+
if (isBashMode && empty === empty) {
354+
isBashMode = false
355+
}
356+
expect(isBashMode).toBe(false)
357+
358+
// Re-enter bash mode
359+
if (!isBashMode && exclamation === exclamation) {
360+
isBashMode = true
361+
}
362+
expect(isBashMode).toBe(true)
363+
})
364+
})
365+
366+
describe('integration with command router', () => {
367+
test('bash mode commands are routed differently than normal prompts', () => {
368+
const isBashMode = true
369+
const normalPrompt = false
370+
371+
// In bash mode, commands should be handled by terminal execution
372+
// Not by the LLM agent
373+
expect(isBashMode).toBe(true)
374+
expect(normalPrompt).toBe(false)
375+
})
376+
377+
test('normal commands starting with "!" are NOT bash commands', () => {
378+
const isBashMode = false
379+
const inputValue = '!ls' // User typed this in normal mode
380+
381+
// This should be treated as a normal prompt, not a bash command
382+
// because bash mode was not activated
383+
expect(isBashMode).toBe(false)
384+
})
385+
386+
test('bash mode takes precedence over slash commands', () => {
387+
const isBashMode = true
388+
const trimmedInput = '/help' // Looks like a slash command
389+
390+
// But in bash mode, it's just a bash command
391+
if (isBashMode) {
392+
const commandWithBang = '!' + trimmedInput
393+
expect(commandWithBang).toBe('!/help')
394+
}
395+
})
396+
})
397+
})

0 commit comments

Comments
 (0)