Skip to content

Commit 4f37a8c

Browse files
committed
test: add Harness test suites
1 parent 47bcbb0 commit 4f37a8c

3 files changed

Lines changed: 328 additions & 7 deletions

File tree

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import { describe, it, expect, afterEach } from 'react-native-harness';
2+
import { Worker } from 'react-native-webworker';
3+
4+
// Helper to prevent tests from hanging indefinitely
5+
function withTimeout<T>(
6+
promise: Promise<T>,
7+
ms: number = 2000,
8+
msg: string = 'Operation timed out'
9+
): Promise<T> {
10+
return Promise.race([
11+
promise,
12+
new Promise<T>((_, reject) => setTimeout(() => reject(new Error(msg)), ms)),
13+
]);
14+
}
15+
16+
describe('EventLoop', () => {
17+
let worker: Worker;
18+
19+
afterEach(async () => {
20+
await worker.terminate();
21+
});
22+
23+
it('should execute setTimeout callback', async () => {
24+
worker = new Worker({
25+
script: `
26+
self.onmessage = function() {
27+
setTimeout(function() {
28+
self.postMessage('timeout executed');
29+
}, 50);
30+
};
31+
`,
32+
});
33+
34+
const responsePromise = new Promise<string>((resolve) => {
35+
worker.onmessage = (event) => resolve(event.data as string);
36+
});
37+
38+
await worker.postMessage('start');
39+
40+
const response = await withTimeout(
41+
responsePromise,
42+
1000,
43+
'Worker did not respond with timeout message'
44+
);
45+
46+
expect(response).toBe('timeout executed');
47+
});
48+
49+
it('should execute setInterval multiple times and then clear it', async () => {
50+
worker = new Worker({
51+
script: `
52+
self.onmessage = function() {
53+
let count = 0;
54+
const id = setInterval(function() {
55+
count++;
56+
self.postMessage({ type: 'tick', count: count });
57+
if (count >= 3) {
58+
clearInterval(id);
59+
self.postMessage({ type: 'done' });
60+
}
61+
}, 50);
62+
};
63+
`,
64+
});
65+
66+
const ticks: number[] = [];
67+
68+
const intervalPromise = new Promise<void>((resolve) => {
69+
worker.onmessage = (event) => {
70+
const data = event.data as { type: string; count?: number };
71+
if (data.type === 'tick') {
72+
ticks.push(data.count!);
73+
} else if (data.type === 'done') {
74+
resolve();
75+
}
76+
};
77+
});
78+
79+
await worker.postMessage('start');
80+
81+
await withTimeout(
82+
intervalPromise,
83+
2000,
84+
'Worker did not complete interval sequence'
85+
);
86+
87+
// Wait a bit longer to ensure no more ticks come in (proving clearInterval worked)
88+
await new Promise((resolve) => setTimeout(resolve, 150));
89+
90+
expect(ticks).toEqual([1, 2, 3]);
91+
});
92+
93+
it('should support clearTimeout to cancel execution', async () => {
94+
worker = new Worker({
95+
script: `
96+
self.onmessage = function() {
97+
const id = setTimeout(function() {
98+
self.postMessage('should not happen');
99+
}, 50);
100+
101+
clearTimeout(id);
102+
103+
// Send a safe message after a delay to prove the other one didn't happen
104+
setTimeout(function() {
105+
self.postMessage('safe');
106+
}, 100);
107+
};
108+
`,
109+
});
110+
111+
const messages: string[] = [];
112+
113+
const cancelPromise = new Promise<void>((resolve) => {
114+
worker.onmessage = (event) => {
115+
messages.push(event.data as string);
116+
// If we get 'safe', we are done.
117+
// If we get 'should not happen', we also resolve so we can fail the assertion.
118+
if (event.data === 'safe' || event.data === 'should not happen') {
119+
resolve();
120+
}
121+
};
122+
});
123+
124+
await worker.postMessage('start');
125+
126+
await withTimeout(cancelPromise, 1000, 'Worker did not respond');
127+
128+
expect(messages).toContain('safe');
129+
expect(messages).not.toContain('should not happen');
130+
});
131+
132+
it('should prioritize microtasks (Promises) over macrotasks (setTimeout)', async () => {
133+
worker = new Worker({
134+
script: `
135+
self.onmessage = function() {
136+
const logs = [];
137+
138+
setTimeout(function() {
139+
logs.push('macro');
140+
if (logs.length === 2) {
141+
self.postMessage(logs);
142+
}
143+
}, 0);
144+
145+
Promise.resolve().then(function() {
146+
logs.push('micro');
147+
if (logs.length === 2) {
148+
self.postMessage(logs);
149+
}
150+
});
151+
};
152+
`,
153+
});
154+
155+
const logsPromise = new Promise<string[]>((resolve) => {
156+
worker.onmessage = (event) => resolve(event.data as string[]);
157+
});
158+
159+
await worker.postMessage('start');
160+
161+
const logs = await withTimeout(
162+
logsPromise,
163+
1000,
164+
'Worker did not return execution order logs'
165+
);
166+
167+
expect(logs).toEqual(['micro', 'macro']);
168+
});
169+
170+
it('should execute setImmediate', async () => {
171+
worker = new Worker({
172+
script: `
173+
self.onmessage = function() {
174+
setImmediate(function() {
175+
self.postMessage('immediate executed');
176+
});
177+
};
178+
`,
179+
});
180+
181+
const responsePromise = new Promise<string>((resolve) => {
182+
worker.onmessage = (event) => resolve(event.data as string);
183+
});
184+
185+
await worker.postMessage('start');
186+
187+
const response = await withTimeout(
188+
responsePromise,
189+
1000,
190+
'Worker did not execute setImmediate'
191+
);
192+
193+
expect(response).toBe('immediate executed');
194+
});
195+
});

