126 lines
3.6 KiB
TypeScript
126 lines
3.6 KiB
TypeScript
/**
|
|
* Unit tests for desktop/clipboard.ts — pasteText flow, backup/restore, key order.
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
const mockRead = vi.fn();
|
|
const mockWrite = vi.fn();
|
|
|
|
vi.mock('clipboardy', () => ({
|
|
default: {
|
|
read: () => mockRead(),
|
|
write: (text: string) => mockWrite(text),
|
|
},
|
|
}));
|
|
|
|
const mockPressKey = vi.fn();
|
|
const mockReleaseKey = vi.fn();
|
|
|
|
vi.mock('@nut-tree-fork/nut-js', () => ({
|
|
keyboard: {
|
|
pressKey: mockPressKey,
|
|
releaseKey: mockReleaseKey,
|
|
},
|
|
Key: {
|
|
Meta: 100,
|
|
Control: 101,
|
|
V: 102,
|
|
},
|
|
}));
|
|
|
|
const mockGetPlatformPasteKeys = vi.fn();
|
|
vi.mock('../../src/utils/platform.js', () => ({
|
|
getPlatformPasteKeys: () => mockGetPlatformPasteKeys(),
|
|
}));
|
|
|
|
import { pasteText, readClipboard, writeClipboard } from '../../src/desktop/clipboard.js';
|
|
|
|
describe('readClipboard', () => {
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
it('reads clipboard content', async () => {
|
|
mockRead.mockResolvedValue('existing content');
|
|
const result = await readClipboard();
|
|
expect(result).toBe('existing content');
|
|
});
|
|
});
|
|
|
|
describe('writeClipboard', () => {
|
|
beforeEach(() => vi.clearAllMocks());
|
|
|
|
it('writes text to clipboard', async () => {
|
|
mockWrite.mockResolvedValue(undefined);
|
|
await writeClipboard('new text');
|
|
expect(mockWrite).toHaveBeenCalledWith('new text');
|
|
});
|
|
});
|
|
|
|
describe('pasteText', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockGetPlatformPasteKeys.mockReturnValue(['Meta', 'V']);
|
|
mockRead.mockResolvedValue('backup-content');
|
|
mockWrite.mockResolvedValue(undefined);
|
|
mockPressKey.mockResolvedValue(undefined);
|
|
mockReleaseKey.mockResolvedValue(undefined);
|
|
});
|
|
|
|
it('follows full paste flow: backup → write → press → wait → restore', async () => {
|
|
await pasteText('target text');
|
|
|
|
// 1. Backup clipboard
|
|
expect(mockRead).toHaveBeenCalledTimes(1);
|
|
|
|
// 2. Write target text
|
|
expect(mockWrite).toHaveBeenNthCalledWith(1, 'target text');
|
|
|
|
// 3. Press paste keys
|
|
expect(mockPressKey).toHaveBeenCalledWith(100, 102); // Meta, V
|
|
|
|
// 4. Release in REVERSE order (V first, then Meta)
|
|
expect(mockReleaseKey).toHaveBeenCalledWith(102, 100); // V, Meta
|
|
|
|
// 5. Restore original clipboard
|
|
expect(mockWrite).toHaveBeenNthCalledWith(2, 'backup-content');
|
|
});
|
|
|
|
it('continues if backup read fails', async () => {
|
|
mockRead.mockRejectedValue(new Error('clipboard not available'));
|
|
|
|
await pasteText('text');
|
|
|
|
// Should still write and paste
|
|
expect(mockWrite).toHaveBeenCalledWith('text');
|
|
expect(mockPressKey).toHaveBeenCalled();
|
|
});
|
|
|
|
it('does not throw if restore fails', async () => {
|
|
mockRead.mockResolvedValue('backup');
|
|
// First write succeeds, second (restore) fails
|
|
mockWrite
|
|
.mockResolvedValueOnce(undefined)
|
|
.mockRejectedValueOnce(new Error('restore failed'));
|
|
|
|
// Should not throw
|
|
await expect(pasteText('text')).resolves.toBeUndefined();
|
|
});
|
|
|
|
it('does not attempt restore if backup was undefined', async () => {
|
|
mockRead.mockRejectedValue(new Error('no clipboard'));
|
|
await pasteText('text');
|
|
|
|
// Only one write call (the target text), no restore
|
|
expect(mockWrite).toHaveBeenCalledTimes(1);
|
|
expect(mockWrite).toHaveBeenCalledWith('text');
|
|
});
|
|
|
|
it('uses platform-specific paste keys', async () => {
|
|
mockGetPlatformPasteKeys.mockReturnValue(['Control', 'V']);
|
|
await pasteText('text');
|
|
|
|
expect(mockPressKey).toHaveBeenCalledWith(101, 102); // Control, V
|
|
expect(mockReleaseKey).toHaveBeenCalledWith(102, 101); // V, Control (reversed)
|
|
});
|
|
});
|