Skip to content

Commit 3f55fb9

Browse files
goosewobblerclaude
andauthored
test: unit & integration testing improvements (#213)
* test(electron): test improvements - Modified assertions in `bridge.spec.ts` to expect `client.close()` to resolve to `undefined` instead of not throwing an error. - Updated error messages in `getDebuggerEndpoint` tests in `bridge.spec.ts` to be more specific. - Changed `describe` blocks in various test files from async to synchronous for consistency. - Removed outdated test files and added new tests for mock operations to enhance coverage and maintainability. * test(electron): enhance test coverage and error handling - Added new tests in `launcher.spec.ts` to verify behavior when only `appEntryPoint` is provided and to handle unexpected errors from `getBinaryPath`. - Introduced tests in `pathResolver.spec.ts` for `getElectronBinaryPath`, covering successful resolution, package.json not found, and binary path resolution failures. - Improved mock implementations in `mock.spec.ts` and `triggerDeeplink.spec.ts` to ensure accurate behavior and error handling during tests. - Updated assertions and added caching logic for userDataDir detection in `triggerDeeplink.spec.ts` to enhance reliability. * fix: update command execution for cross-platform compatibility - Modified the command execution in `build-package.ts` to use `cmd.exe` on Windows and `pnpm` on other platforms, ensuring proper invocation of the build process. - Adjusted arguments for the spawn function to accommodate the changes in command structure, enhancing compatibility across different operating systems. * fix(electron): resolve TypeScript errors in test suite Fix pre-existing TS errors: vitest module augmentation shadowing, wrong import source for waitUntilWindowAvailable, missing Result type narrowing guards, and mismatched Assertion type parameters. Co-Authored-By: Claude Opus 4.6 <[email protected]> * test(electron): add coverage tests for mockAll, isMockFunction, mock, and pathResolver Add tests for execute params/callback in mockAll, class mock path in mock command, and getElectronBinaryPath resolution in pathResolver. Co-Authored-By: Claude Opus 4.6 <[email protected]> * test: update mock function types for better TypeScript compatibility Refine the type definitions for mocked functions in the mockOperations test suite to allow for both function return types and string return types, enhancing type safety and clarity in the tests. * chore: remove redundant mock definitions Removed redundant mock definitions for appBuildInfo, binaryPath, and electronVersion, simplifying the test setup in pathResolver.spec.ts. This change enhances clarity and maintainability of the test suite. * test(electron): extract MockApiMethod type for mock operation test stubs Replace inline Record union type with a named MockApiMethod type that explicitly declares getMockName, making the required property visible at the type level. Co-Authored-By: Claude Opus 4.6 <[email protected]> * test(native-spy): enhance mock function tests with new scenarios Added multiple test cases to improve coverage for mock functions, including scenarios for `mockResolvedValueOnce`, `mockRejectedValueOnce`, `mockRestore`, and context tracking. These additions ensure better validation of mock behavior and state management in various use cases. * test(native-utils): add URL support for cwd in readPackageUp and readPackageUpSync Enhanced tests for `readPackageUp` and `readPackageUpSync` to validate functionality when a URL is provided as the `cwd`. This includes checks for both asynchronous and synchronous methods, ensuring that the package JSON is correctly read from the specified URL. Additionally, improved error handling tests in `selectExecutable` to cover scenarios with empty paths and errors without message properties. * test(electron): update mock cleanup method in tests Replaced `vi.resetAllMocks()` with `vi.clearAllMocks()` in the mock operations test suite to ensure a more precise reset of mock states after each test, improving test isolation and reliability. * test(native-spy): standardise test descriptions for mock function behavior Updated test descriptions in `spy.spec.ts` to follow a consistent format, enhancing clarity and readability. Each test now begins with "should" to clearly define the expected behavior of the mock functions. * test(native-spy): add new test cases for error handling and instance tracking Introduced additional test scenarios in `spy.spec.ts` to enhance coverage for mock functions. New tests include tracking results for queued implementations that throw errors and verifying the tracking of instances, ensuring comprehensive validation of mock behavior. --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
1 parent 34e5b4c commit 3f55fb9

24 files changed

Lines changed: 704 additions & 289 deletions

@types/vitest/index.d.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import 'vitest';
2+
13
interface CustomMatchers<R = unknown> {
24
anyMockFunction(): R;
35
}
6+
47
declare module 'vitest' {
5-
interface Assertion<T = unknown> extends CustomMatchers<T> {}
8+
interface Assertion<T> extends CustomMatchers<T> {}
69
interface AsymmetricMatchersContaining extends CustomMatchers {}
710
}
8-
9-
export * from 'vitest';

packages/electron-cdp-bridge/test/bridge.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,14 +284,14 @@ describe('CdpBridge', () => {
284284
await client.connect();
285285
const result = client.close();
286286
triggerWebSocketEvent('close'); // emulate close event
287-
await expect(result).resolves.not.toThrowError();
287+
await expect(result).resolves.toBeUndefined();
288288
});
289289

290290
it('should handle calling close before connect without errors', async () => {
291291
debuggerList = [{ webSocketDebuggerUrl: 'ws://localhost:123/uuid' }];
292292
const client = new CdpBridge();
293293
const result = client.close();
294-
await expect(result).resolves.not.toThrowError();
294+
await expect(result).resolves.toBeUndefined();
295295
});
296296
});
297297

