Skip to content

Commit bdf44bc

Browse files
committed
test(unit): create unit testing for all repo
1 parent 9be6e42 commit bdf44bc

File tree

10 files changed

+1380
-3
lines changed

10 files changed

+1380
-3
lines changed

lib/powershell/common.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class PSControlType extends PSObject {
9595
export class PSPoint extends PSObject {
9696
constructor(value: Position) {
9797
const requiredFields = ['x', 'y'];
98-
if (!(Object.keys(value).every(requiredFields.includes) && typeof value.x === 'number' && typeof value.y === 'number')) {
98+
if (!(requiredFields.every((f) => f in value) && typeof value.x === 'number' && typeof value.y === 'number')) {
9999
throw new errors.InvalidArgumentError('PSPoint accepts a Position object { x: number, y: number } in the constructor.');
100100
}
101101

@@ -106,7 +106,7 @@ export class PSPoint extends PSObject {
106106
export class PSRect extends PSObject {
107107
constructor(value: Rect) {
108108
const requiredFields = ['x', 'y', 'width', 'height'];
109-
if (!(Object.keys(value).every(requiredFields.includes) && typeof value.x === 'number' && typeof value.y === 'number' && typeof value.width === 'number' && typeof value.height === 'number')) {
109+
if (!(requiredFields.every((f) => f in value) && typeof value.x === 'number' && typeof value.y === 'number' && typeof value.width === 'number' && typeof value.height === 'number')) {
110110
throw new errors.InvalidArgumentError('PSRect accepts a Rect object { x: number, y: number, width: number, height: number } in the constructor.');
111111
}
112112

@@ -128,7 +128,7 @@ export class PSCultureInfo extends PSObject {
128128
constructor(name: string, useUserOverride?: boolean)
129129
constructor(culture: number, useUserOverride?: boolean)
130130
constructor(nameOrCulture: string | number, useUserOverride?: boolean) {
131-
if (typeof nameOrCulture !== 'string' || (typeof nameOrCulture === 'number' && nameOrCulture < 0)) {
131+
if (typeof nameOrCulture !== 'string' && (typeof nameOrCulture !== 'number' || nameOrCulture < 0)) {
132132
throw new errors.InvalidArgumentError('PSCultureInfo accepts a string or positive integer value in the constructor.');
133133
}
134134

test/commands/app/app.test.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* Unit tests for additional lib/commands/app.ts functions
3+
* (getPageSource, getWindowHandle, getWindowHandles, getWindowRect, setWindow)
4+
*/
5+
import { describe, it, expect, vi, beforeEach } from 'vitest';
6+
import {
7+
getPageSource,
8+
getWindowHandle,
9+
getWindowHandles,
10+
getWindowRect,
11+
setWindow,
12+
} from '../../../lib/commands/app';
13+
import { createMockDriver } from '../../fixtures/driver';
14+
15+
vi.mock('../../../lib/winapi/user32', () => ({
16+
getWindowAllHandlesForProcessIds: vi.fn().mockReturnValue([]),
17+
trySetForegroundWindow: vi.fn().mockReturnValue(true),
18+
}));
19+
20+
describe('getPageSource', () => {
21+
beforeEach(() => vi.clearAllMocks());
22+
23+
it('returns XML page source string', async () => {
24+
const driver = createMockDriver() as any;
25+
driver.sendPowerShellCommand.mockResolvedValue('<Root><Child /></Root>');
26+
const result = await getPageSource.call(driver);
27+
expect(result).toBe('<Root><Child /></Root>');
28+
expect(driver.sendPowerShellCommand).toHaveBeenCalledTimes(1);
29+
});
30+
});
31+
32+
describe('getWindowHandle', () => {
33+
beforeEach(() => vi.clearAllMocks());
34+
35+
it('returns hex-formatted window handle', async () => {
36+
const driver = createMockDriver() as any;
37+
driver.sendPowerShellCommand.mockResolvedValue('12648430'); // 0x00C0FFEE
38+
const result = await getWindowHandle.call(driver);
39+
expect(result).toBe('0x00c0ffee');
40+
});
41+
42+
it('pads handle to 8 hex digits', async () => {
43+
const driver = createMockDriver() as any;
44+
driver.sendPowerShellCommand.mockResolvedValue('1'); // 0x00000001
45+
const result = await getWindowHandle.call(driver);
46+
expect(result).toBe('0x00000001');
47+
});
48+
});
49+
50+
describe('getWindowHandles', () => {
51+
beforeEach(() => vi.clearAllMocks());
52+
53+
it('returns array of hex window handles for each child window', async () => {
54+
const driver = createMockDriver() as any;
55+
// First call: findAll children of rootElement → two element IDs
56+
// Subsequent calls: getNativeWindowHandle for each child
57+
driver.sendPowerShellCommand
58+
.mockResolvedValueOnce('1.1.1\n2.2.2') // findAll
59+
.mockResolvedValueOnce('100') // handle for element 1
60+
.mockResolvedValueOnce('200'); // handle for element 2
61+
62+
const result = await getWindowHandles.call(driver);
63+
expect(result).toHaveLength(2);
64+
expect(result[0]).toBe('0x00000064'); // 100 = 0x64
65+
expect(result[1]).toBe('0x000000c8'); // 200 = 0xC8
66+
});
67+
68+
it('returns empty array when no child windows found', async () => {
69+
const driver = createMockDriver() as any;
70+
driver.sendPowerShellCommand.mockResolvedValue(''); // no elements
71+
const result = await getWindowHandles.call(driver);
72+
expect(result).toEqual([]);
73+
});
74+
});
75+
76+
describe('getWindowRect', () => {
77+
beforeEach(() => vi.clearAllMocks());
78+
79+
it('returns parsed rect object', async () => {
80+
const driver = createMockDriver() as any;
81+
driver.sendPowerShellCommand.mockResolvedValue('{"x":10,"y":20,"width":800,"height":600}');
82+
const result = await getWindowRect.call(driver);
83+
expect(result).toEqual({ x: 10, y: 20, width: 800, height: 600 });
84+
});
85+
86+
it('replaces Infinity with max int32', async () => {
87+
const driver = createMockDriver() as any;
88+
driver.sendPowerShellCommand.mockResolvedValue('{"x":Infinity,"y":0,"width":Infinity,"height":0}');
89+
const result = await getWindowRect.call(driver);
90+
expect(result.x).toBe(0x7FFFFFFF);
91+
expect(result.width).toBe(0x7FFFFFFF);
92+
});
93+
});
94+
95+
describe('setWindow', () => {
96+
beforeEach(() => vi.clearAllMocks());
97+
98+
it('sets root element by numeric handle', async () => {
99+
const driver = createMockDriver() as any;
100+
const { trySetForegroundWindow } = await import('../../../lib/winapi/user32');
101+
102+
// findFirst returns a valid element ID
103+
driver.sendPowerShellCommand.mockResolvedValue('1.2.3');
104+
await setWindow.call(driver, '12345');
105+
expect(driver.sendPowerShellCommand).toHaveBeenCalled();
106+
// The last PS command should set $rootElement
107+
const calls = driver.sendPowerShellCommand.mock.calls;
108+
const setRootCall = calls.find((c: any[]) => c[0].includes('$rootElement ='));
109+
expect(setRootCall).toBeDefined();
110+
expect(trySetForegroundWindow).toHaveBeenCalledWith(12345);
111+
});
112+
113+
it('sets root element by window name', async () => {
114+
const driver = createMockDriver() as any;
115+
116+
driver.sendPowerShellCommand
117+
.mockResolvedValueOnce('') // numeric handle search fails (NaN)
118+
.mockResolvedValueOnce('5.6.7'); // name search succeeds
119+
120+
await setWindow.call(driver, 'Calculator');
121+
const calls = driver.sendPowerShellCommand.mock.calls;
122+
const setRootCall = calls.find((c: any[]) => c[0].includes('$rootElement ='));
123+
expect(setRootCall).toBeDefined();
124+
});
125+
126+
it('throws NoSuchWindowError when window is not found after retries', async () => {
127+
const driver = createMockDriver() as any;
128+
// All calls return empty (window not found)
129+
driver.sendPowerShellCommand.mockResolvedValue('');
130+
131+
await expect(setWindow.call(driver, 'NonExistentWindow')).rejects.toThrow('No window was found');
132+
}, 10000);
133+
});

test/commands/device.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Unit tests for lib/commands/device.ts
3+
*/
4+
import { describe, it, expect, vi, beforeEach } from 'vitest';
5+
import { getDeviceTime } from '../../lib/commands/device';
6+
import { createMockDriver } from '../fixtures/driver';
7+
8+
/** Decode base64 Invoke-Expression wrappers to reveal the underlying PS command. */
9+
function decodeCommand(cmd: string): string {
10+
const match = cmd.match(/FromBase64String\('([^']+)'\)/);
11+
if (!match) {return cmd;}
12+
return decodeCommand(Buffer.from(match[1], 'base64').toString('utf8'));
13+
}
14+
15+
describe('getDeviceTime', () => {
16+
beforeEach(() => vi.clearAllMocks());
17+
18+
it('returns formatted date string from PS command', async () => {
19+
const driver = createMockDriver() as any;
20+
driver.sendPowerShellCommand.mockResolvedValue('2026-02-25T10:30:00+00:00');
21+
const result = await getDeviceTime.call(driver);
22+
expect(result).toBe('2026-02-25T10:30:00+00:00');
23+
expect(driver.sendPowerShellCommand).toHaveBeenCalledTimes(1);
24+
});
25+
26+
it('uses ISO 8061 format by default when no format provided', async () => {
27+
const driver = createMockDriver() as any;
28+
driver.sendPowerShellCommand.mockResolvedValue('2026-02-25T10:30:00+00:00');
29+
await getDeviceTime.call(driver);
30+
const cmd = decodeCommand(driver.sendPowerShellCommand.mock.calls[0][0]);
31+
expect(cmd).toContain('Get-Date');
32+
expect(cmd).toContain('yyyy-MM-ddTHH:mm:sszzz');
33+
});
34+
35+
it('uses custom format when provided', async () => {
36+
const driver = createMockDriver() as any;
37+
driver.sendPowerShellCommand.mockResolvedValue('25/02/2026');
38+
const result = await getDeviceTime.call(driver, 'dd/MM/yyyy');
39+
expect(result).toBe('25/02/2026');
40+
const cmd = decodeCommand(driver.sendPowerShellCommand.mock.calls[0][0]);
41+
expect(cmd).toContain('Get-Date');
42+
// The custom format is embedded as a PSString (unicode-escaped)
43+
expect(cmd).toContain('ToString');
44+
});
45+
});

0 commit comments

Comments
 (0)