example/src/__tests__/sanity.harness.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { describe, it, expect, afterEach } from 'react-native-harness';
2+
import { Worker } from 'react-native-webworker';
3+
4+
// Helper to prevent tests from hanging indefinitely
5+
function withTimeout<T>(
6+
promise: Promise<T>,
7+
ms: number = 2000,
8+
msg: string = 'Operation timed out'
9+
): Promise<T> {
10+
return Promise.race([
11+
promise,
12+
new Promise<T>((_, reject) => setTimeout(() => reject(new Error(msg)), ms)),
13+
]);
14+
}
15+
16+
describe('WebWorker', () => {
17+
let worker: Worker;
18+
19+
afterEach(async () => {
20+
await worker.terminate();
21+
});
22+
23+
it('should create a worker and receive a message back', async () => {
24+
worker = new Worker({
25+
script: `
26+
self.onmessage = function(event) {
27+
self.postMessage('echo: ' + event.data);
28+
};
29+
`,
30+
name: 'test-worker',
31+
});
32+
33+
const responsePromise = new Promise<string>((resolve, reject) => {
34+
worker.onmessage = (event) => {
35+
resolve(event.data as string);
36+
};
37+
worker.onerror = (err) => {
38+
reject(err);
39+
};
40+
});
41+
42+
await worker.postMessage('hello');
43+
44+
const response = await withTimeout(
45+
responsePromise,
46+
2000,
47+
'Worker did not echo message'
48+
);
49+
50+
expect(response).toBe('echo: hello');
51+
});
52+
53+
it('should handle complex objects', async () => {
54+
worker = new Worker({
55+
script: `
56+
self.onmessage = function(event) {
57+
const data = event.data;
58+
self.postMessage({
59+
received: data,
60+
modified: true
61+
});
62+
};
63+
`,
64+
});
65+
66+
const payload = { foo: 'bar', num: 123 };
67+
const responsePromise = new Promise<any>((resolve, reject) => {
68+
worker.onmessage = (event) => {
69+
resolve(event.data);
70+
};
71+
worker.onerror = (err) => {
72+
reject(err);
73+
};
74+
});
75+
76+
await worker.postMessage(payload);
77+
78+
const response = await withTimeout(
79+
responsePromise,
80+
1000,
81+
'Worker did not handle complex object'
82+
);
83+
84+
expect(response.received.foo).toBe(payload.foo);
85+
expect(response.received.num).toBe(payload.num);
86+
expect(response.modified).toBe(true);
87+
});
88+
89+
it('should handle errors from worker', async () => {
90+
worker = new Worker({
91+
script: `
92+
self.onmessage = function() {
93+
throw new Error('Test Error');
94+
};
95+
`,
96+
});
97+
98+
const errorPromise = new Promise<string>((resolve) => {
99+
worker.onerror = (err) => {
100+
resolve(err.message);
101+
};
102+
});
103+
104+
await worker.postMessage('trigger error');
105+
106+
const errorMsg = await withTimeout(
107+
errorPromise,
108+
1000,
109+
'Worker did not report error'
110+
);
111+
112+
// The error message format might depend on implementation, but should contain the error text
113+
expect(errorMsg.includes('Test Error')).toBe(true);
114+
});
115+
116+
it('should throw when posting to terminated worker', async () => {
117+
worker = new Worker({
118+
script: 'self.onmessage = function() {}',
119+
});
120+
121+
await worker.terminate();
122+
123+
let error;
124+
try {
125+
await worker.postMessage('test');
126+
} catch (e) {
127+
error = e;
128+
}
129+
130+
expect(error).not.toBe(undefined);
131+
expect((error as Error).message).toBe('Worker has been terminated');
132+
});
133+
});

0 commit comments

Comments
 (0)