ai-evaluate 2.1.6 → 2.1.8
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/README.md +90 -3
- package/dist/capnweb-bundle.d.ts +10 -0
- package/dist/capnweb-bundle.d.ts.map +1 -0
- package/dist/capnweb-bundle.js +2596 -0
- package/dist/capnweb-bundle.js.map +1 -0
- package/dist/evaluate.d.ts +1 -1
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js +186 -7
- package/dist/evaluate.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/miniflare-pool.d.ts +109 -0
- package/dist/miniflare-pool.d.ts.map +1 -0
- package/dist/miniflare-pool.js +308 -0
- package/dist/miniflare-pool.js.map +1 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +42 -10
- package/dist/node.js.map +1 -1
- package/dist/shared.d.ts +66 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +169 -0
- package/dist/shared.js.map +1 -0
- package/dist/type-guards.d.ts +21 -0
- package/dist/type-guards.d.ts.map +1 -0
- package/dist/type-guards.js +216 -0
- package/dist/type-guards.js.map +1 -0
- package/dist/types.d.ts +17 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts +26 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +104 -0
- package/dist/validation.js.map +1 -0
- package/dist/worker-template/code-transforms.d.ts +9 -0
- package/dist/worker-template/code-transforms.d.ts.map +1 -0
- package/dist/worker-template/code-transforms.js +28 -0
- package/dist/worker-template/code-transforms.js.map +1 -0
- package/{src/worker-template.d.ts → dist/worker-template/core.d.ts} +7 -15
- package/dist/worker-template/core.d.ts.map +1 -0
- package/dist/worker-template/core.js +502 -0
- package/dist/worker-template/core.js.map +1 -0
- package/dist/worker-template/helpers.d.ts +14 -0
- package/dist/worker-template/helpers.d.ts.map +1 -0
- package/dist/worker-template/helpers.js +79 -0
- package/dist/worker-template/helpers.js.map +1 -0
- package/dist/worker-template/index.d.ts +14 -0
- package/dist/worker-template/index.d.ts.map +1 -0
- package/dist/worker-template/index.js +19 -0
- package/dist/worker-template/index.js.map +1 -0
- package/dist/worker-template/sdk-generator.d.ts +17 -0
- package/dist/worker-template/sdk-generator.d.ts.map +1 -0
- package/{src/worker-template.js → dist/worker-template/sdk-generator.js} +377 -1506
- package/dist/worker-template/sdk-generator.js.map +1 -0
- package/dist/worker-template/test-generator.d.ts +16 -0
- package/dist/worker-template/test-generator.d.ts.map +1 -0
- package/dist/worker-template/test-generator.js +357 -0
- package/dist/worker-template/test-generator.js.map +1 -0
- package/dist/worker-template.d.ts +2 -2
- package/dist/worker-template.d.ts.map +1 -1
- package/dist/worker-template.js +64 -31
- package/dist/worker-template.js.map +1 -1
- package/example/package.json +7 -3
- package/example/src/index.ts +194 -40
- package/example/wrangler.jsonc +18 -2
- package/package.json +1 -3
- package/src/capnweb-bundle.ts +2596 -0
- package/src/evaluate.ts +216 -7
- package/src/index.ts +3 -1
- package/src/miniflare-pool.ts +395 -0
- package/src/node.ts +56 -11
- package/src/shared.ts +186 -0
- package/src/type-guards.ts +323 -0
- package/src/types.ts +18 -2
- package/src/validation.ts +120 -0
- package/src/worker-template/code-transforms.ts +32 -0
- package/src/worker-template/core.ts +557 -0
- package/src/worker-template/helpers.ts +90 -0
- package/src/worker-template/index.ts +23 -0
- package/src/{worker-template.ts → worker-template/sdk-generator.ts} +322 -1566
- package/src/worker-template/test-generator.ts +358 -0
- package/test/miniflare-pool.test.ts +246 -0
- package/test/node.test.ts +467 -0
- package/test/security.test.ts +1009 -0
- package/test/shared.test.ts +105 -0
- package/test/type-guards.test.ts +303 -0
- package/test/validation.test.ts +240 -0
- package/test/worker-template.test.ts +21 -19
- package/src/evaluate.js +0 -187
- package/src/index.js +0 -10
- package/src/node.d.ts +0 -17
- package/src/node.d.ts.map +0 -1
- package/src/node.js +0 -168
- package/src/node.js.map +0 -1
- package/src/types.d.ts +0 -172
- package/src/types.d.ts.map +0 -1
- package/src/types.js +0 -4
- package/src/types.js.map +0 -1
- package/src/worker-template.d.ts.map +0 -1
- package/src/worker-template.js.map +0 -1
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
describe('ai-evaluate/node', () => {
|
|
4
|
+
describe('JSX transformation', () => {
|
|
5
|
+
// Note: JSX transformation uses esbuild which may produce ESM-wrapped code
|
|
6
|
+
// These tests verify the JSX detection and transformation attempt
|
|
7
|
+
|
|
8
|
+
it('detects and attempts to transform simple JSX', async () => {
|
|
9
|
+
const { evaluate } = await import('../src/node.js')
|
|
10
|
+
|
|
11
|
+
// JSX code that would need transformation
|
|
12
|
+
const result = await evaluate({
|
|
13
|
+
module: `
|
|
14
|
+
function h(tag, props, ...children) {
|
|
15
|
+
return { tag, props, children }
|
|
16
|
+
}
|
|
17
|
+
exports.render = () => <div>Hello</div>
|
|
18
|
+
`,
|
|
19
|
+
script: 'return render()',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// The transformation is attempted - result depends on esbuild output format
|
|
23
|
+
// Either it succeeds or returns an error (not a crash)
|
|
24
|
+
expect(typeof result.success).toBe('boolean')
|
|
25
|
+
expect(Array.isArray(result.logs)).toBe(true)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('detects JSX with props', async () => {
|
|
29
|
+
const { evaluate } = await import('../src/node.js')
|
|
30
|
+
|
|
31
|
+
const result = await evaluate({
|
|
32
|
+
module: `
|
|
33
|
+
function h(tag, props, ...children) {
|
|
34
|
+
return { tag, props, children }
|
|
35
|
+
}
|
|
36
|
+
const handler = () => 'clicked'
|
|
37
|
+
exports.render = () => <Button onClick={handler}>Click</Button>
|
|
38
|
+
`,
|
|
39
|
+
script: 'return render()',
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Verify the function doesn't crash and returns a valid result shape
|
|
43
|
+
expect(typeof result.success).toBe('boolean')
|
|
44
|
+
expect(typeof result.duration).toBe('number')
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('detects JSX fragments', async () => {
|
|
48
|
+
const { evaluate } = await import('../src/node.js')
|
|
49
|
+
|
|
50
|
+
const result = await evaluate({
|
|
51
|
+
module: `
|
|
52
|
+
function h(tag, props, ...children) {
|
|
53
|
+
return { tag, props, children }
|
|
54
|
+
}
|
|
55
|
+
function Fragment(props) {
|
|
56
|
+
return props.children
|
|
57
|
+
}
|
|
58
|
+
exports.render = () => <><span/><span/></>
|
|
59
|
+
`,
|
|
60
|
+
script: 'return render()',
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Verify graceful handling
|
|
64
|
+
expect(typeof result.success).toBe('boolean')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('handles JSX transform failure gracefully', async () => {
|
|
68
|
+
const { evaluate } = await import('../src/node.js')
|
|
69
|
+
|
|
70
|
+
// Even with JSX that fails to transform correctly, evaluate should not throw
|
|
71
|
+
const result = await evaluate({
|
|
72
|
+
module: `
|
|
73
|
+
function h(tag, props, ...children) {
|
|
74
|
+
return { tag, props, children }
|
|
75
|
+
}
|
|
76
|
+
exports.element = <div>Test</div>
|
|
77
|
+
`,
|
|
78
|
+
script: 'return element',
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Should return a result (success or error), not throw
|
|
82
|
+
expect(result).toHaveProperty('success')
|
|
83
|
+
expect(result).toHaveProperty('logs')
|
|
84
|
+
expect(result).toHaveProperty('duration')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('passes through non-JSX code unchanged', async () => {
|
|
88
|
+
const { evaluate } = await import('../src/node.js')
|
|
89
|
+
|
|
90
|
+
const result = await evaluate({
|
|
91
|
+
module: `
|
|
92
|
+
exports.add = (a, b) => a + b
|
|
93
|
+
`,
|
|
94
|
+
script: 'return add(2, 3)',
|
|
95
|
+
})
|
|
96
|
+
expect(result.success).toBe(true)
|
|
97
|
+
expect(result.value).toBe(5)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('handles code that looks like JSX but is a string', async () => {
|
|
101
|
+
const { evaluate } = await import('../src/node.js')
|
|
102
|
+
|
|
103
|
+
// String literals with angle brackets may still be detected by the regex
|
|
104
|
+
// but the transformation should still produce valid code
|
|
105
|
+
const result = await evaluate({
|
|
106
|
+
module: `
|
|
107
|
+
exports.html = "Not JSX"
|
|
108
|
+
`,
|
|
109
|
+
script: 'return html',
|
|
110
|
+
})
|
|
111
|
+
expect(result.success).toBe(true)
|
|
112
|
+
expect(result.value).toBe('Not JSX')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('handles empty module gracefully', async () => {
|
|
116
|
+
const { evaluate } = await import('../src/node.js')
|
|
117
|
+
|
|
118
|
+
const result = await evaluate({
|
|
119
|
+
script: 'return 123',
|
|
120
|
+
})
|
|
121
|
+
expect(result.success).toBe(true)
|
|
122
|
+
expect(result.value).toBe(123)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('Miniflare-specific behavior', () => {
|
|
127
|
+
it('evaluates without env binding (uses Miniflare)', async () => {
|
|
128
|
+
const { evaluate } = await import('../src/node.js')
|
|
129
|
+
|
|
130
|
+
// No env passed - should use Miniflare fallback
|
|
131
|
+
const result = await evaluate({
|
|
132
|
+
script: 'return 42',
|
|
133
|
+
})
|
|
134
|
+
expect(result.success).toBe(true)
|
|
135
|
+
expect(result.value).toBe(42)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('handles timeout with AbortController', async () => {
|
|
139
|
+
const { evaluate } = await import('../src/node.js')
|
|
140
|
+
|
|
141
|
+
const result = await evaluate({
|
|
142
|
+
script: `
|
|
143
|
+
// Infinite loop that should be aborted
|
|
144
|
+
while(true) {}
|
|
145
|
+
return 'never'
|
|
146
|
+
`,
|
|
147
|
+
timeout: 100,
|
|
148
|
+
})
|
|
149
|
+
expect(result.success).toBe(false)
|
|
150
|
+
expect(result.error).toContain('Timeout')
|
|
151
|
+
}, 10000)
|
|
152
|
+
|
|
153
|
+
it('blocks network via outboundService when fetch: null', async () => {
|
|
154
|
+
const { evaluate } = await import('../src/node.js')
|
|
155
|
+
|
|
156
|
+
const result = await evaluate({
|
|
157
|
+
script: `
|
|
158
|
+
try {
|
|
159
|
+
await fetch('https://example.com')
|
|
160
|
+
return 'fetch succeeded'
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return 'fetch blocked: ' + e.message
|
|
163
|
+
}
|
|
164
|
+
`,
|
|
165
|
+
fetch: null,
|
|
166
|
+
})
|
|
167
|
+
// Network blocking via outboundService may cause different errors
|
|
168
|
+
// The key is that fetch doesn't succeed
|
|
169
|
+
if (result.success) {
|
|
170
|
+
expect(result.value).toContain('fetch blocked')
|
|
171
|
+
} else {
|
|
172
|
+
// Network blocking might cause an error at the worker level
|
|
173
|
+
expect(result.error).toBeDefined()
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('allows network when fetch is not null', async () => {
|
|
178
|
+
const { evaluate } = await import('../src/node.js')
|
|
179
|
+
|
|
180
|
+
// This test verifies network is allowed by default
|
|
181
|
+
// We don't actually make a network call, just verify the option is respected
|
|
182
|
+
const result = await evaluate({
|
|
183
|
+
script: `
|
|
184
|
+
// Verify fetch exists and is callable
|
|
185
|
+
return typeof globalThis.fetch === 'function'
|
|
186
|
+
`,
|
|
187
|
+
})
|
|
188
|
+
expect(result.success).toBe(true)
|
|
189
|
+
expect(result.value).toBe(true)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('disposes Miniflare instance after execution', async () => {
|
|
193
|
+
const { evaluate } = await import('../src/node.js')
|
|
194
|
+
|
|
195
|
+
// Execute multiple evaluations to ensure proper cleanup
|
|
196
|
+
for (let i = 0; i < 3; i++) {
|
|
197
|
+
const result = await evaluate({
|
|
198
|
+
script: `return ${i}`,
|
|
199
|
+
})
|
|
200
|
+
expect(result.success).toBe(true)
|
|
201
|
+
expect(result.value).toBe(i)
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('returns duration in result', async () => {
|
|
206
|
+
const { evaluate } = await import('../src/node.js')
|
|
207
|
+
|
|
208
|
+
const result = await evaluate({
|
|
209
|
+
script: 'return true',
|
|
210
|
+
})
|
|
211
|
+
expect(result.success).toBe(true)
|
|
212
|
+
expect(typeof result.duration).toBe('number')
|
|
213
|
+
expect(result.duration).toBeGreaterThanOrEqual(0)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('captures console output in Miniflare', async () => {
|
|
217
|
+
const { evaluate } = await import('../src/node.js')
|
|
218
|
+
|
|
219
|
+
const result = await evaluate({
|
|
220
|
+
script: `
|
|
221
|
+
console.log('log message');
|
|
222
|
+
console.warn('warn message');
|
|
223
|
+
console.error('error message');
|
|
224
|
+
return 'done'
|
|
225
|
+
`,
|
|
226
|
+
})
|
|
227
|
+
expect(result.success).toBe(true)
|
|
228
|
+
expect(result.logs.length).toBeGreaterThanOrEqual(3)
|
|
229
|
+
expect(result.logs.some((l) => l.level === 'log' && l.message === 'log message')).toBe(true)
|
|
230
|
+
expect(result.logs.some((l) => l.level === 'warn' && l.message === 'warn message')).toBe(true)
|
|
231
|
+
expect(result.logs.some((l) => l.level === 'error' && l.message === 'error message')).toBe(
|
|
232
|
+
true
|
|
233
|
+
)
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
describe('esbuild optional dependency', () => {
|
|
238
|
+
it('esbuild is available in dev environment', async () => {
|
|
239
|
+
// esbuild should be available in dev
|
|
240
|
+
const esbuild = await import('esbuild').catch(() => null)
|
|
241
|
+
expect(esbuild).not.toBeNull()
|
|
242
|
+
expect(typeof esbuild?.transform).toBe('function')
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
it('graceful fallback when code has no JSX', async () => {
|
|
246
|
+
const { evaluate } = await import('../src/node.js')
|
|
247
|
+
|
|
248
|
+
// Code without JSX should work regardless of esbuild
|
|
249
|
+
const result = await evaluate({
|
|
250
|
+
module: `
|
|
251
|
+
exports.multiply = (a, b) => a * b
|
|
252
|
+
`,
|
|
253
|
+
script: 'return multiply(6, 7)',
|
|
254
|
+
})
|
|
255
|
+
expect(result.success).toBe(true)
|
|
256
|
+
expect(result.value).toBe(42)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
it('uses esbuild for JSX detection patterns', async () => {
|
|
260
|
+
// Test that JSX patterns are detected
|
|
261
|
+
// The containsJSX function should identify these patterns
|
|
262
|
+
const patterns = [
|
|
263
|
+
'<div>content</div>', // lowercase tag
|
|
264
|
+
'<Button />', // uppercase tag
|
|
265
|
+
'<>fragment</>', // fragment
|
|
266
|
+
'return <Component />', // return JSX
|
|
267
|
+
'return (\n<div>\n</div>\n)', // multiline return JSX
|
|
268
|
+
]
|
|
269
|
+
|
|
270
|
+
// All these should be detected as JSX
|
|
271
|
+
for (const pattern of patterns) {
|
|
272
|
+
const jsxPattern = /<[A-Z][a-zA-Z0-9]*[\s/>]|<[a-z][a-z0-9-]*[\s/>]|<>|<\/>/
|
|
273
|
+
const jsxReturnPattern = /return\s*\(\s*<|return\s+<[A-Za-z]/
|
|
274
|
+
const isJSX = jsxPattern.test(pattern) || jsxReturnPattern.test(pattern)
|
|
275
|
+
expect(isJSX).toBe(true)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('does not detect non-JSX patterns', async () => {
|
|
280
|
+
// These should NOT be detected as JSX
|
|
281
|
+
const patterns = [
|
|
282
|
+
'const x = a < b ? c : d', // comparison (no space after <)
|
|
283
|
+
'5 > 3', // comparison
|
|
284
|
+
'arr.map(x => x * 2)', // arrow function
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
for (const pattern of patterns) {
|
|
288
|
+
const jsxPattern = /<[A-Z][a-zA-Z0-9]*[\s/>]|<[a-z][a-z0-9-]*[\s/>]|<>|<\/>/
|
|
289
|
+
const jsxReturnPattern = /return\s*\(\s*<|return\s+<[A-Za-z]/
|
|
290
|
+
const isJSX = jsxPattern.test(pattern) || jsxReturnPattern.test(pattern)
|
|
291
|
+
expect(isJSX).toBe(false)
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('import from ai-evaluate/node', () => {
|
|
297
|
+
it('exports evaluate function', async () => {
|
|
298
|
+
const nodeModule = await import('../src/node.js')
|
|
299
|
+
expect(typeof nodeModule.evaluate).toBe('function')
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('exports createEvaluator function', async () => {
|
|
303
|
+
const nodeModule = await import('../src/node.js')
|
|
304
|
+
expect(typeof nodeModule.createEvaluator).toBe('function')
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it('createEvaluator returns working evaluator', async () => {
|
|
308
|
+
const { createEvaluator } = await import('../src/node.js')
|
|
309
|
+
|
|
310
|
+
const evaluator = createEvaluator()
|
|
311
|
+
expect(typeof evaluator).toBe('function')
|
|
312
|
+
|
|
313
|
+
const result = await evaluator({
|
|
314
|
+
script: 'return "hello from evaluator"',
|
|
315
|
+
})
|
|
316
|
+
expect(result.success).toBe(true)
|
|
317
|
+
expect(result.value).toBe('hello from evaluator')
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('evaluate function works standalone', async () => {
|
|
321
|
+
const { evaluate } = await import('../src/node.js')
|
|
322
|
+
|
|
323
|
+
const result = await evaluate({
|
|
324
|
+
module: `
|
|
325
|
+
exports.greet = (name) => 'Hello, ' + name
|
|
326
|
+
`,
|
|
327
|
+
script: 'return greet("World")',
|
|
328
|
+
})
|
|
329
|
+
expect(result.success).toBe(true)
|
|
330
|
+
expect(result.value).toBe('Hello, World')
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('re-exports types from types.js', async () => {
|
|
334
|
+
// Types are compile-time only, just verify module loads
|
|
335
|
+
const nodeModule = await import('../src/node.js')
|
|
336
|
+
expect(nodeModule).toBeDefined()
|
|
337
|
+
// Verify the module exports the expected functions
|
|
338
|
+
expect(Object.keys(nodeModule)).toContain('evaluate')
|
|
339
|
+
expect(Object.keys(nodeModule)).toContain('createEvaluator')
|
|
340
|
+
})
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
describe('error handling', () => {
|
|
344
|
+
it('handles evaluation errors gracefully', async () => {
|
|
345
|
+
const { evaluate } = await import('../src/node.js')
|
|
346
|
+
|
|
347
|
+
const result = await evaluate({
|
|
348
|
+
script: 'throw new Error("intentional error")',
|
|
349
|
+
})
|
|
350
|
+
expect(result.success).toBe(false)
|
|
351
|
+
expect(result.error).toContain('intentional error')
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('handles syntax errors in module', async () => {
|
|
355
|
+
const { evaluate } = await import('../src/node.js')
|
|
356
|
+
|
|
357
|
+
const result = await evaluate({
|
|
358
|
+
module: 'exports.foo = {;', // Invalid syntax
|
|
359
|
+
})
|
|
360
|
+
expect(result.success).toBe(false)
|
|
361
|
+
expect(result.error).toBeDefined()
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('returns error for undefined function calls', async () => {
|
|
365
|
+
const { evaluate } = await import('../src/node.js')
|
|
366
|
+
|
|
367
|
+
const result = await evaluate({
|
|
368
|
+
script: 'return undefinedFunction()',
|
|
369
|
+
})
|
|
370
|
+
expect(result.success).toBe(false)
|
|
371
|
+
expect(result.error).toBeDefined()
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('handles async errors in script', async () => {
|
|
375
|
+
const { evaluate } = await import('../src/node.js')
|
|
376
|
+
|
|
377
|
+
const result = await evaluate({
|
|
378
|
+
script: `
|
|
379
|
+
return Promise.reject(new Error('async failure'))
|
|
380
|
+
`,
|
|
381
|
+
})
|
|
382
|
+
expect(result.success).toBe(false)
|
|
383
|
+
expect(result.error).toContain('async failure')
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
it('catches module initialization errors', async () => {
|
|
387
|
+
const { evaluate } = await import('../src/node.js')
|
|
388
|
+
|
|
389
|
+
const result = await evaluate({
|
|
390
|
+
module: 'throw new Error("init error")',
|
|
391
|
+
script: 'return true',
|
|
392
|
+
})
|
|
393
|
+
// Module errors are logged, and execution may continue or fail
|
|
394
|
+
expect(result).toHaveProperty('logs')
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('returns proper error shape on failure', async () => {
|
|
398
|
+
const { evaluate } = await import('../src/node.js')
|
|
399
|
+
|
|
400
|
+
const result = await evaluate({
|
|
401
|
+
script: 'throw new Error("test")',
|
|
402
|
+
})
|
|
403
|
+
expect(result).toHaveProperty('success', false)
|
|
404
|
+
expect(result).toHaveProperty('error')
|
|
405
|
+
expect(result).toHaveProperty('logs')
|
|
406
|
+
expect(result).toHaveProperty('duration')
|
|
407
|
+
expect(typeof result.error).toBe('string')
|
|
408
|
+
expect(typeof result.duration).toBe('number')
|
|
409
|
+
})
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
describe('test execution', () => {
|
|
413
|
+
it('runs tests with passing results', async () => {
|
|
414
|
+
const { evaluate } = await import('../src/node.js')
|
|
415
|
+
|
|
416
|
+
const result = await evaluate({
|
|
417
|
+
tests: `
|
|
418
|
+
describe('math', () => {
|
|
419
|
+
it('adds numbers', () => {
|
|
420
|
+
expect(1 + 1).toBe(2);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
`,
|
|
424
|
+
})
|
|
425
|
+
expect(result.success).toBe(true)
|
|
426
|
+
expect(result.testResults?.total).toBe(1)
|
|
427
|
+
expect(result.testResults?.passed).toBe(1)
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
it('runs tests with failing results', async () => {
|
|
431
|
+
const { evaluate } = await import('../src/node.js')
|
|
432
|
+
|
|
433
|
+
const result = await evaluate({
|
|
434
|
+
tests: `
|
|
435
|
+
it('fails', () => {
|
|
436
|
+
expect(1).toBe(2);
|
|
437
|
+
});
|
|
438
|
+
`,
|
|
439
|
+
})
|
|
440
|
+
expect(result.success).toBe(false)
|
|
441
|
+
expect(result.testResults?.failed).toBe(1)
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
it('combines module and tests', async () => {
|
|
445
|
+
const { evaluate } = await import('../src/node.js')
|
|
446
|
+
|
|
447
|
+
const result = await evaluate({
|
|
448
|
+
module: `
|
|
449
|
+
exports.double = (n) => n * 2
|
|
450
|
+
`,
|
|
451
|
+
tests: `
|
|
452
|
+
describe('double', () => {
|
|
453
|
+
it('doubles 5', () => {
|
|
454
|
+
expect(double(5)).toBe(10);
|
|
455
|
+
});
|
|
456
|
+
it('doubles 0', () => {
|
|
457
|
+
expect(double(0)).toBe(0);
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
`,
|
|
461
|
+
})
|
|
462
|
+
expect(result.success).toBe(true)
|
|
463
|
+
expect(result.testResults?.total).toBe(2)
|
|
464
|
+
expect(result.testResults?.passed).toBe(2)
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
})
|