134 lines
4.0 KiB
TypeScript
134 lines
4.0 KiB
TypeScript
/**
|
|
* Unit tests for desktop/imageSearch.ts — findImage, waitForImage.
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
|
// Mock fs
|
|
const mockWriteFile = vi.fn();
|
|
const mockUnlink = vi.fn();
|
|
vi.mock('fs', () => ({
|
|
promises: {
|
|
writeFile: (...args: any[]) => mockWriteFile(...args),
|
|
unlink: (...args: any[]) => mockUnlink(...args),
|
|
},
|
|
}));
|
|
|
|
// Mock nut.js
|
|
const mockFind = vi.fn();
|
|
const mockLoadImage = vi.fn();
|
|
const mockScreenConfig = { confidence: 0.9 };
|
|
|
|
vi.mock('@nut-tree-fork/nut-js', () => ({
|
|
screen: {
|
|
find: (...args: any[]) => mockFind(...args),
|
|
config: mockScreenConfig,
|
|
},
|
|
loadImage: (...args: any[]) => mockLoadImage(...args),
|
|
}));
|
|
|
|
import { findImage, waitForImage } from '../../src/desktop/imageSearch.js';
|
|
|
|
describe('findImage', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockWriteFile.mockResolvedValue(undefined);
|
|
mockUnlink.mockResolvedValue(undefined);
|
|
mockLoadImage.mockResolvedValue('template-obj');
|
|
});
|
|
|
|
it('returns found=true with region when match found', async () => {
|
|
mockFind.mockResolvedValue({ left: 10, top: 20, width: 50, height: 30 });
|
|
|
|
const result = await findImage('dGVzdA==', 0.85);
|
|
|
|
expect(result.found).toBe(true);
|
|
expect(result.region).toEqual({ x: 10, y: 20, width: 50, height: 30 });
|
|
expect(result.confidence).toBe(0.85);
|
|
expect(mockScreenConfig.confidence).toBe(0.85);
|
|
});
|
|
|
|
it('returns found=false when no match', async () => {
|
|
mockFind.mockRejectedValue(new Error('no match'));
|
|
|
|
const result = await findImage('dGVzdA==');
|
|
expect(result.found).toBe(false);
|
|
expect(result.region).toBeUndefined();
|
|
});
|
|
|
|
it('writes template to temp file and cleans up', async () => {
|
|
mockFind.mockResolvedValue({ left: 0, top: 0, width: 10, height: 10 });
|
|
|
|
await findImage('dGVzdA==');
|
|
|
|
expect(mockWriteFile).toHaveBeenCalledTimes(1);
|
|
expect(mockUnlink).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('cleans up temp file even when match fails', async () => {
|
|
mockFind.mockRejectedValue(new Error('no match'));
|
|
|
|
await findImage('dGVzdA==');
|
|
expect(mockUnlink).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('uses default confidence of 0.9', async () => {
|
|
mockFind.mockResolvedValue({ left: 0, top: 0, width: 10, height: 10 });
|
|
|
|
const result = await findImage('dGVzdA==');
|
|
expect(result.confidence).toBe(0.9);
|
|
});
|
|
});
|
|
|
|
describe('waitForImage', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockWriteFile.mockResolvedValue(undefined);
|
|
mockUnlink.mockResolvedValue(undefined);
|
|
mockLoadImage.mockResolvedValue('template-obj');
|
|
});
|
|
|
|
it('returns found=true with elapsed when image found immediately', async () => {
|
|
mockFind.mockResolvedValue({ left: 5, top: 10, width: 20, height: 15 });
|
|
|
|
const result = await waitForImage('dGVzdA==', 5000, 0.8);
|
|
|
|
expect(result.found).toBe(true);
|
|
expect(result.region).toEqual({ x: 5, y: 10, width: 20, height: 15 });
|
|
expect(result.elapsed).toBeDefined();
|
|
expect(result.elapsed).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('returns found=false with elapsed after timeout', async () => {
|
|
mockFind.mockRejectedValue(new Error('no match'));
|
|
|
|
// Use a very short timeout to avoid slow tests
|
|
const result = await waitForImage('dGVzdA==', 100, 0.9);
|
|
|
|
expect(result.found).toBe(false);
|
|
expect(result.elapsed).toBeGreaterThanOrEqual(100);
|
|
});
|
|
|
|
it('cleans up temp file after completion', async () => {
|
|
mockFind.mockResolvedValue({ left: 0, top: 0, width: 10, height: 10 });
|
|
|
|
await waitForImage('dGVzdA==', 5000);
|
|
expect(mockUnlink).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('cleans up temp file even after timeout', async () => {
|
|
mockFind.mockRejectedValue(new Error('no match'));
|
|
|
|
await waitForImage('dGVzdA==', 100);
|
|
expect(mockUnlink).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('writes temp file only once for reuse across polls', async () => {
|
|
mockFind.mockRejectedValue(new Error('no match'));
|
|
|
|
await waitForImage('dGVzdA==', 100);
|
|
// Only 1 writeFile call despite potentially multiple polls
|
|
expect(mockWriteFile).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|