zero-query 0.9.9 → 1.0.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/README.md +34 -33
- package/cli/args.js +1 -1
- package/cli/commands/build.js +2 -2
- package/cli/commands/bundle.js +21 -18
- package/cli/commands/create.js +9 -2
- package/cli/commands/dev/devtools/index.js +1 -1
- package/cli/commands/dev/devtools/js/core.js +14 -14
- package/cli/commands/dev/devtools/js/elements.js +4 -4
- package/cli/commands/dev/devtools/js/stats.js +1 -1
- package/cli/commands/dev/devtools/styles.css +2 -2
- package/cli/commands/dev/index.js +2 -2
- package/cli/commands/dev/logger.js +1 -1
- package/cli/commands/dev/overlay.js +21 -14
- package/cli/commands/dev/server.js +5 -5
- package/cli/commands/dev/validator.js +7 -7
- package/cli/commands/dev/watcher.js +6 -6
- package/cli/help.js +3 -3
- package/cli/index.js +1 -1
- package/cli/scaffold/default/app/app.js +17 -18
- package/cli/scaffold/default/app/components/about.js +9 -9
- package/cli/scaffold/default/app/components/api-demo.js +6 -6
- package/cli/scaffold/default/app/components/contact-card.js +4 -4
- package/cli/scaffold/default/app/components/contacts/contacts.css +2 -2
- package/cli/scaffold/default/app/components/contacts/contacts.html +3 -3
- package/cli/scaffold/default/app/components/contacts/contacts.js +11 -11
- package/cli/scaffold/default/app/components/counter.js +8 -8
- package/cli/scaffold/default/app/components/home.js +13 -13
- package/cli/scaffold/default/app/components/not-found.js +1 -1
- package/cli/scaffold/default/app/components/playground/playground.css +1 -1
- package/cli/scaffold/default/app/components/playground/playground.html +11 -11
- package/cli/scaffold/default/app/components/playground/playground.js +11 -11
- package/cli/scaffold/default/app/components/todos.js +8 -8
- package/cli/scaffold/default/app/components/toolkit/toolkit.css +1 -1
- package/cli/scaffold/default/app/components/toolkit/toolkit.html +4 -4
- package/cli/scaffold/default/app/components/toolkit/toolkit.js +7 -7
- package/cli/scaffold/default/app/routes.js +1 -1
- package/cli/scaffold/default/app/store.js +1 -1
- package/cli/scaffold/default/global.css +2 -2
- package/cli/scaffold/default/index.html +2 -2
- package/cli/scaffold/minimal/app/app.js +6 -7
- package/cli/scaffold/minimal/app/components/about.js +5 -5
- package/cli/scaffold/minimal/app/components/counter.js +6 -6
- package/cli/scaffold/minimal/app/components/home.js +8 -8
- package/cli/scaffold/minimal/app/components/not-found.js +1 -1
- package/cli/scaffold/minimal/app/routes.js +1 -1
- package/cli/scaffold/minimal/app/store.js +1 -1
- package/cli/scaffold/minimal/global.css +2 -2
- package/cli/scaffold/minimal/index.html +1 -1
- package/cli/scaffold/ssr/app/app.js +1 -2
- package/cli/scaffold/ssr/app/components/about.js +5 -5
- package/cli/scaffold/ssr/app/components/home.js +2 -2
- package/cli/scaffold/ssr/app/components/not-found.js +2 -2
- package/cli/scaffold/ssr/app/routes.js +1 -1
- package/cli/scaffold/ssr/global.css +3 -4
- package/cli/scaffold/ssr/index.html +2 -2
- package/cli/scaffold/ssr/server/index.js +26 -25
- package/cli/utils.js +6 -6
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +508 -227
- package/dist/zquery.min.js +2 -2
- package/index.d.ts +16 -13
- package/index.js +7 -5
- package/package.json +3 -3
- package/src/component.js +64 -63
- package/src/core.js +15 -15
- package/src/diff.js +38 -38
- package/src/errors.js +17 -17
- package/src/expression.js +15 -17
- package/src/http.js +4 -4
- package/src/reactive.js +75 -9
- package/src/router.js +104 -24
- package/src/ssr.js +28 -28
- package/src/store.js +103 -21
- package/src/utils.js +64 -12
- package/tests/audit.test.js +143 -15
- package/tests/cli.test.js +20 -20
- package/tests/component.test.js +121 -121
- package/tests/core.test.js +56 -56
- package/tests/diff.test.js +42 -42
- package/tests/errors.test.js +5 -5
- package/tests/expression.test.js +58 -53
- package/tests/http.test.js +20 -20
- package/tests/reactive.test.js +185 -24
- package/tests/router.test.js +501 -74
- package/tests/ssr.test.js +15 -13
- package/tests/store.test.js +264 -23
- package/tests/test-minifier.js +153 -0
- package/tests/test-ssr.js +27 -0
- package/tests/utils.test.js +163 -26
- package/types/collection.d.ts +2 -2
- package/types/component.d.ts +5 -5
- package/types/errors.d.ts +3 -3
- package/types/http.d.ts +3 -3
- package/types/misc.d.ts +9 -9
- package/types/reactive.d.ts +25 -3
- package/types/router.d.ts +10 -6
- package/types/ssr.d.ts +2 -2
- package/types/store.d.ts +40 -5
- package/types/utils.d.ts +1 -1
package/tests/expression.test.js
CHANGED
|
@@ -12,7 +12,7 @@ const eval_ = (expr, ...scopes) => safeEval(expr, scopes.length ? scopes : [{}])
|
|
|
12
12
|
// Literals
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
|
|
15
|
-
describe('expression parser
|
|
15
|
+
describe('expression parser - literals', () => {
|
|
16
16
|
it('numbers', () => {
|
|
17
17
|
expect(eval_('42')).toBe(42);
|
|
18
18
|
expect(eval_('3.14')).toBe(3.14);
|
|
@@ -44,7 +44,7 @@ describe('expression parser — literals', () => {
|
|
|
44
44
|
// Arithmetic
|
|
45
45
|
// ---------------------------------------------------------------------------
|
|
46
46
|
|
|
47
|
-
describe('expression parser
|
|
47
|
+
describe('expression parser - arithmetic', () => {
|
|
48
48
|
it('basic operations', () => {
|
|
49
49
|
expect(eval_('2 + 3')).toBe(5);
|
|
50
50
|
expect(eval_('10 - 4')).toBe(6);
|
|
@@ -69,7 +69,7 @@ describe('expression parser — arithmetic', () => {
|
|
|
69
69
|
// Comparison & logical
|
|
70
70
|
// ---------------------------------------------------------------------------
|
|
71
71
|
|
|
72
|
-
describe('expression parser
|
|
72
|
+
describe('expression parser - comparison', () => {
|
|
73
73
|
it('equality', () => {
|
|
74
74
|
expect(eval_('1 === 1')).toBe(true);
|
|
75
75
|
expect(eval_('1 !== 2')).toBe(true);
|
|
@@ -86,7 +86,7 @@ describe('expression parser — comparison', () => {
|
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
describe('expression parser
|
|
89
|
+
describe('expression parser - logical', () => {
|
|
90
90
|
it('&& and ||', () => {
|
|
91
91
|
expect(eval_('true && false')).toBe(false);
|
|
92
92
|
expect(eval_('true || false')).toBe(true);
|
|
@@ -113,7 +113,7 @@ describe('expression parser — logical', () => {
|
|
|
113
113
|
// Ternary
|
|
114
114
|
// ---------------------------------------------------------------------------
|
|
115
115
|
|
|
116
|
-
describe('expression parser
|
|
116
|
+
describe('expression parser - ternary', () => {
|
|
117
117
|
it('evaluates truthy branch', () => {
|
|
118
118
|
expect(eval_("true ? 'yes' : 'no'")).toBe('yes');
|
|
119
119
|
});
|
|
@@ -132,7 +132,7 @@ describe('expression parser — ternary', () => {
|
|
|
132
132
|
// Property access & scope
|
|
133
133
|
// ---------------------------------------------------------------------------
|
|
134
134
|
|
|
135
|
-
describe('expression parser
|
|
135
|
+
describe('expression parser - property access', () => {
|
|
136
136
|
it('reads scope variables', () => {
|
|
137
137
|
expect(eval_('x', { x: 42 })).toBe(42);
|
|
138
138
|
expect(eval_('name', { name: 'Tony' })).toBe('Tony');
|
|
@@ -163,7 +163,7 @@ describe('expression parser — property access', () => {
|
|
|
163
163
|
// Method calls
|
|
164
164
|
// ---------------------------------------------------------------------------
|
|
165
165
|
|
|
166
|
-
describe('expression parser
|
|
166
|
+
describe('expression parser - method calls', () => {
|
|
167
167
|
it('string methods', () => {
|
|
168
168
|
expect(eval_("'hello'.toUpperCase()")).toBe('HELLO');
|
|
169
169
|
expect(eval_("'hello world'.split(' ')")).toEqual(['hello', 'world']);
|
|
@@ -187,7 +187,7 @@ describe('expression parser — method calls', () => {
|
|
|
187
187
|
// Built-in globals
|
|
188
188
|
// ---------------------------------------------------------------------------
|
|
189
189
|
|
|
190
|
-
describe('expression parser
|
|
190
|
+
describe('expression parser - built-in globals', () => {
|
|
191
191
|
it('Math', () => {
|
|
192
192
|
expect(eval_('Math.PI')).toBeCloseTo(3.14159);
|
|
193
193
|
expect(eval_('Math.max(1, 5, 3)')).toBe(5);
|
|
@@ -214,7 +214,7 @@ describe('expression parser — built-in globals', () => {
|
|
|
214
214
|
// Template literals
|
|
215
215
|
// ---------------------------------------------------------------------------
|
|
216
216
|
|
|
217
|
-
describe('expression parser
|
|
217
|
+
describe('expression parser - template literals', () => {
|
|
218
218
|
it('simple interpolation', () => {
|
|
219
219
|
expect(eval_('`Hello ${name}`', { name: 'Tony' })).toBe('Hello Tony');
|
|
220
220
|
});
|
|
@@ -233,7 +233,7 @@ describe('expression parser — template literals', () => {
|
|
|
233
233
|
// Array & object literals
|
|
234
234
|
// ---------------------------------------------------------------------------
|
|
235
235
|
|
|
236
|
-
describe('expression parser
|
|
236
|
+
describe('expression parser - array/object literals', () => {
|
|
237
237
|
it('array literal', () => {
|
|
238
238
|
expect(eval_('[1, 2, 3]')).toEqual([1, 2, 3]);
|
|
239
239
|
expect(eval_('[]')).toEqual([]);
|
|
@@ -253,7 +253,7 @@ describe('expression parser — array/object literals', () => {
|
|
|
253
253
|
// Arrow functions
|
|
254
254
|
// ---------------------------------------------------------------------------
|
|
255
255
|
|
|
256
|
-
describe('expression parser
|
|
256
|
+
describe('expression parser - arrow functions', () => {
|
|
257
257
|
it('single-param arrow', () => {
|
|
258
258
|
const fn = eval_('x => x * 2');
|
|
259
259
|
expect(fn(3)).toBe(6);
|
|
@@ -280,7 +280,7 @@ describe('expression parser — arrow functions', () => {
|
|
|
280
280
|
// typeof
|
|
281
281
|
// ---------------------------------------------------------------------------
|
|
282
282
|
|
|
283
|
-
describe('expression parser
|
|
283
|
+
describe('expression parser - typeof', () => {
|
|
284
284
|
it('typeof string', () => {
|
|
285
285
|
expect(eval_("typeof 'hello'")).toBe('string');
|
|
286
286
|
});
|
|
@@ -299,7 +299,7 @@ describe('expression parser — typeof', () => {
|
|
|
299
299
|
// Safety / security
|
|
300
300
|
// ---------------------------------------------------------------------------
|
|
301
301
|
|
|
302
|
-
describe('expression parser
|
|
302
|
+
describe('expression parser - safety', () => {
|
|
303
303
|
it('blocks constructor access', () => {
|
|
304
304
|
expect(eval_("''.constructor")).toBe(undefined);
|
|
305
305
|
});
|
|
@@ -323,7 +323,7 @@ describe('expression parser — safety', () => {
|
|
|
323
323
|
// Multi-scope resolution
|
|
324
324
|
// ---------------------------------------------------------------------------
|
|
325
325
|
|
|
326
|
-
describe('expression parser
|
|
326
|
+
describe('expression parser - multi-scope', () => {
|
|
327
327
|
it('checks scope layers in order', () => {
|
|
328
328
|
expect(safeEval('x', [{ x: 'first' }, { x: 'second' }])).toBe('first');
|
|
329
329
|
});
|
|
@@ -338,7 +338,7 @@ describe('expression parser — multi-scope', () => {
|
|
|
338
338
|
// in operator
|
|
339
339
|
// ---------------------------------------------------------------------------
|
|
340
340
|
|
|
341
|
-
describe('expression parser
|
|
341
|
+
describe('expression parser - in operator', () => {
|
|
342
342
|
it('checks property existence', () => {
|
|
343
343
|
expect(eval_("'x' in obj", { obj: { x: 1 } })).toBe(true);
|
|
344
344
|
expect(eval_("'y' in obj", { obj: { x: 1 } })).toBe(false);
|
|
@@ -350,7 +350,7 @@ describe('expression parser — in operator', () => {
|
|
|
350
350
|
// instanceof operator
|
|
351
351
|
// ---------------------------------------------------------------------------
|
|
352
352
|
|
|
353
|
-
describe('expression parser
|
|
353
|
+
describe('expression parser - instanceof', () => {
|
|
354
354
|
it('checks instanceOf', () => {
|
|
355
355
|
expect(eval_('arr instanceof Array', { arr: [1, 2] })).toBe(true);
|
|
356
356
|
expect(eval_('obj instanceof Array', { obj: {} })).toBe(false);
|
|
@@ -362,7 +362,7 @@ describe('expression parser — instanceof', () => {
|
|
|
362
362
|
// Nested ternary
|
|
363
363
|
// ---------------------------------------------------------------------------
|
|
364
364
|
|
|
365
|
-
describe('expression parser
|
|
365
|
+
describe('expression parser - nested ternary', () => {
|
|
366
366
|
it('evaluates simple ternary correctly', () => {
|
|
367
367
|
expect(eval_("x > 10 ? 'big' : 'small'", { x: 12 })).toBe('big');
|
|
368
368
|
expect(eval_("x > 10 ? 'big' : 'small'", { x: 2 })).toBe('small');
|
|
@@ -374,7 +374,7 @@ describe('expression parser — nested ternary', () => {
|
|
|
374
374
|
// Chained method calls
|
|
375
375
|
// ---------------------------------------------------------------------------
|
|
376
376
|
|
|
377
|
-
describe('expression parser
|
|
377
|
+
describe('expression parser - chained calls', () => {
|
|
378
378
|
it('chains array methods', () => {
|
|
379
379
|
expect(eval_('items.filter(x => x > 1).map(x => x * 2)', { items: [1, 2, 3] })).toEqual([4, 6]);
|
|
380
380
|
});
|
|
@@ -389,8 +389,8 @@ describe('expression parser — chained calls', () => {
|
|
|
389
389
|
// Spread operator
|
|
390
390
|
// ---------------------------------------------------------------------------
|
|
391
391
|
|
|
392
|
-
describe('expression parser
|
|
393
|
-
it('spread is not supported
|
|
392
|
+
describe('expression parser - spread / rest', () => {
|
|
393
|
+
it('spread is not supported - returns gracefully', () => {
|
|
394
394
|
// The parser does not support spread syntax; verify it doesn't throw
|
|
395
395
|
const result = eval_('[...items, 4]', { items: [1, 2, 3] });
|
|
396
396
|
expect(result).toBeDefined();
|
|
@@ -402,7 +402,7 @@ describe('expression parser — spread / rest', () => {
|
|
|
402
402
|
// Destructuring assignment in arrow body
|
|
403
403
|
// ---------------------------------------------------------------------------
|
|
404
404
|
|
|
405
|
-
describe('expression parser
|
|
405
|
+
describe('expression parser - complex arrow', () => {
|
|
406
406
|
it('arrow as callback in array method', () => {
|
|
407
407
|
const items = [{ n: 'a' }, { n: 'b' }];
|
|
408
408
|
expect(eval_('items.map(x => x.n)', { items })).toEqual(['a', 'b']);
|
|
@@ -420,8 +420,8 @@ describe('expression parser — complex arrow', () => {
|
|
|
420
420
|
// Bitwise operators
|
|
421
421
|
// ---------------------------------------------------------------------------
|
|
422
422
|
|
|
423
|
-
describe('expression parser
|
|
424
|
-
it('bitwise operators are not supported
|
|
423
|
+
describe('expression parser - bitwise', () => {
|
|
424
|
+
it('bitwise operators are not supported - does not throw', () => {
|
|
425
425
|
// The expression parser does not implement bitwise operators
|
|
426
426
|
// Verify graceful fallback rather than crashes
|
|
427
427
|
expect(() => eval_('5 | 3')).not.toThrow();
|
|
@@ -435,8 +435,8 @@ describe('expression parser — bitwise', () => {
|
|
|
435
435
|
// Comma expressions
|
|
436
436
|
// ---------------------------------------------------------------------------
|
|
437
437
|
|
|
438
|
-
describe('expression parser
|
|
439
|
-
it('comma expressions are not supported
|
|
438
|
+
describe('expression parser - comma', () => {
|
|
439
|
+
it('comma expressions are not supported - does not throw', () => {
|
|
440
440
|
// The parser does not support comma expressions
|
|
441
441
|
expect(() => eval_('(1, 2, 3)')).not.toThrow();
|
|
442
442
|
});
|
|
@@ -447,7 +447,7 @@ describe('expression parser — comma', () => {
|
|
|
447
447
|
// Edge cases
|
|
448
448
|
// ---------------------------------------------------------------------------
|
|
449
449
|
|
|
450
|
-
describe('expression parser
|
|
450
|
+
describe('expression parser - edge cases', () => {
|
|
451
451
|
it('handles very long dot chains', () => {
|
|
452
452
|
const data = { a: { b: { c: { d: { e: 42 } } } } };
|
|
453
453
|
expect(eval_('a.b.c.d.e', data)).toBe(42);
|
|
@@ -470,7 +470,7 @@ describe('expression parser — edge cases', () => {
|
|
|
470
470
|
expect(eval_('-1 + -2')).toBe(-3);
|
|
471
471
|
});
|
|
472
472
|
|
|
473
|
-
it('exponentiation ** is not supported
|
|
473
|
+
it('exponentiation ** is not supported - does not throw', () => {
|
|
474
474
|
// The parser does not implement ** operator
|
|
475
475
|
expect(() => eval_('2 ** 3')).not.toThrow();
|
|
476
476
|
});
|
|
@@ -486,7 +486,7 @@ describe('expression parser — edge cases', () => {
|
|
|
486
486
|
// Optional chaining edge cases
|
|
487
487
|
// ---------------------------------------------------------------------------
|
|
488
488
|
|
|
489
|
-
describe('expression parser
|
|
489
|
+
describe('expression parser - optional chaining edge cases', () => {
|
|
490
490
|
it('returns undefined for null base with ?.', () => {
|
|
491
491
|
expect(eval_('a?.b', { a: null })).toBeUndefined();
|
|
492
492
|
});
|
|
@@ -519,7 +519,7 @@ describe('expression parser — optional chaining edge cases', () => {
|
|
|
519
519
|
// Complex property access
|
|
520
520
|
// ---------------------------------------------------------------------------
|
|
521
521
|
|
|
522
|
-
describe('expression parser
|
|
522
|
+
describe('expression parser - complex property access', () => {
|
|
523
523
|
it('accesses deeply nested objects', () => {
|
|
524
524
|
const scope = { a: { b: { c: { d: { e: 'deep' } } } } };
|
|
525
525
|
expect(eval_('a.b.c.d.e', scope)).toBe('deep');
|
|
@@ -548,7 +548,7 @@ describe('expression parser — complex property access', () => {
|
|
|
548
548
|
// Arrow function edge cases
|
|
549
549
|
// ---------------------------------------------------------------------------
|
|
550
550
|
|
|
551
|
-
describe('expression parser
|
|
551
|
+
describe('expression parser - arrow function edge cases', () => {
|
|
552
552
|
it('no-param arrow function', () => {
|
|
553
553
|
const fn = eval_('() => 42');
|
|
554
554
|
expect(fn()).toBe(42);
|
|
@@ -581,7 +581,7 @@ describe('expression parser — arrow function edge cases', () => {
|
|
|
581
581
|
// Template literal edge cases
|
|
582
582
|
// ---------------------------------------------------------------------------
|
|
583
583
|
|
|
584
|
-
describe('expression parser
|
|
584
|
+
describe('expression parser - template literal edge cases', () => {
|
|
585
585
|
it('template with no interpolation', () => {
|
|
586
586
|
expect(eval_('`hello world`')).toBe('hello world');
|
|
587
587
|
});
|
|
@@ -612,7 +612,7 @@ describe('expression parser — template literal edge cases', () => {
|
|
|
612
612
|
// Nullish coalescing edge cases
|
|
613
613
|
// ---------------------------------------------------------------------------
|
|
614
614
|
|
|
615
|
-
describe('expression parser
|
|
615
|
+
describe('expression parser - nullish coalescing edge cases', () => {
|
|
616
616
|
it('returns left side for 0', () => {
|
|
617
617
|
expect(eval_('x ?? 10', { x: 0 })).toBe(0);
|
|
618
618
|
});
|
|
@@ -643,7 +643,7 @@ describe('expression parser — nullish coalescing edge cases', () => {
|
|
|
643
643
|
// Typeof edge cases
|
|
644
644
|
// ---------------------------------------------------------------------------
|
|
645
645
|
|
|
646
|
-
describe('expression parser
|
|
646
|
+
describe('expression parser - typeof edge cases', () => {
|
|
647
647
|
it('typeof undefined variable returns "undefined"', () => {
|
|
648
648
|
expect(eval_('typeof x')).toBe('undefined');
|
|
649
649
|
});
|
|
@@ -678,7 +678,7 @@ describe('expression parser — typeof edge cases', () => {
|
|
|
678
678
|
// Array/Object literal edge cases
|
|
679
679
|
// ---------------------------------------------------------------------------
|
|
680
680
|
|
|
681
|
-
describe('expression parser
|
|
681
|
+
describe('expression parser - array/object literal edge cases', () => {
|
|
682
682
|
it('array with trailing expression', () => {
|
|
683
683
|
expect(eval_('[1, 2, 3].length')).toBe(3);
|
|
684
684
|
});
|
|
@@ -709,7 +709,7 @@ describe('expression parser — array/object literal edge cases', () => {
|
|
|
709
709
|
// Method call edge cases
|
|
710
710
|
// ---------------------------------------------------------------------------
|
|
711
711
|
|
|
712
|
-
describe('expression parser
|
|
712
|
+
describe('expression parser - method call edge cases', () => {
|
|
713
713
|
it('chained string methods', () => {
|
|
714
714
|
expect(eval_('"Hello World".toLowerCase().split(" ")')).toEqual(['hello', 'world']);
|
|
715
715
|
});
|
|
@@ -758,7 +758,7 @@ describe('expression parser — method call edge cases', () => {
|
|
|
758
758
|
// Multi-scope resolution
|
|
759
759
|
// ---------------------------------------------------------------------------
|
|
760
760
|
|
|
761
|
-
describe('expression parser
|
|
761
|
+
describe('expression parser - multi-scope resolution', () => {
|
|
762
762
|
it('resolves from first scope when available', () => {
|
|
763
763
|
expect(eval_('x', { x: 1 }, { x: 2 })).toBe(1);
|
|
764
764
|
});
|
|
@@ -777,7 +777,7 @@ describe('expression parser — multi-scope resolution', () => {
|
|
|
777
777
|
// Security: blocked access
|
|
778
778
|
// ---------------------------------------------------------------------------
|
|
779
779
|
|
|
780
|
-
describe('expression parser
|
|
780
|
+
describe('expression parser - security', () => {
|
|
781
781
|
it('blocks constructor access', () => {
|
|
782
782
|
expect(() => eval_('"".constructor')).not.toThrow();
|
|
783
783
|
});
|
|
@@ -790,11 +790,11 @@ describe('expression parser — security', () => {
|
|
|
790
790
|
expect(() => eval_('++++')).not.toThrow();
|
|
791
791
|
});
|
|
792
792
|
|
|
793
|
-
it('global access is sandboxed
|
|
793
|
+
it('global access is sandboxed - no window', () => {
|
|
794
794
|
expect(eval_('typeof window')).toBe('undefined');
|
|
795
795
|
});
|
|
796
796
|
|
|
797
|
-
it('global access is sandboxed
|
|
797
|
+
it('global access is sandboxed - no document', () => {
|
|
798
798
|
expect(eval_('typeof document')).toBe('undefined');
|
|
799
799
|
});
|
|
800
800
|
});
|
|
@@ -804,7 +804,7 @@ describe('expression parser — security', () => {
|
|
|
804
804
|
// Comparison edge cases
|
|
805
805
|
// ---------------------------------------------------------------------------
|
|
806
806
|
|
|
807
|
-
describe('expression parser
|
|
807
|
+
describe('expression parser - comparison edge cases', () => {
|
|
808
808
|
it('strict equality with type mismatch', () => {
|
|
809
809
|
expect(eval_('1 === "1"')).toBe(false);
|
|
810
810
|
});
|
|
@@ -833,7 +833,7 @@ describe('expression parser — comparison edge cases', () => {
|
|
|
833
833
|
// Grouping / precedence
|
|
834
834
|
// ---------------------------------------------------------------------------
|
|
835
835
|
|
|
836
|
-
describe('expression parser
|
|
836
|
+
describe('expression parser - grouping and precedence', () => {
|
|
837
837
|
it('parentheses override precedence', () => {
|
|
838
838
|
expect(eval_('(2 + 3) * 4')).toBe(20);
|
|
839
839
|
});
|
|
@@ -857,10 +857,10 @@ describe('expression parser — grouping and precedence', () => {
|
|
|
857
857
|
|
|
858
858
|
|
|
859
859
|
// ===========================================================================
|
|
860
|
-
// new keyword
|
|
860
|
+
// new keyword - safe constructors
|
|
861
861
|
// ===========================================================================
|
|
862
862
|
|
|
863
|
-
describe('safeEval
|
|
863
|
+
describe('safeEval - new keyword', () => {
|
|
864
864
|
it('creates new Date (no args)', () => {
|
|
865
865
|
const result = eval_('new Date');
|
|
866
866
|
expect(result).toBeInstanceOf(Date);
|
|
@@ -871,14 +871,19 @@ describe('safeEval — new keyword', () => {
|
|
|
871
871
|
expect(result).toBeInstanceOf(Array);
|
|
872
872
|
});
|
|
873
873
|
|
|
874
|
-
it('Map/Set
|
|
875
|
-
// Map, Set
|
|
874
|
+
it('Map/Set in globals - new creates instances', () => {
|
|
875
|
+
// Map, Set are exposed as globals and whitelisted as safe constructors
|
|
876
876
|
expect(eval_('new Map')).toBeInstanceOf(Map);
|
|
877
877
|
expect(eval_('new Set')).toBeInstanceOf(Set);
|
|
878
|
-
expect(eval_('new RegExp')).toBeInstanceOf(RegExp);
|
|
879
878
|
});
|
|
880
879
|
|
|
881
|
-
it('
|
|
880
|
+
it('RegExp blocked - ReDoS prevention', () => {
|
|
881
|
+
// RegExp is no longer exposed or allowed as constructor to prevent ReDoS attacks
|
|
882
|
+
expect(eval_('new RegExp')).toBeUndefined();
|
|
883
|
+
expect(eval_('RegExp')).toBeUndefined();
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
it('new with args - parser correctly passes args to constructor', () => {
|
|
882
887
|
const result = eval_('new Date(2024, 0, 1)');
|
|
883
888
|
expect(result).toBeInstanceOf(Date);
|
|
884
889
|
expect(result.getFullYear()).toBe(2024);
|
|
@@ -897,7 +902,7 @@ describe('safeEval — new keyword', () => {
|
|
|
897
902
|
// void operator
|
|
898
903
|
// ===========================================================================
|
|
899
904
|
|
|
900
|
-
describe('safeEval
|
|
905
|
+
describe('safeEval - void operator', () => {
|
|
901
906
|
it('returns undefined', () => {
|
|
902
907
|
expect(eval_('void 0')).toBeUndefined();
|
|
903
908
|
});
|
|
@@ -912,7 +917,7 @@ describe('safeEval — void operator', () => {
|
|
|
912
917
|
// AST cache
|
|
913
918
|
// ===========================================================================
|
|
914
919
|
|
|
915
|
-
describe('safeEval
|
|
920
|
+
describe('safeEval - AST cache', () => {
|
|
916
921
|
it('returns same result on repeated evaluation (cache hit)', () => {
|
|
917
922
|
const r1 = eval_('1 + 2');
|
|
918
923
|
const r2 = eval_('1 + 2');
|
|
@@ -933,7 +938,7 @@ describe('safeEval — AST cache', () => {
|
|
|
933
938
|
// Optional call ?.()
|
|
934
939
|
// ===========================================================================
|
|
935
940
|
|
|
936
|
-
describe('safeEval
|
|
941
|
+
describe('safeEval - optional call ?.()', () => {
|
|
937
942
|
it('calls function when not null', () => {
|
|
938
943
|
expect(eval_('fn?.()', { fn: () => 42 })).toBe(42);
|
|
939
944
|
});
|
|
@@ -952,7 +957,7 @@ describe('safeEval — optional call ?.()', () => {
|
|
|
952
957
|
// Global builtins
|
|
953
958
|
// ===========================================================================
|
|
954
959
|
|
|
955
|
-
describe('safeEval
|
|
960
|
+
describe('safeEval - global builtins', () => {
|
|
956
961
|
it('accesses Date constructor', () => {
|
|
957
962
|
expect(eval_('Date.now()')).toBeGreaterThan(0);
|
|
958
963
|
});
|
|
@@ -1016,7 +1021,7 @@ describe('safeEval — global builtins', () => {
|
|
|
1016
1021
|
// Number methods
|
|
1017
1022
|
// ===========================================================================
|
|
1018
1023
|
|
|
1019
|
-
describe('safeEval
|
|
1024
|
+
describe('safeEval - number methods', () => {
|
|
1020
1025
|
it('calls toFixed', () => {
|
|
1021
1026
|
expect(eval_('x.toFixed(2)', { x: 3.14159 })).toBe('3.14');
|
|
1022
1027
|
});
|
|
@@ -1031,7 +1036,7 @@ describe('safeEval — number methods', () => {
|
|
|
1031
1036
|
// Empty/edge expressions
|
|
1032
1037
|
// ===========================================================================
|
|
1033
1038
|
|
|
1034
|
-
describe('safeEval
|
|
1039
|
+
describe('safeEval - edge cases', () => {
|
|
1035
1040
|
it('returns undefined for empty string', () => {
|
|
1036
1041
|
expect(eval_('')).toBeUndefined();
|
|
1037
1042
|
});
|
package/tests/http.test.js
CHANGED
|
@@ -99,7 +99,7 @@ describe('http.post', () => {
|
|
|
99
99
|
// Error handling
|
|
100
100
|
// ---------------------------------------------------------------------------
|
|
101
101
|
|
|
102
|
-
describe('http
|
|
102
|
+
describe('http - error handling', () => {
|
|
103
103
|
it('throws on non-ok response', async () => {
|
|
104
104
|
mockFetch({ error: 'Not Found' }, false, 404);
|
|
105
105
|
await expect(http.get('https://api.test.com/missing')).rejects.toThrow('HTTP 404');
|
|
@@ -126,7 +126,7 @@ describe('http — error handling', () => {
|
|
|
126
126
|
// PUT / PATCH / DELETE
|
|
127
127
|
// ---------------------------------------------------------------------------
|
|
128
128
|
|
|
129
|
-
describe('http
|
|
129
|
+
describe('http - other methods', () => {
|
|
130
130
|
it('PUT sends correct method', async () => {
|
|
131
131
|
mockFetch({});
|
|
132
132
|
await http.put('https://api.test.com/users/1', { name: 'Updated' });
|
|
@@ -172,7 +172,7 @@ describe('http.configure', () => {
|
|
|
172
172
|
// Text response
|
|
173
173
|
// ---------------------------------------------------------------------------
|
|
174
174
|
|
|
175
|
-
describe('http
|
|
175
|
+
describe('http - text response', () => {
|
|
176
176
|
it('parses text response', async () => {
|
|
177
177
|
mockFetch('Hello World');
|
|
178
178
|
const result = await http.get('https://api.test.com/text');
|
|
@@ -185,7 +185,7 @@ describe('http — text response', () => {
|
|
|
185
185
|
// Interceptors
|
|
186
186
|
// ---------------------------------------------------------------------------
|
|
187
187
|
|
|
188
|
-
describe('http
|
|
188
|
+
describe('http - interceptors', () => {
|
|
189
189
|
it('request interceptor via onRequest', async () => {
|
|
190
190
|
http.configure({ baseURL: '' });
|
|
191
191
|
http.onRequest((fetchOpts, url) => {
|
|
@@ -212,13 +212,13 @@ describe('http — interceptors', () => {
|
|
|
212
212
|
// Timeout / abort
|
|
213
213
|
// ---------------------------------------------------------------------------
|
|
214
214
|
|
|
215
|
-
describe('http
|
|
215
|
+
describe('http - abort signal', () => {
|
|
216
216
|
it('passes signal through options', async () => {
|
|
217
217
|
const controller = new AbortController();
|
|
218
218
|
mockFetch({});
|
|
219
219
|
await http.get('https://api.test.com/data', null, { signal: controller.signal });
|
|
220
220
|
const opts = fetchSpy.mock.calls[0][1];
|
|
221
|
-
// Signal may be combined via AbortSignal.any
|
|
221
|
+
// Signal may be combined via AbortSignal.any - verify it responds to user abort
|
|
222
222
|
expect(opts.signal).toBeDefined();
|
|
223
223
|
expect(opts.signal.aborted).toBe(false);
|
|
224
224
|
controller.abort();
|
|
@@ -231,7 +231,7 @@ describe('http — abort signal', () => {
|
|
|
231
231
|
// Blob response
|
|
232
232
|
// ---------------------------------------------------------------------------
|
|
233
233
|
|
|
234
|
-
describe('http
|
|
234
|
+
describe('http - blob response', () => {
|
|
235
235
|
it('can request blob responses', async () => {
|
|
236
236
|
mockFetch('binary data');
|
|
237
237
|
const result = await http.get('https://api.test.com/file', null, { responseType: 'blob' });
|
|
@@ -245,7 +245,7 @@ describe('http — blob response', () => {
|
|
|
245
245
|
// HEAD and OPTIONS methods
|
|
246
246
|
// ---------------------------------------------------------------------------
|
|
247
247
|
|
|
248
|
-
describe('http
|
|
248
|
+
describe('http - raw fetch pass-through', () => {
|
|
249
249
|
it('raw() delegates to native fetch', async () => {
|
|
250
250
|
mockFetch({ ok: true });
|
|
251
251
|
await http.raw('https://api.test.com/ping', { method: 'HEAD' });
|
|
@@ -258,7 +258,7 @@ describe('http — raw fetch pass-through', () => {
|
|
|
258
258
|
// Request with custom headers
|
|
259
259
|
// ---------------------------------------------------------------------------
|
|
260
260
|
|
|
261
|
-
describe('http
|
|
261
|
+
describe('http - custom per-request headers', () => {
|
|
262
262
|
it('merges per-request headers', async () => {
|
|
263
263
|
mockFetch({});
|
|
264
264
|
await http.get('https://api.test.com/data', null, {
|
|
@@ -274,7 +274,7 @@ describe('http — custom per-request headers', () => {
|
|
|
274
274
|
// Response metadata
|
|
275
275
|
// ---------------------------------------------------------------------------
|
|
276
276
|
|
|
277
|
-
describe('http
|
|
277
|
+
describe('http - response metadata', () => {
|
|
278
278
|
it('includes status and ok in result', async () => {
|
|
279
279
|
mockFetch({ data: 'yes' }, true, 200);
|
|
280
280
|
const result = await http.get('https://api.test.com/data');
|
|
@@ -326,7 +326,7 @@ describe('http.configure', () => {
|
|
|
326
326
|
// interceptors
|
|
327
327
|
// ===========================================================================
|
|
328
328
|
|
|
329
|
-
describe('http.onRequest
|
|
329
|
+
describe('http.onRequest - extra', () => {
|
|
330
330
|
it('interceptor modifying URL', async () => {
|
|
331
331
|
// Note: interceptors from earlier tests may still be registered; this one
|
|
332
332
|
// only adds a query param so it doesn't break others
|
|
@@ -337,16 +337,16 @@ describe('http.onRequest — extra', () => {
|
|
|
337
337
|
});
|
|
338
338
|
});
|
|
339
339
|
|
|
340
|
-
// Test interceptor blocking in isolation
|
|
340
|
+
// Test interceptor blocking in isolation - it permanently modifies internal state,
|
|
341
341
|
// so we verify behavior without adding a blocking interceptor that breaks later tests
|
|
342
|
-
describe('http
|
|
342
|
+
describe('http - interceptor blocking concept', () => {
|
|
343
343
|
it('onRequest returning false would throw blocked error', () => {
|
|
344
344
|
// Just verify the error message format expected by the source code
|
|
345
345
|
expect(() => { throw new Error('Request blocked by interceptor'); }).toThrow('blocked by interceptor');
|
|
346
346
|
});
|
|
347
347
|
});
|
|
348
348
|
|
|
349
|
-
describe('http.onResponse
|
|
349
|
+
describe('http.onResponse - extra', () => {
|
|
350
350
|
it('response interceptors are additive', async () => {
|
|
351
351
|
// onResponse was already called in earlier tests; verify the callback
|
|
352
352
|
const spy = vi.fn();
|
|
@@ -384,7 +384,7 @@ describe('http.delete', () => {
|
|
|
384
384
|
// GET with existing query string
|
|
385
385
|
// ===========================================================================
|
|
386
386
|
|
|
387
|
-
describe('http.get
|
|
387
|
+
describe('http.get - query params', () => {
|
|
388
388
|
it('appends params with & when URL already has ?', async () => {
|
|
389
389
|
mockFetch({ ok: true });
|
|
390
390
|
await http.get('https://api.test.com/search?q=hello', { page: 2 });
|
|
@@ -397,7 +397,7 @@ describe('http.get — query params', () => {
|
|
|
397
397
|
// string body
|
|
398
398
|
// ===========================================================================
|
|
399
399
|
|
|
400
|
-
describe('http.post
|
|
400
|
+
describe('http.post - string body', () => {
|
|
401
401
|
it('sends string body as-is', async () => {
|
|
402
402
|
mockFetch({ ok: true });
|
|
403
403
|
await http.post('https://api.test.com/raw', 'plain text body');
|
|
@@ -411,7 +411,7 @@ describe('http.post — string body', () => {
|
|
|
411
411
|
// URL validation
|
|
412
412
|
// ===========================================================================
|
|
413
413
|
|
|
414
|
-
describe('http
|
|
414
|
+
describe('http - URL validation', () => {
|
|
415
415
|
it('throws on missing URL', async () => {
|
|
416
416
|
await expect(http.get(undefined)).rejects.toThrow('URL string');
|
|
417
417
|
});
|
|
@@ -438,7 +438,7 @@ describe('http.createAbort', () => {
|
|
|
438
438
|
// timeout / abort
|
|
439
439
|
// ===========================================================================
|
|
440
440
|
|
|
441
|
-
describe('http
|
|
441
|
+
describe('http - AbortController integration', () => {
|
|
442
442
|
it('createAbort signal can be passed as option', async () => {
|
|
443
443
|
const controller = http.createAbort();
|
|
444
444
|
mockFetch({ ok: true });
|
|
@@ -490,7 +490,7 @@ describe('http.head', () => {
|
|
|
490
490
|
// Interceptor unsubscribe
|
|
491
491
|
// ===========================================================================
|
|
492
492
|
|
|
493
|
-
describe('http
|
|
493
|
+
describe('http - interceptor unsubscribe', () => {
|
|
494
494
|
it('onRequest returns an unsubscribe function', async () => {
|
|
495
495
|
http.clearInterceptors();
|
|
496
496
|
const spy = vi.fn();
|
|
@@ -584,7 +584,7 @@ describe('http.clearInterceptors', () => {
|
|
|
584
584
|
|
|
585
585
|
|
|
586
586
|
// ===========================================================================
|
|
587
|
-
// http.all
|
|
587
|
+
// http.all - parallel requests
|
|
588
588
|
// ===========================================================================
|
|
589
589
|
|
|
590
590
|
describe('http.all', () => {
|