Skip to content

Commit 0c42bbe

Browse files
committed
refactor(cli): simplify handleImageCommand and add tests
- Simplify handleImageCommand to return just a string instead of complex object - Use split() instead of regex for parsing - Remove unused postUserMessage callback pattern - Add unit tests for argument parsing behavior
1 parent 754f3aa commit 0c42bbe

File tree

3 files changed

+106
-37
lines changed

3 files changed

+106
-37
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { describe, test, expect } from 'bun:test'
2+
3+
/**
4+
* Tests for the handleImageCommand argument parsing behavior.
5+
*
6+
* These tests verify the parsing logic independently of the actual
7+
* validateAndAddImage implementation by testing the parsing function directly.
8+
*/
9+
10+
// Extract the parsing logic that handleImageCommand uses
11+
// New simplified implementation: split on whitespace
12+
function parseImageCommandArgs(args: string): {
13+
imagePath: string | null
14+
message: string
15+
} {
16+
const [imagePath, ...rest] = args.trim().split(/\s+/)
17+
18+
if (!imagePath) {
19+
return { imagePath: null, message: '' }
20+
}
21+
22+
return { imagePath, message: rest.join(' ') }
23+
}
24+
25+
describe('handleImageCommand parsing', () => {
26+
describe('argument parsing', () => {
27+
test('parses image path only', () => {
28+
const result = parseImageCommandArgs('./screenshot.png')
29+
expect(result.imagePath).toBe('./screenshot.png')
30+
expect(result.message).toBe('')
31+
})
32+
33+
test('parses image path with message', () => {
34+
const result = parseImageCommandArgs('./screenshot.png please analyze this')
35+
expect(result.imagePath).toBe('./screenshot.png')
36+
expect(result.message).toBe('please analyze this')
37+
})
38+
39+
test('parses image path with multi-word message', () => {
40+
const result = parseImageCommandArgs('./image.jpg what is in this picture?')
41+
expect(result.imagePath).toBe('./image.jpg')
42+
expect(result.message).toBe('what is in this picture?')
43+
})
44+
45+
test('handles absolute paths with message', () => {
46+
const result = parseImageCommandArgs('/path/to/file.png describe the UI')
47+
expect(result.imagePath).toBe('/path/to/file.png')
48+
expect(result.message).toBe('describe the UI')
49+
})
50+
51+
test('trims whitespace from input', () => {
52+
const result = parseImageCommandArgs(' ./image.png ')
53+
expect(result.imagePath).toBe('./image.png')
54+
expect(result.message).toBe('')
55+
})
56+
57+
test('handles multiple spaces between path and message', () => {
58+
const result = parseImageCommandArgs('./image.png hello world')
59+
expect(result.imagePath).toBe('./image.png')
60+
// The regex only captures content after the first whitespace group
61+
expect(result.message).toBe('hello world')
62+
})
63+
})
64+
65+
describe('invalid input handling', () => {
66+
test('returns null imagePath for empty input', () => {
67+
const result = parseImageCommandArgs('')
68+
expect(result.imagePath).toBeNull()
69+
expect(result.message).toBe('')
70+
})
71+
72+
test('returns null imagePath for whitespace-only input', () => {
73+
const result = parseImageCommandArgs(' ')
74+
expect(result.imagePath).toBeNull()
75+
expect(result.message).toBe('')
76+
})
77+
})
78+
79+
describe('edge cases', () => {
80+
test('handles filenames with extensions', () => {
81+
const result = parseImageCommandArgs('image.jpeg')
82+
expect(result.imagePath).toBe('image.jpeg')
83+
})
84+
85+
test('handles relative paths', () => {
86+
const result = parseImageCommandArgs('../screenshots/test.png')
87+
expect(result.imagePath).toBe('../screenshots/test.png')
88+
})
89+
90+
test('handles tilde paths', () => {
91+
const result = parseImageCommandArgs('~/Downloads/image.png')
92+
expect(result.imagePath).toBe('~/Downloads/image.png')
93+
})
94+
})
95+
})

cli/src/commands/command-registry.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,7 @@ export const COMMAND_REGISTRY: CommandDefinition[] = [
223223

224224
// If user provided a path directly, process it immediately
225225
if (trimmedArgs) {
226-
const result = await handleImageCommand(trimmedArgs)
227-
params.setMessages((prev) => result.postUserMessage(prev))
226+
await handleImageCommand(trimmedArgs)
228227
params.saveToHistory(params.inputValue.trim())
229228
clearInput(params)
230229
return

cli/src/commands/image.ts

Lines changed: 10 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,20 @@
11
import { getProjectRoot } from '../project-files'
22
import { validateAndAddImage } from '../utils/add-pending-image'
3-
import { getSystemMessage } from '../utils/message-history'
4-
5-
import type { PostUserMessageFn } from '../types/contracts/send-message'
63

74
/**
85
* Handle the /image command to attach an image file.
96
* Usage: /image <path> [message]
107
* Example: /image ./screenshot.png please analyze this
8+
*
9+
* Returns the optional message as transformedPrompt (empty string if none).
10+
* Errors are shown in the pending images banner with auto-remove.
1111
*/
12-
export async function handleImageCommand(args: string): Promise<{
13-
postUserMessage: PostUserMessageFn
14-
transformedPrompt?: string
15-
}> {
16-
const trimmedArgs = args.trim()
17-
18-
// Parse the path and optional message
19-
// The path is the first argument (up to first space or the whole string)
20-
const parts = trimmedArgs.match(/^(\S+)(?:\s+(.*))?$/)
21-
if (!parts) {
22-
const postUserMessage: PostUserMessageFn = (prev) => [
23-
...prev,
24-
getSystemMessage('❌ Invalid image command format. Use: /image <path> [message]'),
25-
]
26-
return { postUserMessage }
27-
}
28-
29-
const [, imagePath, message] = parts
30-
const projectRoot = getProjectRoot()
31-
32-
// Validate and add the image (handles path resolution, format check, and processing)
33-
// Errors are shown in the pending images banner with auto-remove
34-
await validateAndAddImage(imagePath, projectRoot)
35-
36-
// Use the optional message as the prompt, or empty to just attach the image
37-
const transformedPrompt = message || ''
38-
39-
const postUserMessage: PostUserMessageFn = (prev) => prev
40-
41-
return {
42-
postUserMessage,
43-
transformedPrompt,
12+
export async function handleImageCommand(args: string): Promise<string> {
13+
const [imagePath, ...rest] = args.trim().split(/\s+/)
14+
15+
if (imagePath) {
16+
await validateAndAddImage(imagePath, getProjectRoot())
4417
}
18+
19+
return rest.join(' ')
4520
}

0 commit comments

Comments
 (0)