ai-tests 2.0.2 → 2.1.1
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 +4 -0
- package/CHANGELOG.md +9 -0
- package/package.json +2 -3
- package/src/assertions.js +383 -0
- 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/worker.test.js +162 -0
- package/vitest.config.js +10 -0
package/src/runner.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test runner - describe, it, test, hooks
|
|
3
|
+
*
|
|
4
|
+
* Provides vitest-compatible test runner API via RPC.
|
|
5
|
+
*/
|
|
6
|
+
import { RpcTarget } from 'cloudflare:workers';
|
|
7
|
+
/**
|
|
8
|
+
* Test runner that collects and runs tests
|
|
9
|
+
*/
|
|
10
|
+
export class TestRunner extends RpcTarget {
|
|
11
|
+
tests = [];
|
|
12
|
+
currentSuite = '';
|
|
13
|
+
beforeEachHooks = [];
|
|
14
|
+
afterEachHooks = [];
|
|
15
|
+
beforeAllHooks = [];
|
|
16
|
+
afterAllHooks = [];
|
|
17
|
+
/**
|
|
18
|
+
* Define a test suite
|
|
19
|
+
*/
|
|
20
|
+
describe(name, fn) {
|
|
21
|
+
const prevSuite = this.currentSuite;
|
|
22
|
+
const prevBeforeEach = [...this.beforeEachHooks];
|
|
23
|
+
const prevAfterEach = [...this.afterEachHooks];
|
|
24
|
+
this.currentSuite = this.currentSuite ? `${this.currentSuite} > ${name}` : name;
|
|
25
|
+
try {
|
|
26
|
+
fn();
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
this.currentSuite = prevSuite;
|
|
30
|
+
this.beforeEachHooks = prevBeforeEach;
|
|
31
|
+
this.afterEachHooks = prevAfterEach;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Define a test
|
|
36
|
+
*/
|
|
37
|
+
it(name, fn) {
|
|
38
|
+
const fullName = this.currentSuite ? `${this.currentSuite} > ${name}` : name;
|
|
39
|
+
this.tests.push({
|
|
40
|
+
name: fullName,
|
|
41
|
+
fn,
|
|
42
|
+
hooks: {
|
|
43
|
+
before: [...this.beforeEachHooks],
|
|
44
|
+
after: [...this.afterEachHooks]
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Alias for it
|
|
50
|
+
*/
|
|
51
|
+
test(name, fn) {
|
|
52
|
+
this.it(name, fn);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Skip a test
|
|
56
|
+
*/
|
|
57
|
+
skip(name, _fn) {
|
|
58
|
+
const fullName = this.currentSuite ? `${this.currentSuite} > ${name}` : name;
|
|
59
|
+
this.tests.push({
|
|
60
|
+
name: fullName,
|
|
61
|
+
fn: null,
|
|
62
|
+
hooks: { before: [], after: [] },
|
|
63
|
+
skip: true
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Run only this test
|
|
68
|
+
*/
|
|
69
|
+
only(name, fn) {
|
|
70
|
+
const fullName = this.currentSuite ? `${this.currentSuite} > ${name}` : name;
|
|
71
|
+
this.tests.push({
|
|
72
|
+
name: fullName,
|
|
73
|
+
fn,
|
|
74
|
+
hooks: {
|
|
75
|
+
before: [...this.beforeEachHooks],
|
|
76
|
+
after: [...this.afterEachHooks]
|
|
77
|
+
},
|
|
78
|
+
only: true
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Register a beforeEach hook
|
|
83
|
+
*/
|
|
84
|
+
beforeEach(fn) {
|
|
85
|
+
this.beforeEachHooks.push(fn);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Register an afterEach hook
|
|
89
|
+
*/
|
|
90
|
+
afterEach(fn) {
|
|
91
|
+
this.afterEachHooks.push(fn);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Register a beforeAll hook
|
|
95
|
+
*/
|
|
96
|
+
beforeAll(fn) {
|
|
97
|
+
this.beforeAllHooks.push(fn);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Register an afterAll hook
|
|
101
|
+
*/
|
|
102
|
+
afterAll(fn) {
|
|
103
|
+
this.afterAllHooks.push(fn);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Run all registered tests and return results
|
|
107
|
+
*/
|
|
108
|
+
async run() {
|
|
109
|
+
const startTime = Date.now();
|
|
110
|
+
const results = [];
|
|
111
|
+
// Check for .only tests
|
|
112
|
+
const hasOnly = this.tests.some(t => t.only);
|
|
113
|
+
const testsToRun = hasOnly
|
|
114
|
+
? this.tests.filter(t => t.only || t.skip)
|
|
115
|
+
: this.tests;
|
|
116
|
+
// Run beforeAll hooks
|
|
117
|
+
for (const hook of this.beforeAllHooks) {
|
|
118
|
+
try {
|
|
119
|
+
await hook();
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
// If beforeAll fails, fail all tests
|
|
123
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
124
|
+
for (const test of testsToRun) {
|
|
125
|
+
results.push({
|
|
126
|
+
name: test.name,
|
|
127
|
+
passed: false,
|
|
128
|
+
error: `beforeAll hook failed: ${error}`,
|
|
129
|
+
duration: 0
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return this.buildResults(results, startTime);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Run each test
|
|
136
|
+
for (const test of testsToRun) {
|
|
137
|
+
if (test.skip) {
|
|
138
|
+
results.push({
|
|
139
|
+
name: test.name,
|
|
140
|
+
passed: true,
|
|
141
|
+
skipped: true,
|
|
142
|
+
duration: 0
|
|
143
|
+
});
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const testStart = Date.now();
|
|
147
|
+
try {
|
|
148
|
+
// Run beforeEach hooks
|
|
149
|
+
for (const hook of test.hooks.before) {
|
|
150
|
+
await hook();
|
|
151
|
+
}
|
|
152
|
+
// Run the test
|
|
153
|
+
if (test.fn) {
|
|
154
|
+
await test.fn();
|
|
155
|
+
}
|
|
156
|
+
// Run afterEach hooks
|
|
157
|
+
for (const hook of test.hooks.after) {
|
|
158
|
+
await hook();
|
|
159
|
+
}
|
|
160
|
+
results.push({
|
|
161
|
+
name: test.name,
|
|
162
|
+
passed: true,
|
|
163
|
+
duration: Date.now() - testStart
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
results.push({
|
|
168
|
+
name: test.name,
|
|
169
|
+
passed: false,
|
|
170
|
+
error: e instanceof Error ? e.message : String(e),
|
|
171
|
+
duration: Date.now() - testStart
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Run afterAll hooks
|
|
176
|
+
for (const hook of this.afterAllHooks) {
|
|
177
|
+
try {
|
|
178
|
+
await hook();
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
// Log but don't fail tests
|
|
182
|
+
console.error('afterAll hook failed:', e);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return this.buildResults(results, startTime);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Clear all registered tests and hooks
|
|
189
|
+
*/
|
|
190
|
+
reset() {
|
|
191
|
+
this.tests = [];
|
|
192
|
+
this.currentSuite = '';
|
|
193
|
+
this.beforeEachHooks = [];
|
|
194
|
+
this.afterEachHooks = [];
|
|
195
|
+
this.beforeAllHooks = [];
|
|
196
|
+
this.afterAllHooks = [];
|
|
197
|
+
}
|
|
198
|
+
buildResults(results, startTime) {
|
|
199
|
+
return {
|
|
200
|
+
total: results.length,
|
|
201
|
+
passed: results.filter(r => r.passed && !r.skipped).length,
|
|
202
|
+
failed: results.filter(r => !r.passed).length,
|
|
203
|
+
skipped: results.filter(r => r.skipped).length,
|
|
204
|
+
tests: results,
|
|
205
|
+
duration: Date.now() - startTime
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Create a new test runner instance
|
|
211
|
+
*/
|
|
212
|
+
export function createRunner() {
|
|
213
|
+
return new TestRunner();
|
|
214
|
+
}
|
package/src/types.js
ADDED
package/src/worker.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Worker - provides test utilities via RPC
|
|
3
|
+
*
|
|
4
|
+
* This worker can be deployed to Cloudflare Workers or run locally via Miniflare.
|
|
5
|
+
* It exposes expect, should, assert, and a test runner via Workers RPC.
|
|
6
|
+
*
|
|
7
|
+
* Uses Cloudflare Workers RPC (WorkerEntrypoint, RpcTarget) for communication.
|
|
8
|
+
*/
|
|
9
|
+
import { WorkerEntrypoint, RpcTarget } from 'cloudflare:workers';
|
|
10
|
+
import { expect, should, assert } from './assertions.js';
|
|
11
|
+
import { createRunner } from './runner.js';
|
|
12
|
+
/**
|
|
13
|
+
* Core test service - extends RpcTarget so it can be passed over RPC
|
|
14
|
+
*
|
|
15
|
+
* Contains all test functionality: assertions (expect, should, assert)
|
|
16
|
+
* and test runner (describe, it, test, hooks)
|
|
17
|
+
*/
|
|
18
|
+
export class TestServiceCore extends RpcTarget {
|
|
19
|
+
runner;
|
|
20
|
+
constructor() {
|
|
21
|
+
super();
|
|
22
|
+
this.runner = createRunner();
|
|
23
|
+
}
|
|
24
|
+
expect(value, message) {
|
|
25
|
+
return expect(value, message);
|
|
26
|
+
}
|
|
27
|
+
should(value) {
|
|
28
|
+
return should(value);
|
|
29
|
+
}
|
|
30
|
+
get assert() {
|
|
31
|
+
return assert;
|
|
32
|
+
}
|
|
33
|
+
describe(name, fn) {
|
|
34
|
+
this.runner.describe(name, fn);
|
|
35
|
+
}
|
|
36
|
+
it(name, fn) {
|
|
37
|
+
this.runner.it(name, fn);
|
|
38
|
+
}
|
|
39
|
+
test(name, fn) {
|
|
40
|
+
this.runner.test(name, fn);
|
|
41
|
+
}
|
|
42
|
+
skip(name, fn) {
|
|
43
|
+
this.runner.skip(name, fn);
|
|
44
|
+
}
|
|
45
|
+
only(name, fn) {
|
|
46
|
+
this.runner.only(name, fn);
|
|
47
|
+
}
|
|
48
|
+
beforeEach(fn) {
|
|
49
|
+
this.runner.beforeEach(fn);
|
|
50
|
+
}
|
|
51
|
+
afterEach(fn) {
|
|
52
|
+
this.runner.afterEach(fn);
|
|
53
|
+
}
|
|
54
|
+
beforeAll(fn) {
|
|
55
|
+
this.runner.beforeAll(fn);
|
|
56
|
+
}
|
|
57
|
+
afterAll(fn) {
|
|
58
|
+
this.runner.afterAll(fn);
|
|
59
|
+
}
|
|
60
|
+
async run() {
|
|
61
|
+
return this.runner.run();
|
|
62
|
+
}
|
|
63
|
+
reset() {
|
|
64
|
+
this.runner.reset();
|
|
65
|
+
}
|
|
66
|
+
createRunner() {
|
|
67
|
+
return createRunner();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Main test service exposed via RPC as WorkerEntrypoint
|
|
72
|
+
*
|
|
73
|
+
* Usage:
|
|
74
|
+
* const tests = await env.TEST.connect()
|
|
75
|
+
* tests.expect(1).to.equal(1)
|
|
76
|
+
* tests.describe('suite', () => { ... })
|
|
77
|
+
* const results = await tests.run()
|
|
78
|
+
*/
|
|
79
|
+
export class TestService extends WorkerEntrypoint {
|
|
80
|
+
/**
|
|
81
|
+
* Get a test service instance - returns an RpcTarget that can be used directly
|
|
82
|
+
* This avoids boilerplate delegation and allows using `test` method name
|
|
83
|
+
*/
|
|
84
|
+
connect() {
|
|
85
|
+
return new TestServiceCore();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Export as default for WorkerEntrypoint pattern
|
|
89
|
+
export default TestService;
|
|
90
|
+
// Export aliases
|
|
91
|
+
export { TestService as TestWorker };
|