ai-tests 2.0.2 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -5
- package/.turbo/turbo-test.log +8 -0
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/dist/assertions.d.ts +79 -7
- package/dist/assertions.d.ts.map +1 -1
- package/dist/assertions.js +80 -14
- package/dist/assertions.js.map +1 -1
- package/package.json +13 -14
- package/src/assertions.js +383 -0
- package/src/assertions.ts +143 -32
- package/src/cli.js +76 -0
- package/src/index.js +18 -0
- package/src/local.js +62 -0
- package/src/runner.js +214 -0
- package/src/types.js +4 -0
- package/src/worker.js +91 -0
- package/test/assertions.test.js +493 -0
- package/test/index.test.js +42 -0
- package/test/local.test.js +27 -0
- package/test/runner.test.js +315 -0
- package/test/type-safety-assertions.test.ts +201 -0
- package/test/worker.test.js +162 -0
- package/vitest.config.js +10 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { TestRunner, createRunner } from '../src/runner.js';
|
|
3
|
+
describe('TestRunner', () => {
|
|
4
|
+
let runner;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
runner = createRunner();
|
|
7
|
+
});
|
|
8
|
+
describe('createRunner()', () => {
|
|
9
|
+
it('creates a new TestRunner instance', () => {
|
|
10
|
+
expect(runner).toBeInstanceOf(TestRunner);
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
describe('describe()', () => {
|
|
14
|
+
it('groups tests with a suite name', async () => {
|
|
15
|
+
runner.describe('math', () => {
|
|
16
|
+
runner.it('adds', () => { });
|
|
17
|
+
});
|
|
18
|
+
const results = await runner.run();
|
|
19
|
+
expect(results.tests[0].name).toBe('math > adds');
|
|
20
|
+
});
|
|
21
|
+
it('supports nested describes', async () => {
|
|
22
|
+
runner.describe('outer', () => {
|
|
23
|
+
runner.describe('inner', () => {
|
|
24
|
+
runner.it('test', () => { });
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
const results = await runner.run();
|
|
28
|
+
expect(results.tests[0].name).toBe('outer > inner > test');
|
|
29
|
+
});
|
|
30
|
+
it('restores previous suite after describe', async () => {
|
|
31
|
+
runner.describe('first', () => {
|
|
32
|
+
runner.it('test1', () => { });
|
|
33
|
+
});
|
|
34
|
+
runner.it('standalone', () => { });
|
|
35
|
+
const results = await runner.run();
|
|
36
|
+
expect(results.tests[0].name).toBe('first > test1');
|
|
37
|
+
expect(results.tests[1].name).toBe('standalone');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('it()', () => {
|
|
41
|
+
it('registers a test', async () => {
|
|
42
|
+
runner.it('my test', () => { });
|
|
43
|
+
const results = await runner.run();
|
|
44
|
+
expect(results.total).toBe(1);
|
|
45
|
+
expect(results.tests[0].name).toBe('my test');
|
|
46
|
+
});
|
|
47
|
+
it('test() is alias for it()', async () => {
|
|
48
|
+
runner.test('my test', () => { });
|
|
49
|
+
const results = await runner.run();
|
|
50
|
+
expect(results.total).toBe(1);
|
|
51
|
+
});
|
|
52
|
+
it('passes for successful tests', async () => {
|
|
53
|
+
runner.it('passes', () => {
|
|
54
|
+
// No error
|
|
55
|
+
});
|
|
56
|
+
const results = await runner.run();
|
|
57
|
+
expect(results.passed).toBe(1);
|
|
58
|
+
expect(results.failed).toBe(0);
|
|
59
|
+
});
|
|
60
|
+
it('fails for throwing tests', async () => {
|
|
61
|
+
runner.it('fails', () => {
|
|
62
|
+
throw new Error('test error');
|
|
63
|
+
});
|
|
64
|
+
const results = await runner.run();
|
|
65
|
+
expect(results.passed).toBe(0);
|
|
66
|
+
expect(results.failed).toBe(1);
|
|
67
|
+
expect(results.tests[0].error).toContain('test error');
|
|
68
|
+
});
|
|
69
|
+
it('supports async tests', async () => {
|
|
70
|
+
runner.it('async', async () => {
|
|
71
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
72
|
+
});
|
|
73
|
+
const results = await runner.run();
|
|
74
|
+
expect(results.passed).toBe(1);
|
|
75
|
+
});
|
|
76
|
+
it('catches async errors', async () => {
|
|
77
|
+
runner.it('async fail', async () => {
|
|
78
|
+
await Promise.reject(new Error('async error'));
|
|
79
|
+
});
|
|
80
|
+
const results = await runner.run();
|
|
81
|
+
expect(results.failed).toBe(1);
|
|
82
|
+
expect(results.tests[0].error).toContain('async error');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('skip()', () => {
|
|
86
|
+
it('marks test as skipped', async () => {
|
|
87
|
+
runner.skip('skipped test', () => {
|
|
88
|
+
throw new Error('should not run');
|
|
89
|
+
});
|
|
90
|
+
const results = await runner.run();
|
|
91
|
+
expect(results.total).toBe(1);
|
|
92
|
+
expect(results.skipped).toBe(1);
|
|
93
|
+
expect(results.passed).toBe(0);
|
|
94
|
+
expect(results.failed).toBe(0);
|
|
95
|
+
expect(results.tests[0].skipped).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
it('works without fn argument', async () => {
|
|
98
|
+
runner.skip('skipped');
|
|
99
|
+
const results = await runner.run();
|
|
100
|
+
expect(results.skipped).toBe(1);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('only()', () => {
|
|
104
|
+
it('runs only marked tests', async () => {
|
|
105
|
+
runner.it('normal', () => { });
|
|
106
|
+
runner.only('only', () => { });
|
|
107
|
+
const results = await runner.run();
|
|
108
|
+
expect(results.total).toBe(1);
|
|
109
|
+
expect(results.tests[0].name).toBe('only');
|
|
110
|
+
});
|
|
111
|
+
it('still includes skipped tests when only is used', async () => {
|
|
112
|
+
runner.it('normal', () => { });
|
|
113
|
+
runner.only('only', () => { });
|
|
114
|
+
runner.skip('skipped', () => { });
|
|
115
|
+
const results = await runner.run();
|
|
116
|
+
expect(results.total).toBe(2);
|
|
117
|
+
expect(results.tests.find(t => t.name === 'only')).toBeDefined();
|
|
118
|
+
expect(results.tests.find(t => t.name === 'skipped')).toBeDefined();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('beforeEach()', () => {
|
|
122
|
+
it('runs before each test', async () => {
|
|
123
|
+
let count = 0;
|
|
124
|
+
runner.beforeEach(() => {
|
|
125
|
+
count++;
|
|
126
|
+
});
|
|
127
|
+
runner.it('test1', () => { });
|
|
128
|
+
runner.it('test2', () => { });
|
|
129
|
+
await runner.run();
|
|
130
|
+
expect(count).toBe(2);
|
|
131
|
+
});
|
|
132
|
+
it('hooks are scoped to describe blocks', async () => {
|
|
133
|
+
let count = 0;
|
|
134
|
+
runner.describe('suite', () => {
|
|
135
|
+
runner.beforeEach(() => {
|
|
136
|
+
count++;
|
|
137
|
+
});
|
|
138
|
+
runner.it('inside', () => { });
|
|
139
|
+
});
|
|
140
|
+
runner.it('outside', () => { });
|
|
141
|
+
await runner.run();
|
|
142
|
+
expect(count).toBe(1);
|
|
143
|
+
});
|
|
144
|
+
it('supports async hooks', async () => {
|
|
145
|
+
let value = 0;
|
|
146
|
+
runner.beforeEach(async () => {
|
|
147
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
148
|
+
value = 42;
|
|
149
|
+
});
|
|
150
|
+
runner.it('test', () => {
|
|
151
|
+
if (value !== 42)
|
|
152
|
+
throw new Error('hook did not run');
|
|
153
|
+
});
|
|
154
|
+
const results = await runner.run();
|
|
155
|
+
expect(results.passed).toBe(1);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('afterEach()', () => {
|
|
159
|
+
it('runs after each test', async () => {
|
|
160
|
+
let count = 0;
|
|
161
|
+
runner.afterEach(() => {
|
|
162
|
+
count++;
|
|
163
|
+
});
|
|
164
|
+
runner.it('test1', () => { });
|
|
165
|
+
runner.it('test2', () => { });
|
|
166
|
+
await runner.run();
|
|
167
|
+
expect(count).toBe(2);
|
|
168
|
+
});
|
|
169
|
+
it('runs only for passing tests (current behavior)', async () => {
|
|
170
|
+
let count = 0;
|
|
171
|
+
runner.afterEach(() => {
|
|
172
|
+
count++;
|
|
173
|
+
});
|
|
174
|
+
runner.it('passing', () => { });
|
|
175
|
+
runner.it('failing', () => {
|
|
176
|
+
throw new Error('fail');
|
|
177
|
+
});
|
|
178
|
+
await runner.run();
|
|
179
|
+
// Currently afterEach only runs for passing tests
|
|
180
|
+
expect(count).toBe(1);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
describe('beforeAll()', () => {
|
|
184
|
+
it('runs once before all tests', async () => {
|
|
185
|
+
let count = 0;
|
|
186
|
+
runner.beforeAll(() => {
|
|
187
|
+
count++;
|
|
188
|
+
});
|
|
189
|
+
runner.it('test1', () => { });
|
|
190
|
+
runner.it('test2', () => { });
|
|
191
|
+
await runner.run();
|
|
192
|
+
expect(count).toBe(1);
|
|
193
|
+
});
|
|
194
|
+
it('fails all tests if beforeAll throws', async () => {
|
|
195
|
+
runner.beforeAll(() => {
|
|
196
|
+
throw new Error('setup failed');
|
|
197
|
+
});
|
|
198
|
+
runner.it('test1', () => { });
|
|
199
|
+
runner.it('test2', () => { });
|
|
200
|
+
const results = await runner.run();
|
|
201
|
+
expect(results.failed).toBe(2);
|
|
202
|
+
expect(results.tests[0].error).toContain('beforeAll hook failed');
|
|
203
|
+
expect(results.tests[1].error).toContain('beforeAll hook failed');
|
|
204
|
+
});
|
|
205
|
+
it('supports async hooks', async () => {
|
|
206
|
+
let value = 0;
|
|
207
|
+
runner.beforeAll(async () => {
|
|
208
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
209
|
+
value = 42;
|
|
210
|
+
});
|
|
211
|
+
runner.it('test', () => {
|
|
212
|
+
if (value !== 42)
|
|
213
|
+
throw new Error('hook did not run');
|
|
214
|
+
});
|
|
215
|
+
const results = await runner.run();
|
|
216
|
+
expect(results.passed).toBe(1);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
describe('afterAll()', () => {
|
|
220
|
+
it('runs once after all tests', async () => {
|
|
221
|
+
let count = 0;
|
|
222
|
+
runner.afterAll(() => {
|
|
223
|
+
count++;
|
|
224
|
+
});
|
|
225
|
+
runner.it('test1', () => { });
|
|
226
|
+
runner.it('test2', () => { });
|
|
227
|
+
await runner.run();
|
|
228
|
+
expect(count).toBe(1);
|
|
229
|
+
});
|
|
230
|
+
it('runs even if tests fail', async () => {
|
|
231
|
+
let ran = false;
|
|
232
|
+
runner.afterAll(() => {
|
|
233
|
+
ran = true;
|
|
234
|
+
});
|
|
235
|
+
runner.it('failing', () => {
|
|
236
|
+
throw new Error('fail');
|
|
237
|
+
});
|
|
238
|
+
await runner.run();
|
|
239
|
+
expect(ran).toBe(true);
|
|
240
|
+
});
|
|
241
|
+
it('does not fail tests if afterAll throws', async () => {
|
|
242
|
+
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
243
|
+
runner.it('test', () => { });
|
|
244
|
+
runner.afterAll(() => {
|
|
245
|
+
throw new Error('cleanup failed');
|
|
246
|
+
});
|
|
247
|
+
const results = await runner.run();
|
|
248
|
+
expect(results.passed).toBe(1);
|
|
249
|
+
expect(results.failed).toBe(0);
|
|
250
|
+
consoleSpy.mockRestore();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
describe('reset()', () => {
|
|
254
|
+
it('clears all tests', async () => {
|
|
255
|
+
runner.it('test', () => { });
|
|
256
|
+
runner.reset();
|
|
257
|
+
const results = await runner.run();
|
|
258
|
+
expect(results.total).toBe(0);
|
|
259
|
+
});
|
|
260
|
+
it('clears all hooks', async () => {
|
|
261
|
+
let count = 0;
|
|
262
|
+
runner.beforeEach(() => { count++; });
|
|
263
|
+
runner.afterEach(() => { count++; });
|
|
264
|
+
runner.beforeAll(() => { count++; });
|
|
265
|
+
runner.afterAll(() => { count++; });
|
|
266
|
+
runner.reset();
|
|
267
|
+
runner.it('test', () => { });
|
|
268
|
+
await runner.run();
|
|
269
|
+
expect(count).toBe(0);
|
|
270
|
+
});
|
|
271
|
+
it('clears current suite', async () => {
|
|
272
|
+
runner.describe('suite', () => {
|
|
273
|
+
runner.reset();
|
|
274
|
+
runner.it('test', () => { });
|
|
275
|
+
});
|
|
276
|
+
const results = await runner.run();
|
|
277
|
+
expect(results.tests[0].name).toBe('test');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
describe('run()', () => {
|
|
281
|
+
it('returns correct totals', async () => {
|
|
282
|
+
runner.it('pass1', () => { });
|
|
283
|
+
runner.it('pass2', () => { });
|
|
284
|
+
runner.it('fail', () => { throw new Error(); });
|
|
285
|
+
runner.skip('skip', () => { });
|
|
286
|
+
const results = await runner.run();
|
|
287
|
+
expect(results.total).toBe(4);
|
|
288
|
+
expect(results.passed).toBe(2);
|
|
289
|
+
expect(results.failed).toBe(1);
|
|
290
|
+
expect(results.skipped).toBe(1);
|
|
291
|
+
});
|
|
292
|
+
it('tracks test durations', async () => {
|
|
293
|
+
runner.it('test', async () => {
|
|
294
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
295
|
+
});
|
|
296
|
+
const results = await runner.run();
|
|
297
|
+
expect(results.tests[0].duration).toBeGreaterThanOrEqual(10);
|
|
298
|
+
});
|
|
299
|
+
it('tracks total duration', async () => {
|
|
300
|
+
runner.it('test', async () => {
|
|
301
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
302
|
+
});
|
|
303
|
+
const results = await runner.run();
|
|
304
|
+
expect(results.duration).toBeGreaterThanOrEqual(10);
|
|
305
|
+
});
|
|
306
|
+
it('returns empty results for no tests', async () => {
|
|
307
|
+
const results = await runner.run();
|
|
308
|
+
expect(results.total).toBe(0);
|
|
309
|
+
expect(results.passed).toBe(0);
|
|
310
|
+
expect(results.failed).toBe(0);
|
|
311
|
+
expect(results.skipped).toBe(0);
|
|
312
|
+
expect(results.tests).toEqual([]);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type safety tests for assertions.ts
|
|
3
|
+
*
|
|
4
|
+
* These tests verify proper TypeScript type inference and type safety
|
|
5
|
+
* for the assertion functions, particularly around the areas that
|
|
6
|
+
* previously used `as any` casts.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expectTypeOf, expect as vitestExpect } from 'vitest'
|
|
9
|
+
import { expect, Assertion } from '../src/assertions.js'
|
|
10
|
+
|
|
11
|
+
describe('Type Safety: Assertion', () => {
|
|
12
|
+
describe('instanceof type handling', () => {
|
|
13
|
+
it('accepts constructor functions', () => {
|
|
14
|
+
// Should accept class constructors
|
|
15
|
+
class MyClass {}
|
|
16
|
+
const assertion = expect(new MyClass()).instanceof(MyClass)
|
|
17
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('accepts built-in constructors', () => {
|
|
21
|
+
// Should accept Error constructor
|
|
22
|
+
const assertion = expect(new Error()).instanceof(Error)
|
|
23
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('accepts ErrorConstructor types', () => {
|
|
27
|
+
// Should work with various error types
|
|
28
|
+
const assertion1 = expect(new TypeError()).instanceof(TypeError)
|
|
29
|
+
const assertion2 = expect(new RangeError()).instanceof(RangeError)
|
|
30
|
+
expectTypeOf(assertion1).toEqualTypeOf<Assertion>()
|
|
31
|
+
expectTypeOf(assertion2).toEqualTypeOf<Assertion>()
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('instanceOf type handling (alias)', () => {
|
|
36
|
+
it('accepts constructor functions', () => {
|
|
37
|
+
class MyClass {}
|
|
38
|
+
const assertion = expect(new MyClass()).instanceOf(MyClass)
|
|
39
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('toBeInstanceOf type handling', () => {
|
|
44
|
+
it('accepts constructor functions', () => {
|
|
45
|
+
class MyClass {}
|
|
46
|
+
const assertion = expect(new MyClass()).toBeInstanceOf(MyClass)
|
|
47
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('accepts built-in constructors', () => {
|
|
51
|
+
const assertion = expect(new Error()).toBeInstanceOf(Error)
|
|
52
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('throw type handling', () => {
|
|
57
|
+
it('accepts no arguments', () => {
|
|
58
|
+
const assertion = expect(() => { throw new Error() }).throw()
|
|
59
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('accepts string error message', () => {
|
|
63
|
+
const assertion = expect(() => { throw new Error('test') }).throw('test')
|
|
64
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('accepts RegExp pattern', () => {
|
|
68
|
+
const assertion = expect(() => { throw new Error('test') }).throw(/test/)
|
|
69
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('accepts Error instance', () => {
|
|
73
|
+
const err = new Error('test')
|
|
74
|
+
const assertion = expect(() => { throw err }).throw(err)
|
|
75
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('accepts Error constructor', () => {
|
|
79
|
+
const assertion = expect(() => { throw new TypeError() }).throw(TypeError)
|
|
80
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('accepts Error constructor with message pattern', () => {
|
|
84
|
+
const assertion = expect(() => { throw new Error('test') }).throw(Error, /test/)
|
|
85
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('throws type handling (alias)', () => {
|
|
90
|
+
it('accepts Error constructor', () => {
|
|
91
|
+
const assertion = expect(() => { throw new TypeError() }).throws(TypeError)
|
|
92
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
describe('Throw type handling (alias)', () => {
|
|
97
|
+
it('accepts Error constructor', () => {
|
|
98
|
+
const assertion = expect(() => { throw new TypeError() }).Throw(TypeError)
|
|
99
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
describe('toThrow type handling', () => {
|
|
104
|
+
it('accepts no arguments', () => {
|
|
105
|
+
const assertion = expect(() => { throw new Error() }).toThrow()
|
|
106
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('accepts string error message', () => {
|
|
110
|
+
const assertion = expect(() => { throw new Error('test') }).toThrow('test')
|
|
111
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('accepts RegExp pattern', () => {
|
|
115
|
+
const assertion = expect(() => { throw new Error('test') }).toThrow(/test/)
|
|
116
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('accepts Error instance', () => {
|
|
120
|
+
const err = new Error('test')
|
|
121
|
+
const assertion = expect(() => { throw err }).toThrow(err)
|
|
122
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('Assertion class internal type safety', () => {
|
|
127
|
+
it('returns correct Assertion type from expect', () => {
|
|
128
|
+
const assertion = expect(42)
|
|
129
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('chains return Assertion type', () => {
|
|
133
|
+
const assertion = expect(42).to.be.a('number')
|
|
134
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('negation returns Assertion type', () => {
|
|
138
|
+
const assertion = expect(42).not
|
|
139
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('deep flag returns Assertion type', () => {
|
|
143
|
+
const assertion = expect({ a: 1 }).deep
|
|
144
|
+
expectTypeOf(assertion).toEqualTypeOf<Assertion>()
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
describe('Runtime Type Safety', () => {
|
|
150
|
+
describe('instanceof runtime behavior', () => {
|
|
151
|
+
it('works with custom classes at runtime', () => {
|
|
152
|
+
class CustomError extends Error {
|
|
153
|
+
constructor(message: string) {
|
|
154
|
+
super(message)
|
|
155
|
+
this.name = 'CustomError'
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
vitestExpect(() => expect(new CustomError('test')).instanceof(CustomError)).not.toThrow()
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('works with inheritance chains', () => {
|
|
162
|
+
vitestExpect(() => expect(new TypeError()).instanceof(Error)).not.toThrow()
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
describe('throw runtime behavior with typed constructors', () => {
|
|
167
|
+
it('works with Error constructor', () => {
|
|
168
|
+
vitestExpect(() =>
|
|
169
|
+
expect(() => { throw new Error('test') }).throw(Error)
|
|
170
|
+
).not.toThrow()
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('works with specific error types', () => {
|
|
174
|
+
vitestExpect(() =>
|
|
175
|
+
expect(() => { throw new TypeError('test') }).throw(TypeError)
|
|
176
|
+
).not.toThrow()
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('works with Error constructor and message', () => {
|
|
180
|
+
vitestExpect(() =>
|
|
181
|
+
expect(() => { throw new Error('test message') }).throw(Error, /test/)
|
|
182
|
+
).not.toThrow()
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe('toBeInstanceOf runtime behavior', () => {
|
|
187
|
+
it('works with built-in types', () => {
|
|
188
|
+
vitestExpect(() => expect([]).toBeInstanceOf(Array)).not.toThrow()
|
|
189
|
+
vitestExpect(() => expect(new Map()).toBeInstanceOf(Map)).not.toThrow()
|
|
190
|
+
vitestExpect(() => expect(new Set()).toBeInstanceOf(Set)).not.toThrow()
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
describe('toThrow runtime behavior', () => {
|
|
195
|
+
it('works with Error type', () => {
|
|
196
|
+
vitestExpect(() =>
|
|
197
|
+
expect(() => { throw new Error('test') }).toThrow(Error)
|
|
198
|
+
).not.toThrow()
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
})
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { TestServiceCore, TestService } from '../src/worker.js';
|
|
3
|
+
import { Assertion } from '../src/assertions.js';
|
|
4
|
+
import { TestRunner } from '../src/runner.js';
|
|
5
|
+
describe('TestServiceCore', () => {
|
|
6
|
+
let service;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
service = new TestServiceCore();
|
|
9
|
+
});
|
|
10
|
+
describe('constructor', () => {
|
|
11
|
+
it('creates a new TestServiceCore instance', () => {
|
|
12
|
+
expect(service).toBeInstanceOf(TestServiceCore);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe('expect()', () => {
|
|
16
|
+
it('returns an Assertion', () => {
|
|
17
|
+
const assertion = service.expect(42);
|
|
18
|
+
expect(assertion).toBeInstanceOf(Assertion);
|
|
19
|
+
});
|
|
20
|
+
it('accepts optional message', () => {
|
|
21
|
+
const assertion = service.expect(42, 'custom message');
|
|
22
|
+
expect(assertion).toBeInstanceOf(Assertion);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
describe('should()', () => {
|
|
26
|
+
it('returns an Assertion', () => {
|
|
27
|
+
const assertion = service.should(42);
|
|
28
|
+
expect(assertion).toBeInstanceOf(Assertion);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe('assert', () => {
|
|
32
|
+
it('returns chai assert object', () => {
|
|
33
|
+
expect(service.assert).toBeDefined();
|
|
34
|
+
expect(typeof service.assert.equal).toBe('function');
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('describe()', () => {
|
|
38
|
+
it('delegates to runner', async () => {
|
|
39
|
+
service.describe('suite', () => {
|
|
40
|
+
service.it('test', () => { });
|
|
41
|
+
});
|
|
42
|
+
const results = await service.run();
|
|
43
|
+
expect(results.tests[0].name).toBe('suite > test');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('it()', () => {
|
|
47
|
+
it('delegates to runner', async () => {
|
|
48
|
+
service.it('test', () => { });
|
|
49
|
+
const results = await service.run();
|
|
50
|
+
expect(results.total).toBe(1);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe('test()', () => {
|
|
54
|
+
it('is alias for it', async () => {
|
|
55
|
+
service.test('test', () => { });
|
|
56
|
+
const results = await service.run();
|
|
57
|
+
expect(results.total).toBe(1);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('skip()', () => {
|
|
61
|
+
it('delegates to runner', async () => {
|
|
62
|
+
service.skip('skipped', () => { });
|
|
63
|
+
const results = await service.run();
|
|
64
|
+
expect(results.skipped).toBe(1);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('only()', () => {
|
|
68
|
+
it('delegates to runner', async () => {
|
|
69
|
+
service.it('normal', () => { });
|
|
70
|
+
service.only('only', () => { });
|
|
71
|
+
const results = await service.run();
|
|
72
|
+
expect(results.total).toBe(1);
|
|
73
|
+
expect(results.tests[0].name).toBe('only');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('beforeEach()', () => {
|
|
77
|
+
it('delegates to runner', async () => {
|
|
78
|
+
let count = 0;
|
|
79
|
+
service.beforeEach(() => { count++; });
|
|
80
|
+
service.it('test1', () => { });
|
|
81
|
+
service.it('test2', () => { });
|
|
82
|
+
await service.run();
|
|
83
|
+
expect(count).toBe(2);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('afterEach()', () => {
|
|
87
|
+
it('delegates to runner', async () => {
|
|
88
|
+
let count = 0;
|
|
89
|
+
service.afterEach(() => { count++; });
|
|
90
|
+
service.it('test1', () => { });
|
|
91
|
+
service.it('test2', () => { });
|
|
92
|
+
await service.run();
|
|
93
|
+
expect(count).toBe(2);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
describe('beforeAll()', () => {
|
|
97
|
+
it('delegates to runner', async () => {
|
|
98
|
+
let count = 0;
|
|
99
|
+
service.beforeAll(() => { count++; });
|
|
100
|
+
service.it('test1', () => { });
|
|
101
|
+
service.it('test2', () => { });
|
|
102
|
+
await service.run();
|
|
103
|
+
expect(count).toBe(1);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('afterAll()', () => {
|
|
107
|
+
it('delegates to runner', async () => {
|
|
108
|
+
let count = 0;
|
|
109
|
+
service.afterAll(() => { count++; });
|
|
110
|
+
service.it('test1', () => { });
|
|
111
|
+
service.it('test2', () => { });
|
|
112
|
+
await service.run();
|
|
113
|
+
expect(count).toBe(1);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('run()', () => {
|
|
117
|
+
it('returns test results', async () => {
|
|
118
|
+
service.it('pass', () => { });
|
|
119
|
+
service.it('fail', () => { throw new Error('fail'); });
|
|
120
|
+
const results = await service.run();
|
|
121
|
+
expect(results.total).toBe(2);
|
|
122
|
+
expect(results.passed).toBe(1);
|
|
123
|
+
expect(results.failed).toBe(1);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('reset()', () => {
|
|
127
|
+
it('clears all tests and hooks', async () => {
|
|
128
|
+
service.it('test', () => { });
|
|
129
|
+
service.beforeEach(() => { });
|
|
130
|
+
service.reset();
|
|
131
|
+
const results = await service.run();
|
|
132
|
+
expect(results.total).toBe(0);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('createRunner()', () => {
|
|
136
|
+
it('returns a new TestRunner', () => {
|
|
137
|
+
const runner = service.createRunner();
|
|
138
|
+
expect(runner).toBeInstanceOf(TestRunner);
|
|
139
|
+
});
|
|
140
|
+
it('returns independent runner', async () => {
|
|
141
|
+
service.it('service test', () => { });
|
|
142
|
+
const runner = service.createRunner();
|
|
143
|
+
runner.it('runner test', () => { });
|
|
144
|
+
const serviceResults = await service.run();
|
|
145
|
+
const runnerResults = await runner.run();
|
|
146
|
+
expect(serviceResults.total).toBe(1);
|
|
147
|
+
expect(runnerResults.total).toBe(1);
|
|
148
|
+
expect(serviceResults.tests[0].name).toBe('service test');
|
|
149
|
+
expect(runnerResults.tests[0].name).toBe('runner test');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe('TestService (WorkerEntrypoint)', () => {
|
|
154
|
+
it('exports TestService class', async () => {
|
|
155
|
+
const { default: TestServiceClass } = await import('../src/worker.js');
|
|
156
|
+
expect(TestServiceClass).toBeDefined();
|
|
157
|
+
expect(typeof TestServiceClass).toBe('function');
|
|
158
|
+
});
|
|
159
|
+
it('TestService has connect method in prototype', () => {
|
|
160
|
+
expect(typeof TestService.prototype.connect).toBe('function');
|
|
161
|
+
});
|
|
162
|
+
});
|