@@ -313,7 +313,7 @@ describe('CdpBridge', () => {
313313
debuggerList = [{ webSocketDebuggerUrl: 'ws://localhost:123/uuid' }];
314314
const client = new CdpBridge();
315315
const result = client.close();
316-
await expect(result).resolves.not.toThrowError();
316+
await expect(result).resolves.toBeUndefined();
317317
});
318318
});
319319
});

packages/electron-service/test/binaryPath.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ function testBinaryPath(options: TestBinaryPathOptions) {
159159

160160
// Verify the result is successful
161161
expect(result.ok).toBe(true);
162+
if (!result.ok) return;
162163
expect(result.value.binaryPath).toBeDefined();
163164

164165
// Normalize path separators for cross-platform compatibility
@@ -195,7 +196,9 @@ describe('getBinaryPath', () => {
195196
const result = await getBinaryPath(pkgJSONPath, generateAppBuildInfo(false, true), '29.3.1', currentProcess);
196197

197198
expect(result.ok).toBe(false);
199+
if (result.ok) return;
198200
expect(result.error.pathGeneration.ok).toBe(false);
201+
if (result.error.pathGeneration.ok) return;
199202
expect(result.error.pathGeneration.error?.errors).toContainEqual({
200203
type: 'UNSUPPORTED_PLATFORM',
201204
message: 'Unsupported platform: not-supported',
@@ -209,7 +212,9 @@ describe('getBinaryPath', () => {
209212
const result = await getBinaryPath(pkgJSONPath, generateAppBuildInfo(false, false), '29.3.1', currentProcess);
210213

211214
expect(result.ok).toBe(false);
215+
if (result.ok) return;
212216
expect(result.error.pathGeneration.ok).toBe(false);
217+
if (result.error.pathGeneration.ok) return;
213218
expect(result.error.pathGeneration.error?.errors).toContainEqual({
214219
type: 'NO_BUILD_TOOL',
215220
message: 'Configurations that are neither Forge nor Builder are not supported.',

packages/electron-service/test/bridge.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('getDebuggerEndpoint', () => {
2727
args: ['foo=bar'],
2828
},
2929
}),
30-
).toThrowError();
30+
).toThrowError(/--inspect/);
3131
});
3232

3333
it('should throw the error when invalid host is set', () => {
@@ -39,7 +39,7 @@ describe('getDebuggerEndpoint', () => {
3939
args: ['foo=bar', `--inspect=${host}:${port}`],
4040
},
4141
}),
42-
).toThrowError();
42+
).toThrowError(/host|port|invalid/i);
4343
});
4444

4545
it('should throw the error when invalid port number is set', () => {
@@ -51,7 +51,7 @@ describe('getDebuggerEndpoint', () => {
5151
args: ['foo=bar', `--inspect=${host}:${port}`],
5252
},
5353
}),
54-
).toThrowError();
54+
).toThrowError(/port|invalid|NaN/i);
5555
});
5656
});
5757

