Files
qiming/qimingclaw/crates/agent-gui-server/tests/desktop/mouse.test.ts

213 lines
6.7 KiB
TypeScript

/**
* Unit tests for desktop/mouse.ts — click, doubleClick, moveTo, drag, scroll, getPosition.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mockMove = vi.fn();
const mockClick = vi.fn();
const mockDoubleClick = vi.fn();
const mockPressButton = vi.fn();
const mockReleaseButton = vi.fn();
const mockScrollDown = vi.fn();
const mockScrollUp = vi.fn();
const mockScrollLeft = vi.fn();
const mockScrollRight = vi.fn();
const mockGetPosition = vi.fn();
const mockStraightTo = vi.fn();
vi.mock('@nut-tree-fork/nut-js', () => {
class MockPoint {
constructor(public x: number, public y: number) {}
}
return {
mouse: {
move: (...args: any[]) => mockMove(...args),
click: (...args: any[]) => mockClick(...args),
doubleClick: (...args: any[]) => mockDoubleClick(...args),
pressButton: (...args: any[]) => mockPressButton(...args),
releaseButton: (...args: any[]) => mockReleaseButton(...args),
scrollDown: (...args: any[]) => mockScrollDown(...args),
scrollUp: (...args: any[]) => mockScrollUp(...args),
scrollLeft: (...args: any[]) => mockScrollLeft(...args),
scrollRight: (...args: any[]) => mockScrollRight(...args),
getPosition: () => mockGetPosition(),
},
Button: { LEFT: 'LEFT', RIGHT: 'RIGHT', MIDDLE: 'MIDDLE' },
Point: MockPoint,
straightTo: (p: any) => mockStraightTo(p),
};
});
import { click, doubleClick, moveTo, drag, scroll, getPosition } from '../../src/desktop/mouse.js';
describe('click', () => {
beforeEach(() => {
vi.clearAllMocks();
mockMove.mockResolvedValue(undefined);
mockClick.mockResolvedValue(undefined);
mockStraightTo.mockImplementation((p: any) => p);
});
it('moves to position and clicks left button by default', async () => {
await click(100, 200);
expect(mockStraightTo).toHaveBeenCalledWith(expect.objectContaining({ x: 100, y: 200 }));
expect(mockMove).toHaveBeenCalled();
expect(mockClick).toHaveBeenCalledWith('LEFT');
});
it('supports right button', async () => {
await click(50, 60, 'right');
expect(mockClick).toHaveBeenCalledWith('RIGHT');
});
it('supports middle button', async () => {
await click(50, 60, 'middle');
expect(mockClick).toHaveBeenCalledWith('MIDDLE');
});
it('wraps errors as DesktopError', async () => {
mockMove.mockRejectedValue(new Error('screen locked'));
await expect(click(0, 0)).rejects.toThrow('mouse.click');
});
});
describe('doubleClick', () => {
beforeEach(() => {
vi.clearAllMocks();
mockMove.mockResolvedValue(undefined);
mockDoubleClick.mockResolvedValue(undefined);
mockStraightTo.mockImplementation((p: any) => p);
});
it('moves and double-clicks', async () => {
await doubleClick(300, 400);
expect(mockMove).toHaveBeenCalled();
expect(mockDoubleClick).toHaveBeenCalledWith('LEFT');
});
it('wraps errors as DesktopError', async () => {
mockDoubleClick.mockRejectedValue(new Error('fail'));
await expect(doubleClick(0, 0)).rejects.toThrow('mouse.doubleClick');
});
});
describe('moveTo', () => {
beforeEach(() => {
vi.clearAllMocks();
mockMove.mockResolvedValue(undefined);
mockStraightTo.mockImplementation((p: any) => p);
});
it('moves mouse to coordinates', async () => {
await moveTo(500, 600);
expect(mockStraightTo).toHaveBeenCalledWith(expect.objectContaining({ x: 500, y: 600 }));
expect(mockMove).toHaveBeenCalled();
});
it('wraps errors as DesktopError', async () => {
mockMove.mockRejectedValue(new Error('fail'));
await expect(moveTo(0, 0)).rejects.toThrow('mouse.moveTo');
});
});
describe('drag', () => {
beforeEach(() => {
vi.clearAllMocks();
mockMove.mockResolvedValue(undefined);
mockPressButton.mockResolvedValue(undefined);
mockReleaseButton.mockResolvedValue(undefined);
mockStraightTo.mockImplementation((p: any) => p);
});
it('moves to start, presses, moves to end, releases', async () => {
await drag(10, 20, 100, 200);
expect(mockMove).toHaveBeenCalledTimes(2);
expect(mockPressButton).toHaveBeenCalledWith('LEFT');
expect(mockReleaseButton).toHaveBeenCalledWith('LEFT');
// First move to start
const firstMove = mockStraightTo.mock.calls[0][0];
expect(firstMove.x).toBe(10);
expect(firstMove.y).toBe(20);
// Second move to end
const secondMove = mockStraightTo.mock.calls[1][0];
expect(secondMove.x).toBe(100);
expect(secondMove.y).toBe(200);
});
it('supports right button drag', async () => {
await drag(0, 0, 50, 50, 'right');
expect(mockPressButton).toHaveBeenCalledWith('RIGHT');
expect(mockReleaseButton).toHaveBeenCalledWith('RIGHT');
});
it('wraps errors as DesktopError', async () => {
mockPressButton.mockRejectedValue(new Error('fail'));
await expect(drag(0, 0, 10, 10)).rejects.toThrow('mouse.drag');
});
});
describe('scroll', () => {
beforeEach(() => {
vi.clearAllMocks();
mockMove.mockResolvedValue(undefined);
mockScrollDown.mockResolvedValue(undefined);
mockScrollUp.mockResolvedValue(undefined);
mockScrollLeft.mockResolvedValue(undefined);
mockScrollRight.mockResolvedValue(undefined);
mockStraightTo.mockImplementation((p: any) => p);
});
it('scrolls down with positive deltaY', async () => {
await scroll(100, 200, 5);
expect(mockMove).toHaveBeenCalled();
expect(mockScrollDown).toHaveBeenCalledWith(5);
expect(mockScrollUp).not.toHaveBeenCalled();
});
it('scrolls up with negative deltaY', async () => {
await scroll(100, 200, -3);
expect(mockScrollUp).toHaveBeenCalledWith(3);
expect(mockScrollDown).not.toHaveBeenCalled();
});
it('does not scroll vertically when deltaY is 0', async () => {
await scroll(100, 200, 0);
expect(mockScrollDown).not.toHaveBeenCalled();
expect(mockScrollUp).not.toHaveBeenCalled();
});
it('scrolls right with positive deltaX', async () => {
await scroll(100, 200, 0, 4);
expect(mockScrollRight).toHaveBeenCalledWith(4);
});
it('scrolls left with negative deltaX', async () => {
await scroll(100, 200, 0, -2);
expect(mockScrollLeft).toHaveBeenCalledWith(2);
});
it('wraps errors as DesktopError', async () => {
mockMove.mockRejectedValue(new Error('fail'));
await expect(scroll(0, 0, 1)).rejects.toThrow('mouse.scroll');
});
});
describe('getPosition', () => {
beforeEach(() => vi.clearAllMocks());
it('returns cursor position', async () => {
mockGetPosition.mockResolvedValue({ x: 350, y: 450 });
const pos = await getPosition();
expect(pos).toEqual({ x: 350, y: 450 });
});
it('wraps errors as DesktopError', async () => {
mockGetPosition.mockRejectedValue(new Error('fail'));
await expect(getPosition()).rejects.toThrow('mouse.getPosition');
});
});