Skip to content

Commit df5c2e7

Browse files
committed
added worker thread tests
1 parent ebc8a70 commit df5c2e7

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed

src/services/thread.spec.ts

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/**
2+
* Copyright (c) 2024 The Diffusion Studio Authors
3+
*
4+
* This Source Code Form is subject to the terms of the Mozilla
5+
* Public License, v. 2.0 that can be found in the LICENSE file.
6+
*/
7+
8+
import { describe, expect, it, vi } from 'vitest';
9+
import { Thread, withThreadErrorHandler } from './thread';
10+
11+
// Mocking the Worker class to simulate a Web Worker
12+
class MockWorker {
13+
onmessage: ((event: MessageEvent<any>) => void) | null = null;
14+
onerror: ((event: ErrorEvent) => void) | null = null;
15+
terminated = false;
16+
17+
postMessage = vi.fn();
18+
19+
addEventListener(event: string, handler: (event: MessageEvent<any>) => void) {
20+
if (event === 'message') {
21+
this.onmessage = handler;
22+
}
23+
}
24+
25+
terminate() {
26+
this.terminated = true;
27+
}
28+
29+
// Simulate receiving a message
30+
simulateMessage(data: any) {
31+
if (this.onmessage) {
32+
this.onmessage({ data } as MessageEvent);
33+
}
34+
}
35+
36+
// Simulate an error
37+
simulateError(message: string) {
38+
if (this.onerror) {
39+
this.onerror(new ErrorEvent('error', { message }));
40+
}
41+
}
42+
}
43+
44+
describe('Thread', () => {
45+
it('should post a message and receive a result', async () => {
46+
const mockWorkerConstructor = vi.fn(() => new MockWorker());
47+
const thread = new Thread<typeof MockWorker>(mockWorkerConstructor as any);
48+
49+
const resultPromise = thread.run<{ input: number }>({ input: 42 });
50+
51+
// Simulate the worker responding with a result
52+
const mockWorkerInstance = mockWorkerConstructor.mock.results[0].value;
53+
mockWorkerInstance.simulateMessage({ type: 'result', result: 84 });
54+
55+
const response = await resultPromise;
56+
57+
expect(mockWorkerInstance.postMessage).toHaveBeenCalledWith({ type: 'init', input: 42 });
58+
59+
// Adjust assertion to handle the structure returned by the `Thread.run()` method
60+
expect(response).toEqual({ result: { result: 84, type: undefined }, error: undefined });
61+
62+
expect(mockWorkerInstance.terminated).toBe(true);
63+
});
64+
65+
it('should handle worker errors and return error', async () => {
66+
const mockWorkerConstructor = vi.fn(() => new MockWorker());
67+
const thread = new Thread<typeof MockWorker>(mockWorkerConstructor as any);
68+
69+
const resultPromise = thread.run();
70+
71+
// Simulate the worker responding with an error
72+
const mockWorkerInstance = mockWorkerConstructor.mock.results[0].value;
73+
mockWorkerInstance.simulateMessage({ type: 'error', message: 'Something went wrong' });
74+
75+
const response = await resultPromise;
76+
77+
expect(response).toEqual({ result: undefined, error: 'Something went wrong' });
78+
expect(mockWorkerInstance.terminated).toBe(true);
79+
});
80+
81+
it('should call the event listener if provided', async () => {
82+
const mockWorkerConstructor = vi.fn(() => new MockWorker());
83+
const thread = new Thread<typeof MockWorker>(mockWorkerConstructor as any);
84+
85+
const eventListener = vi.fn();
86+
const resultPromise = thread.run(undefined, eventListener);
87+
88+
// Simulate the worker responding with a result
89+
const mockWorkerInstance = mockWorkerConstructor.mock.results[0].value;
90+
mockWorkerInstance.simulateMessage({ type: 'result', result: 84 });
91+
92+
await resultPromise;
93+
94+
// Adjust assertion to account for the fact that `type` is set to undefined
95+
expect(eventListener).toHaveBeenCalledWith({ result: 84, type: undefined });
96+
});
97+
98+
it('should terminate the worker even on error', async () => {
99+
const mockWorkerConstructor = vi.fn(() => new MockWorker());
100+
const thread = new Thread<typeof MockWorker>(mockWorkerConstructor as any);
101+
102+
const resultPromise = thread.run();
103+
104+
// Simulate the worker responding with an error
105+
const mockWorkerInstance = mockWorkerConstructor.mock.results[0].value;
106+
107+
// Simulate a message with an error type
108+
mockWorkerInstance.simulateMessage({ type: 'error', message: 'Worker failed' });
109+
110+
const response = await resultPromise;
111+
112+
expect(response).toEqual({ result: undefined, error: 'Worker failed' });
113+
expect(mockWorkerInstance.terminated).toBe(true);
114+
});
115+
});
116+
117+
describe('withThreadErrorHandler', () => {
118+
it('should call the main function and post an error on failure', async () => {
119+
const mockPostMessage = vi.fn();
120+
const mainFunction = vi.fn().mockRejectedValue(new Error('Test error'));
121+
122+
// Mock the global `self` object to intercept postMessage calls
123+
globalThis.self = { postMessage: mockPostMessage } as any;
124+
125+
const handler = withThreadErrorHandler(mainFunction);
126+
127+
const event = { data: 'test-event' } as MessageEvent<any>;
128+
await handler(event);
129+
130+
expect(mainFunction).toHaveBeenCalledWith(event);
131+
expect(mockPostMessage).toHaveBeenCalledWith({
132+
type: 'error',
133+
message: 'Test error',
134+
});
135+
});
136+
137+
it('should post a default error message if no error message is provided', async () => {
138+
const mockPostMessage = vi.fn();
139+
const mainFunction = vi.fn().mockRejectedValue({});
140+
141+
// Mock the global `self` object to intercept postMessage calls
142+
globalThis.self = { postMessage: mockPostMessage } as any;
143+
144+
const handler = withThreadErrorHandler(mainFunction);
145+
146+
const event = { data: 'test-event' } as MessageEvent<any>;
147+
await handler(event);
148+
149+
expect(mainFunction).toHaveBeenCalledWith(event);
150+
expect(mockPostMessage).toHaveBeenCalledWith({
151+
type: 'error',
152+
message: 'An unkown worker error occured',
153+
});
154+
});
155+
156+
it('should not post an error if the main function succeeds', async () => {
157+
const mockPostMessage = vi.fn();
158+
const mainFunction = vi.fn().mockResolvedValue(undefined);
159+
160+
// Mock the global `self` object to intercept postMessage calls
161+
globalThis.self = { postMessage: mockPostMessage } as any;
162+
163+
const handler = withThreadErrorHandler(mainFunction);
164+
165+
const event = { data: 'test-event' } as MessageEvent<any>;
166+
await handler(event);
167+
168+
expect(mainFunction).toHaveBeenCalledWith(event);
169+
expect(mockPostMessage).not.toHaveBeenCalled();
170+
});
171+
});

0 commit comments

Comments
 (0)