@@ -70,7 +70,7 @@ describe('ElectronCdpBridge', () => {
7070
beforeEach(() => {
7171
vi.clearAllMocks();
7272
});
73-
describe('connect', async () => {
73+
describe('connect', () => {
7474
const expectedContextId = 999;
7575
const getMockedInstance = async () => {
7676
const cdpBridge = new ElectronCdpBridge();

packages/electron-service/test/commands/clearAllMocks.spec.ts

Lines changed: 0 additions & 45 deletions
This file was deleted.

packages/electron-service/test/commands/executeCdp.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ describe('execute Command', () => {
8282
});
8383

8484
it('should execute a function when multi remote browser', async () => {
85-
const mockElectron = globalThis.mrBrowser.getInstance();
85+
const mockElectron = globalThis.mrBrowser.getInstance('browserA');
8686
await execute(globalThis.mrBrowser, client, (_electron, a, b, c) => a + b + c, 1, 2, 3);
8787

8888
expect(mockElectron.electron.execute).toHaveBeenCalledTimes(2); // Because mrBrowser has 2 browser instance

packages/electron-service/test/commands/isMockFunction.spec.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/// <reference types="../../@types/vitest" />
21
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
32
import { isMockFunction } from '../../src/commands/isMockFunction.js';
43
import { createMock } from '../../src/mock.js';

packages/electron-service/test/commands/mock.spec.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
22

3-
import { createMock } from '../../src/mock.js';
3+
import { createClassMock, createMock } from '../../src/mock.js';
44
import mockStore from '../../src/mockStore.js';
55

66
vi.mock('../../src/mock.js', () => ({
77
createMock: vi.fn(),
8+
createClassMock: vi.fn(),
89
}));
910
vi.mock('../../src/mockStore.js', () => ({
1011
default: {
@@ -69,4 +70,27 @@ describe('mock Command', () => {
6970
expect(mockStore.setMock).toBeCalledWith(mockedGetName);
7071
});
7172
});
73+
74+
describe('class mock path (no funcName)', () => {
75+
beforeEach(() => {
76+
vi.clearAllMocks();
77+
});
78+
79+
it('should call createClassMock when no funcName is provided', async () => {
80+
const mockClassInstance = { getMockName: () => 'electron.Tray', __constructor: vi.fn() };
81+
(createClassMock as Mock).mockResolvedValue(mockClassInstance);
82+
83+
const result = await mock('Tray');
84+
expect(createClassMock).toHaveBeenCalledWith('Tray', undefined);
85+
expect(result).toBe(mockClassInstance);
86+
});
87+
88+
it('should not interact with mockStore for class mocks', async () => {
89+
(createClassMock as Mock).mockResolvedValue({ getMockName: () => 'electron.Tray' });
90+
91+
await mock('Tray');
92+
expect(mockStore.getMock).not.toHaveBeenCalled();
93+
expect(mockStore.setMock).not.toHaveBeenCalled();
94+
});
95+
});
7296
});

packages/electron-service/test/commands/mockAll.spec.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
/// <reference types="../../@types/vitest" />
21
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
32

43
import { mockAll } from '../../src/commands/mockAll.js';
54

5+
// Bind to a minimal context to satisfy the `this: ElectronServiceContext` parameter
6+
const callMockAll = mockAll.bind({});
7+
68
describe('mockAll Command', () => {
79
beforeEach(async () => {
810
globalThis.browser = {
@@ -21,7 +23,7 @@ describe('mockAll Command', () => {
2123
});
2224

2325
it('should return mock functions for all API methods', async () => {
24-
const mockedDialog = await mockAll('dialog');
26+
const mockedDialog = await callMockAll('dialog');
2527
expect(mockedDialog).toStrictEqual({
2628
showOpenDialogSync: expect.anyMockFunction(),
2729
showOpenDialog: expect.anyMockFunction(),
@@ -36,4 +38,21 @@ describe('mockAll Command', () => {
3638
expect(mockedDialog.showOpenDialog).toHaveProperty('mock');
3739
expect(mockedDialog.showOpenDialog).toHaveProperty('mockImplementation');
3840
});
41+
42+
it('should call execute with the api name and internal flag', async () => {
43+
await callMockAll('dialog');
44+
expect(globalThis.browser.electron.execute).toHaveBeenCalledWith(expect.any(Function), 'dialog', {
45+
internal: true,
46+
});
47+
});
48+
49+
it('should call the execute callback to extract keys from electron api', async () => {
50+
await callMockAll('dialog');
51+
const executeCallback = vi.mocked(globalThis.browser.electron.execute).mock.calls[0][0] as unknown as (
52+
electron: Record<string, Record<string, unknown>>,
53+
apiName: string,
54+
) => string;
55+
const fakeElectron = { dialog: { foo: 1, bar: 2 } };
56+
expect(executeCallback(fakeElectron, 'dialog')).toBe('foo,bar');
57+
});
3958
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
2+
3+
import { clearAllMocks } from '../../src/commands/clearAllMocks.js';
4+
import { resetAllMocks } from '../../src/commands/resetAllMocks.js';
5+
import { restoreAllMocks } from '../../src/commands/restoreAllMocks.js';
6+
import mockStore from '../../src/mockStore.js';
7+
8+
vi.mock('../../src/mockStore.js', () => ({
9+
default: {
10+
getMocks: vi.fn(),
11+
},
12+
}));
13+
14+
type MockApiMethod = { getMockName: () => string; [key: string]: ReturnType<typeof vi.fn> | (() => string) };
15+
16+
describe.each([
17+
{ name: 'clearAllMocks', fn: clearAllMocks, mockMethod: 'mockClear' },
18+
{ name: 'resetAllMocks', fn: resetAllMocks, mockMethod: 'mockReset' },
19+
{ name: 'restoreAllMocks', fn: restoreAllMocks, mockMethod: 'mockRestore' },
20+
] as const)('$name Command', ({ fn, mockMethod }) => {
21+
let mockedGetName: MockApiMethod;
22+
let mockedShowOpenDialog: MockApiMethod;
23+
24+
beforeEach(() => {
25+
mockedGetName = {
26+
getMockName: () => 'electron.app.getName',
27+
[mockMethod]: vi.fn(),
28+
};
29+
mockedShowOpenDialog = {
30+
getMockName: () => 'electron.dialog.showOpenDialog',
31+
[mockMethod]: vi.fn(),
32+
};
33+
(mockStore.getMocks as Mock).mockReturnValue([
34+
['electron.app.getName', mockedGetName],
35+
['electron.dialog.showOpenDialog', mockedShowOpenDialog],
36+
]);
37+
});
38+
39+
afterEach(() => {
40+
vi.clearAllMocks();
41+
});
42+
43+
it('should operate on all mock functions when no apiName is specified', async () => {
44+
await fn();
45+
expect(mockedGetName[mockMethod]).toHaveBeenCalled();
46+
expect(mockedShowOpenDialog[mockMethod]).toHaveBeenCalled();
47+
});
48+
49+
it('should operate only on mock functions for a specific API', async () => {
50+
await fn('app');
51+
expect(mockedGetName[mockMethod]).toHaveBeenCalled();
52+
expect(mockedShowOpenDialog[mockMethod]).not.toHaveBeenCalled();
53+
});
54+
});

0 commit comments

Comments
 (0)