mirror of
https://github.com/samsonjs/Peekaboo.git
synced 2026-04-19 13:35:51 +00:00
425 lines
No EOL
20 KiB
TypeScript
425 lines
No EOL
20 KiB
TypeScript
import { vi } from 'vitest';
|
|
import { imageToolHandler, buildSwiftCliArgs, ImageToolInput } from '../../../src/tools/image';
|
|
import { executeSwiftCli, readImageAsBase64 } from '../../../src/utils/peekaboo-cli';
|
|
import { mockSwiftCli } from '../../mocks/peekaboo-cli.mock';
|
|
import { pino } from 'pino';
|
|
import { SavedFile, ImageCaptureData, AIProviderConfig, ToolResponse, AIProvider } from '../../../src/types';
|
|
import * as fs from 'fs/promises';
|
|
import * as os from 'os';
|
|
import * as pathModule from 'path';
|
|
|
|
// Mock the Swift CLI utility
|
|
vi.mock('../../../src/utils/peekaboo-cli');
|
|
|
|
// Mock AI Provider utilities
|
|
// Declare the variables that will hold the mock functions first
|
|
let mockDetermineProviderAndModel: vi.MockedFunction<any>;
|
|
let mockAnalyzeImageWithProvider: vi.MockedFunction<any>;
|
|
let mockParseAIProviders: vi.MockedFunction<any>;
|
|
let mockIsProviderAvailable: vi.MockedFunction<any>;
|
|
let mockGetDefaultModelForProvider: vi.MockedFunction<any>;
|
|
|
|
vi.mock('../../../src/utils/ai-providers', () => {
|
|
// Create new vi.fn() instances inside the factory
|
|
const determineProviderAndModel = vi.fn();
|
|
const analyzeImageWithProvider = vi.fn();
|
|
const parseAIProviders = vi.fn();
|
|
const isProviderAvailable = vi.fn();
|
|
const getDefaultModelForProvider = vi.fn().mockReturnValue('default-model');
|
|
|
|
// Assign them to the outer scope variables so tests can reference them
|
|
// This assignment happens AFTER the vi.mock call is processed by Vitest due to hoisting.
|
|
// We will re-assign these correctly after the mock call using an import.
|
|
return {
|
|
determineProviderAndModel,
|
|
analyzeImageWithProvider,
|
|
parseAIProviders,
|
|
isProviderAvailable,
|
|
getDefaultModelForProvider
|
|
};
|
|
});
|
|
|
|
// Mock fs/promises for mkdtemp, unlink, rmdir
|
|
vi.mock('fs/promises');
|
|
|
|
// Now, import the mocked module and assign the vi.fn() instances to our variables
|
|
// This ensures our variables hold the actual mocks created by Vitest's factory.
|
|
import * as ActualAiProvidersMock from '../../../src/utils/ai-providers';
|
|
mockDetermineProviderAndModel = ActualAiProvidersMock.determineProviderAndModel as vi.MockedFunction<any>;
|
|
mockAnalyzeImageWithProvider = ActualAiProvidersMock.analyzeImageWithProvider as vi.MockedFunction<any>;
|
|
mockParseAIProviders = ActualAiProvidersMock.parseAIProviders as vi.MockedFunction<any>;
|
|
mockIsProviderAvailable = ActualAiProvidersMock.isProviderAvailable as vi.MockedFunction<any>;
|
|
mockGetDefaultModelForProvider = ActualAiProvidersMock.getDefaultModelForProvider as vi.MockedFunction<any>;
|
|
|
|
const mockExecuteSwiftCli = executeSwiftCli as vi.MockedFunction<typeof executeSwiftCli>;
|
|
const mockReadImageAsBase64 = readImageAsBase64 as vi.MockedFunction<typeof readImageAsBase64>;
|
|
|
|
const mockFsMkdtemp = fs.mkdtemp as vi.MockedFunction<typeof fs.mkdtemp>;
|
|
const mockFsUnlink = fs.unlink as vi.MockedFunction<typeof fs.unlink>;
|
|
const mockFsRmdir = fs.rmdir as vi.MockedFunction<typeof fs.rmdir>;
|
|
|
|
const mockLogger = pino({ level: 'silent' });
|
|
const mockContext = { logger: mockLogger };
|
|
|
|
const MOCK_TEMP_DIR_PATH = '/tmp/peekaboo-img-mock';
|
|
const MOCK_TEMP_IMAGE_PATH = `${MOCK_TEMP_DIR_PATH}/capture.png`;
|
|
|
|
describe('Image Tool', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockFsMkdtemp.mockResolvedValue(MOCK_TEMP_DIR_PATH);
|
|
mockFsUnlink.mockResolvedValue(undefined);
|
|
mockFsRmdir.mockResolvedValue(undefined);
|
|
process.env.PEEKABOO_AI_PROVIDERS = '';
|
|
|
|
// Ensure specific mock implementations are reset/re-set for each test or suite as needed
|
|
// The functions themselves are already vi.fn() instances.
|
|
mockDetermineProviderAndModel.mockReset();
|
|
mockAnalyzeImageWithProvider.mockReset();
|
|
mockParseAIProviders.mockReset();
|
|
mockIsProviderAvailable.mockReset();
|
|
mockGetDefaultModelForProvider.mockReset().mockReturnValue('default-model'); // Re-apply default mock behavior if any
|
|
});
|
|
|
|
describe('imageToolHandler - Capture Only', () => {
|
|
it('should capture screen with minimal parameters', async () => {
|
|
const mockResponse = mockSwiftCli.captureImage('screen', {});
|
|
mockExecuteSwiftCli.mockResolvedValue(mockResponse);
|
|
|
|
const result = await imageToolHandler({
|
|
format: 'png',
|
|
return_data: false,
|
|
capture_focus: 'background'
|
|
}, mockContext);
|
|
|
|
expect(result.content[0].type).toBe('text');
|
|
expect(result.content[0].text).toContain('Captured 1 image');
|
|
expect(mockExecuteSwiftCli).toHaveBeenCalledWith(
|
|
expect.arrayContaining(['image', '--mode', 'screen']),
|
|
mockLogger
|
|
);
|
|
expect(result.saved_files).toEqual(mockResponse.data?.saved_files);
|
|
expect(result.analysis_text).toBeUndefined();
|
|
expect(result.model_used).toBeUndefined();
|
|
});
|
|
|
|
it('should return image data when return_data is true and no question is asked', async () => {
|
|
const mockSavedFile: SavedFile = { path: '/tmp/test.png', mime_type: 'image/png', item_label: 'Screen 1' };
|
|
const mockCaptureData: ImageCaptureData = { saved_files: [mockSavedFile] };
|
|
const mockCliResponse = { success: true, data: mockCaptureData, messages: ['Captured one file'] };
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
mockReadImageAsBase64.mockResolvedValue('base64imagedata');
|
|
|
|
const result = await imageToolHandler({
|
|
format: 'png',
|
|
return_data: true,
|
|
capture_focus: 'background'
|
|
}, mockContext);
|
|
|
|
expect(result.isError).toBeUndefined();
|
|
expect(result.content).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ type: 'text', text: expect.stringContaining('Captured 1 image') }),
|
|
expect.objectContaining({ type: 'image', data: 'base64imagedata' })
|
|
]));
|
|
expect(mockReadImageAsBase64).toHaveBeenCalledWith('/tmp/test.png');
|
|
expect(result.saved_files).toEqual([mockSavedFile]);
|
|
expect(result.analysis_text).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('imageToolHandler - Capture and Analyze', () => {
|
|
const MOCK_QUESTION = 'What is in this image?';
|
|
const MOCK_ANALYSIS_RESPONSE = 'This is a cat.';
|
|
const MOCK_PROVIDER_DETAILS: AIProvider = { provider: 'ollama', model: 'llava:custom' };
|
|
|
|
beforeEach(() => {
|
|
mockParseAIProviders.mockReturnValue([{ provider: 'ollama', model: 'llava:default' }]);
|
|
mockDetermineProviderAndModel.mockResolvedValue(MOCK_PROVIDER_DETAILS);
|
|
mockAnalyzeImageWithProvider.mockResolvedValue(MOCK_ANALYSIS_RESPONSE);
|
|
mockReadImageAsBase64.mockResolvedValue('base64dataforanalysis');
|
|
process.env.PEEKABOO_AI_PROVIDERS = 'ollama/llava:default';
|
|
});
|
|
|
|
it('should capture, analyze, and delete temp image if no path provided', async () => {
|
|
const mockCliResponse = mockSwiftCli.captureImage('screen', { path: MOCK_TEMP_IMAGE_PATH, format: 'png' });
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
|
|
const result = await imageToolHandler({
|
|
question: MOCK_QUESTION,
|
|
format: 'png',
|
|
}, mockContext);
|
|
|
|
expect(mockFsMkdtemp).toHaveBeenCalled();
|
|
expect(mockExecuteSwiftCli).toHaveBeenCalledWith(expect.arrayContaining(['--path', MOCK_TEMP_IMAGE_PATH]), mockLogger);
|
|
expect(mockReadImageAsBase64).toHaveBeenCalledWith(MOCK_TEMP_IMAGE_PATH);
|
|
expect(mockDetermineProviderAndModel).toHaveBeenCalled();
|
|
expect(mockAnalyzeImageWithProvider).toHaveBeenCalledWith(MOCK_PROVIDER_DETAILS, MOCK_TEMP_IMAGE_PATH, 'base64dataforanalysis', MOCK_QUESTION, mockLogger);
|
|
|
|
expect(result.analysis_text).toBe(MOCK_ANALYSIS_RESPONSE);
|
|
expect(result.model_used).toBe(`${MOCK_PROVIDER_DETAILS.provider}/${MOCK_PROVIDER_DETAILS.model}`);
|
|
expect(result.content).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ text: expect.stringContaining('Captured 1 image') }),
|
|
expect.objectContaining({ text: expect.stringContaining('Analysis succeeded')}),
|
|
expect.objectContaining({ text: `Analysis Result: ${MOCK_ANALYSIS_RESPONSE}` })
|
|
]));
|
|
expect(result.saved_files).toEqual([]);
|
|
expect(result.content.some(item => item.type === 'image' && item.data)).toBe(false);
|
|
expect(mockFsUnlink).toHaveBeenCalledWith(MOCK_TEMP_IMAGE_PATH);
|
|
expect(mockFsRmdir).toHaveBeenCalledWith(MOCK_TEMP_DIR_PATH);
|
|
expect(result.isError).toBeUndefined();
|
|
});
|
|
|
|
it('should capture, analyze, and keep image if path IS provided', async () => {
|
|
const USER_PATH = '/user/specified/path.jpg';
|
|
const mockCliResponse = mockSwiftCli.captureImage('screen', { path: USER_PATH, format: 'jpg' });
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
|
|
const result = await imageToolHandler({
|
|
path: USER_PATH,
|
|
question: MOCK_QUESTION,
|
|
format: 'jpg'
|
|
}, mockContext);
|
|
|
|
expect(mockFsMkdtemp).not.toHaveBeenCalled();
|
|
expect(mockExecuteSwiftCli).toHaveBeenCalledWith(expect.arrayContaining(['--path', USER_PATH]), mockLogger);
|
|
expect(mockReadImageAsBase64).toHaveBeenCalledWith(USER_PATH);
|
|
expect(mockAnalyzeImageWithProvider).toHaveBeenCalled();
|
|
|
|
expect(result.analysis_text).toBe(MOCK_ANALYSIS_RESPONSE);
|
|
expect(result.saved_files).toEqual(mockCliResponse.data?.saved_files);
|
|
expect(mockFsUnlink).not.toHaveBeenCalled();
|
|
expect(mockFsRmdir).not.toHaveBeenCalled();
|
|
expect(result.isError).toBeUndefined();
|
|
});
|
|
|
|
it('should use provider_config if specified', async () => {
|
|
const specificProviderConfig: AIProviderConfig = { type: 'openai', model: 'gpt-4-vision' };
|
|
const specificProviderDetails: AIProvider = { provider: 'openai', model: 'gpt-4-vision' };
|
|
mockDetermineProviderAndModel.mockResolvedValue(specificProviderDetails);
|
|
const mockCliResponse = mockSwiftCli.captureImage('screen', { path: MOCK_TEMP_IMAGE_PATH, format: 'png' });
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
|
|
await imageToolHandler({
|
|
question: MOCK_QUESTION,
|
|
provider_config: specificProviderConfig,
|
|
format: 'png'
|
|
}, mockContext);
|
|
|
|
expect(mockDetermineProviderAndModel).toHaveBeenCalledWith(specificProviderConfig, expect.any(Array), mockLogger);
|
|
expect(mockAnalyzeImageWithProvider).toHaveBeenCalledWith(specificProviderDetails, MOCK_TEMP_IMAGE_PATH, 'base64dataforanalysis', MOCK_QUESTION, mockLogger);
|
|
});
|
|
|
|
it('should handle failure in readImageAsBase64 before analysis', async () => {
|
|
mockReadImageAsBase64.mockRejectedValue(new Error('Failed to read image'));
|
|
const mockCliResponse = mockSwiftCli.captureImage('screen', { path: MOCK_TEMP_IMAGE_PATH, format: 'png' });
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
|
|
const result = await imageToolHandler({ question: MOCK_QUESTION, format: 'png' }, mockContext);
|
|
|
|
expect(mockAnalyzeImageWithProvider).not.toHaveBeenCalled();
|
|
expect(result.analysis_text).toContain('Analysis skipped: Failed to read captured image');
|
|
expect(result.isError).toBe(true);
|
|
expect(result.model_used).toBeUndefined();
|
|
expect(mockFsUnlink).toHaveBeenCalledWith(MOCK_TEMP_IMAGE_PATH);
|
|
});
|
|
|
|
it('should handle failure in determineProviderAndModel (rejected promise)', async () => {
|
|
mockDetermineProviderAndModel.mockRejectedValue(new Error('No provider available error from determine'));
|
|
const mockCliResponse = mockSwiftCli.captureImage('screen', { path: MOCK_TEMP_IMAGE_PATH, format: 'png' });
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
|
|
const result = await imageToolHandler({ question: MOCK_QUESTION, format: 'png' }, mockContext);
|
|
|
|
expect(mockAnalyzeImageWithProvider).not.toHaveBeenCalled();
|
|
expect(result.analysis_text).toContain('AI analysis failed: No provider available error from determine');
|
|
expect(result.isError).toBe(true);
|
|
});
|
|
|
|
it('should handle failure when determineProviderAndModel resolves to no provider', async () => {
|
|
mockDetermineProviderAndModel.mockResolvedValue({ provider: null, model: '' });
|
|
const mockCliResponse = mockSwiftCli.captureImage('screen', { path: MOCK_TEMP_IMAGE_PATH, format: 'png' });
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
|
|
const result = await imageToolHandler({ question: MOCK_QUESTION, format: 'png' }, mockContext);
|
|
|
|
expect(mockAnalyzeImageWithProvider).not.toHaveBeenCalled();
|
|
expect(result.analysis_text).toContain('Analysis skipped: No AI providers are currently operational');
|
|
expect(result.isError).toBe(true);
|
|
});
|
|
|
|
it('should handle failure in analyzeImageWithProvider', async () => {
|
|
mockAnalyzeImageWithProvider.mockRejectedValue(new Error('AI API Error from analyze'));
|
|
const mockCliResponse = mockSwiftCli.captureImage('screen', { path: MOCK_TEMP_IMAGE_PATH, format: 'png' });
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
|
|
const result = await imageToolHandler({ question: MOCK_QUESTION, format: 'png' }, mockContext);
|
|
|
|
expect(result.analysis_text).toContain('AI analysis failed: AI API Error from analyze');
|
|
expect(result.isError).toBe(true);
|
|
expect(result.model_used).toBeUndefined();
|
|
});
|
|
|
|
it('should correctly report error if PEEKABOO_AI_PROVIDERS is not set and no provider_config given', async () => {
|
|
process.env.PEEKABOO_AI_PROVIDERS = '';
|
|
mockParseAIProviders.mockReturnValue([]);
|
|
const mockCliResponse = mockSwiftCli.captureImage('screen', { path: MOCK_TEMP_IMAGE_PATH, format: 'png' });
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
|
|
const result = await imageToolHandler({ question: MOCK_QUESTION, format: 'png' }, mockContext);
|
|
|
|
expect(result.analysis_text).toContain('Analysis skipped: AI analysis not configured on this server');
|
|
expect(result.isError).toBe(true);
|
|
expect(mockAnalyzeImageWithProvider).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should return isError = true if analysis is attempted but fails, even if capture succeeds', async () => {
|
|
mockAnalyzeImageWithProvider.mockRejectedValue(new Error('AI Error'));
|
|
const mockCliResponse = mockSwiftCli.captureImage('screen', { path: MOCK_TEMP_IMAGE_PATH, format: 'png' });
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
|
|
const result = await imageToolHandler({ question: MOCK_QUESTION, format: 'png' }, mockContext);
|
|
expect(result.isError).toBe(true);
|
|
expect(result.content[0].text).toContain('Captured 1 image');
|
|
expect(result.content[0].text).toContain('Analysis failed/skipped');
|
|
expect(result.analysis_text).toContain('AI analysis failed: AI Error');
|
|
});
|
|
|
|
it('should NOT return base64_data in content if question is asked, even if return_data is true', async () => {
|
|
const mockCliResponse = mockSwiftCli.captureImage('screen', { path: MOCK_TEMP_IMAGE_PATH, format: 'png' });
|
|
mockExecuteSwiftCli.mockResolvedValue(mockCliResponse);
|
|
|
|
const result = await imageToolHandler({
|
|
question: MOCK_QUESTION,
|
|
return_data: true,
|
|
format: 'png'
|
|
}, mockContext);
|
|
|
|
expect(result.content.some(item => item.type === 'image' && item.data)).toBe(false);
|
|
expect(result.analysis_text).toBe(MOCK_ANALYSIS_RESPONSE);
|
|
});
|
|
});
|
|
|
|
describe('buildSwiftCliArgs', () => {
|
|
const defaults = { format: 'png' as const, return_data: false, capture_focus: 'background' as const };
|
|
|
|
it('should default to screen mode if no app provided and no mode specified', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults });
|
|
expect(args).toEqual(['image', '--mode', 'screen', '--format', 'png', '--capture-focus', 'background']);
|
|
});
|
|
|
|
it('should default to window mode if app is provided and no mode specified', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults, app: 'Safari' });
|
|
expect(args).toEqual(['image', '--app', 'Safari', '--mode', 'window', '--format', 'png', '--capture-focus', 'background']);
|
|
});
|
|
|
|
it('should use specified mode: screen', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults, mode: 'screen' });
|
|
expect(args).toEqual(expect.arrayContaining(['--mode', 'screen']));
|
|
});
|
|
|
|
it('should use specified mode: window with app', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults, app: 'Terminal', mode: 'window' });
|
|
expect(args).toEqual(expect.arrayContaining(['--app', 'Terminal', '--mode', 'window']));
|
|
});
|
|
|
|
it('should use specified mode: multi with app', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults, app: 'Finder', mode: 'multi' });
|
|
expect(args).toEqual(expect.arrayContaining(['--app', 'Finder', '--mode', 'multi']));
|
|
});
|
|
|
|
it('should include app', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults, app: 'Notes' });
|
|
expect(args).toEqual(expect.arrayContaining(['--app', 'Notes']));
|
|
});
|
|
|
|
it('should include path', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults, path: '/tmp/image.jpg' });
|
|
expect(args).toEqual(expect.arrayContaining(['--path', '/tmp/image.jpg']));
|
|
});
|
|
|
|
it('should include window_specifier by title', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults, app: 'Safari', window_specifier: { title: 'Apple' } });
|
|
expect(args).toEqual(expect.arrayContaining(['--window-title', 'Apple']));
|
|
});
|
|
|
|
it('should include window_specifier by index', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults, app: 'Safari', window_specifier: { index: 0 } });
|
|
expect(args).toEqual(expect.arrayContaining(['--window-index', '0']));
|
|
});
|
|
|
|
it('should include format (default png)', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults });
|
|
expect(args).toEqual(expect.arrayContaining(['--format', 'png']));
|
|
});
|
|
|
|
it('should include specified format jpg', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults, format: 'jpg' });
|
|
expect(args).toEqual(expect.arrayContaining(['--format', 'jpg']));
|
|
});
|
|
|
|
it('should include capture_focus (default background)', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults });
|
|
expect(args).toEqual(expect.arrayContaining(['--capture-focus', 'background']));
|
|
});
|
|
|
|
it('should include specified capture_focus foreground', () => {
|
|
const args = buildSwiftCliArgs({ ...defaults, capture_focus: 'foreground' });
|
|
expect(args).toEqual(expect.arrayContaining(['--capture-focus', 'foreground']));
|
|
});
|
|
|
|
it('should handle all options together', () => {
|
|
const input: ImageToolInput = {
|
|
...defaults, // Ensure all required fields are present
|
|
app: 'Preview',
|
|
path: '/users/test/file.tiff',
|
|
mode: 'window',
|
|
window_specifier: { index: 1 },
|
|
format: 'png',
|
|
capture_focus: 'foreground'
|
|
};
|
|
const args = buildSwiftCliArgs(input);
|
|
expect(args).toEqual([
|
|
'image',
|
|
'--app', 'Preview',
|
|
'--path', '/users/test/file.tiff',
|
|
'--mode', 'window',
|
|
'--window-index', '1',
|
|
'--format', 'png',
|
|
'--capture-focus', 'foreground'
|
|
]);
|
|
});
|
|
|
|
it('should use input.path if provided, even with a question', () => {
|
|
const input: ImageToolInput = { path: '/my/path.png', question: 'test' };
|
|
const args = buildSwiftCliArgs(input);
|
|
expect(args).toContain('--path');
|
|
expect(args).toContain('/my/path.png');
|
|
});
|
|
|
|
it('should NOT use PEEKABOO_DEFAULT_SAVE_PATH if a question is asked', () => {
|
|
process.env.PEEKABOO_DEFAULT_SAVE_PATH = '/default/env.png';
|
|
const input: ImageToolInput = { question: 'test' };
|
|
const args = buildSwiftCliArgs(input);
|
|
expect(args.includes('--path')).toBe(false);
|
|
delete process.env.PEEKABOO_DEFAULT_SAVE_PATH;
|
|
});
|
|
|
|
it('should use PEEKABOO_DEFAULT_SAVE_PATH if no path and no question', () => {
|
|
process.env.PEEKABOO_DEFAULT_SAVE_PATH = '/default/env.png';
|
|
const input: ImageToolInput = {};
|
|
const args = buildSwiftCliArgs(input);
|
|
expect(args).toContain('--path');
|
|
expect(args).toContain('/default/env.png');
|
|
delete process.env.PEEKABOO_DEFAULT_SAVE_PATH;
|
|
});
|
|
|
|
it('should use default format and capture_focus if not provided', () => {
|
|
const input: ImageToolInput = { format: 'png', capture_focus: 'background' };
|
|
const args = buildSwiftCliArgs(input);
|
|
expect(args).toContain('--format');
|
|
expect(args).toContain('png');
|
|
expect(args).toContain('--capture-focus');
|
|
expect(args).toContain('background');
|
|
});
|
|
});
|
|
});
|