Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a3ef752
fix(clipboard): use platform-native tools for image paste on Linux
CNCSMonster May 30, 2026
b3832bc
test: update clipboard tests for platform-native tools
CNCSMonster May 31, 2026
f9121f2
fix: address critical review comments
CNCSMonster May 31, 2026
accb428
fix: address review suggestions for resource leaks and robustness
CNCSMonster May 31, 2026
0c3d2de
fix: address all remaining review comments
CNCSMonster May 31, 2026
9124d74
perf: cache wl-paste --list-types result to avoid redundant calls
CNCSMonster May 31, 2026
7572ac9
fix: address remaining review suggestions
CNCSMonster May 31, 2026
d3270e1
fix: address critical cache invalidation and other review feedback
CNCSMonster Jun 1, 2026
158fcf2
fix: address remaining code review issues
CNCSMonster Jun 1, 2026
010a262
fix: restore getClipboardModule import caching (regression fix)
CNCSMonster Jun 1, 2026
7344381
test: add saveClipboardImage success path and cache behavior tests
CNCSMonster Jun 1, 2026
890d88d
fix: revert execSync to fix WSL2 clipboard detection
CNCSMonster Jun 1, 2026
237073c
fix: address critical file leak and filter issues from review
CNCSMonster Jun 2, 2026
5e803cf
test: add xclip, BMP, error path test coverage; fix weak assertion
CNCSMonster Jun 2, 2026
747adab
fix: remove unused _setupWaylandEnv function that breaks TS build
CNCSMonster Jun 3, 2026
243e1fb
fix: clean up tempFilePath on PIL conversion failure
CNCSMonster Jun 4, 2026
7e43d66
fix: address review feedback on file leaks and test coverage
CNCSMonster Jun 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 71 additions & 54 deletions packages/cli/src/ui/utils/clipboardUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,22 @@ import {
cleanupOldClipboardImages,
} from './clipboardUtils.js';

// Mock ClipboardManager
// Mock child_process for platform-native clipboard tools
const mockSpawn = vi.fn();
vi.mock('node:child_process', () => ({
spawn: mockSpawn,
Comment thread
CNCSMonster marked this conversation as resolved.
Outdated
execSync: vi.fn(),
}));

// Mock fs for file operations
vi.mock('node:fs/promises', () => ({
mkdir: vi.fn().mockResolvedValue(undefined),
readdir: vi.fn().mockResolvedValue([]),
stat: vi.fn().mockResolvedValue({ atimeMs: 0 }),
unlink: vi.fn().mockResolvedValue(undefined),
}));

// Mock ClipboardManager for non-Linux fallback
const mockHasFormat = vi.fn();
const mockGetImageData = vi.fn();

Expand All @@ -34,86 +49,94 @@ describe('clipboardUtils', () => {
});
Comment thread
CNCSMonster marked this conversation as resolved.

describe('clipboardHasImage', () => {
it('should return true when clipboard contains image', async () => {
mockHasFormat.mockReturnValue(true);
it('should return true when clipboard contains image on Linux', async () => {
// Mock wl-paste --list-types returning image types
const mockStdout = {
on: vi.fn((event, callback) => {
if (event === 'data') {
callback(Buffer.from('image/png\nimage/bmp\n'));
}
}),
};
const mockChild = {
stdout: mockStdout,
on: vi.fn((event, callback) => {
if (event === 'close') {
callback(0);
}
}),
};
mockSpawn.mockReturnValue(mockChild);

const result = await clipboardHasImage();
expect(result).toBe(true);
expect(mockHasFormat).toHaveBeenCalledWith('image');
});

it('should return false when clipboard does not contain image', async () => {
mockHasFormat.mockReturnValue(false);
it('should return false when clipboard does not contain image on Linux', async () => {
// Mock wl-paste --list-types returning no image types
const mockStdout = {
on: vi.fn((event, callback) => {
if (event === 'data') {
callback(Buffer.from('text/plain\n'));
}
}),
};
const mockChild = {
stdout: mockStdout,
on: vi.fn((event, callback) => {
if (event === 'close') {
callback(0);
}
}),
};
mockSpawn.mockReturnValue(mockChild);

const result = await clipboardHasImage();
expect(result).toBe(false);
expect(mockHasFormat).toHaveBeenCalledWith('image');
});

it('should return false on error', async () => {
mockHasFormat.mockImplementation(() => {
mockSpawn.mockImplementation(() => {
throw new Error('Clipboard error');
});

const result = await clipboardHasImage();
expect(result).toBe(false);
});

it('should return false and not throw when error occurs in DEBUG mode', async () => {
const originalEnv = process.env;
vi.stubGlobal('process', {
...process,
env: { ...originalEnv, DEBUG: '1' },
});

mockHasFormat.mockImplementation(() => {
throw new Error('Test error');
});

const result = await clipboardHasImage();
expect(result).toBe(false);
});
});

describe('saveClipboardImage', () => {
Comment thread
CNCSMonster marked this conversation as resolved.
it('should return null when clipboard has no image', async () => {
mockHasFormat.mockReturnValue(false);

const result = await saveClipboardImage('/tmp/test');
expect(result).toBe(null);
});

it('should return null when image data buffer is null', async () => {
mockHasFormat.mockReturnValue(true);
mockGetImageData.mockReturnValue({ data: null });
// Mock wl-paste --list-types returning no image types
const mockStdout = {
on: vi.fn((event, callback) => {
if (event === 'data') {
callback(Buffer.from('text/plain\n'));
}
}),
};
const mockChild = {
stdout: mockStdout,
on: vi.fn((event, callback) => {
if (event === 'close') {
callback(0);
}
}),
};
mockSpawn.mockReturnValue(mockChild);

const result = await saveClipboardImage('/tmp/test');
expect(result).toBe(null);
});

it('should handle errors gracefully and return null', async () => {
mockHasFormat.mockImplementation(() => {
mockSpawn.mockImplementation(() => {
throw new Error('Clipboard error');
});

const result = await saveClipboardImage('/tmp/test');
expect(result).toBe(null);
});

it('should return null and not throw when error occurs in DEBUG mode', async () => {
const originalEnv = process.env;
vi.stubGlobal('process', {
...process,
env: { ...originalEnv, DEBUG: '1' },
});

mockHasFormat.mockImplementation(() => {
throw new Error('Test error');
});

const result = await saveClipboardImage('/tmp/test');
expect(result).toBe(null);
});
});

describe('cleanupOldClipboardImages', () => {
Expand All @@ -126,11 +149,5 @@ describe('clipboardUtils', () => {
it('should complete without errors on valid directory', async () => {
await expect(cleanupOldClipboardImages('.')).resolves.not.toThrow();
});

it('should use clipboard directory consistently with saveClipboardImage', () => {
// This test verifies that both functions use the same directory structure
// The implementation uses 'clipboard' subdirectory for both functions
expect(true).toBe(true);
});
});
});
Loading
Loading