zero-query 0.9.8 → 1.0.0
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 +55 -31
- package/cli/args.js +1 -1
- package/cli/commands/build.js +2 -2
- package/cli/commands/bundle.js +15 -15
- package/cli/commands/create.js +41 -7
- 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 +4 -2
- package/cli/index.js +2 -2
- 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 +29 -0
- package/cli/scaffold/ssr/app/components/about.js +28 -0
- package/cli/scaffold/ssr/app/components/home.js +37 -0
- package/cli/scaffold/ssr/app/components/not-found.js +15 -0
- package/cli/scaffold/ssr/app/routes.js +6 -0
- package/cli/scaffold/ssr/global.css +113 -0
- package/cli/scaffold/ssr/index.html +31 -0
- package/cli/scaffold/ssr/package.json +8 -0
- package/cli/scaffold/ssr/server/index.js +118 -0
- package/cli/utils.js +6 -6
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +565 -228
- package/dist/zquery.min.js +2 -2
- package/index.d.ts +25 -12
- package/index.js +11 -7
- package/package.json +9 -3
- package/src/component.js +64 -63
- package/src/core.js +15 -15
- package/src/diff.js +38 -38
- package/src/errors.js +72 -18
- package/src/expression.js +15 -17
- package/src/http.js +4 -4
- package/src/package.json +1 -0
- package/src/reactive.js +75 -9
- package/src/router.js +104 -24
- package/src/ssr.js +133 -39
- 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 +425 -147
- 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 +444 -10
- package/tests/store.test.js +264 -23
- 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 +36 -4
- 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 +22 -2
- package/types/store.d.ts +40 -5
- package/types/utils.d.ts +1 -1
package/tests/reactive.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import { reactive, Signal, signal, computed, effect } from '../src/reactive.js';
|
|
2
|
+
import { reactive, Signal, signal, computed, effect, batch, untracked } from '../src/reactive.js';
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
@@ -58,7 +58,7 @@ describe('reactive', () => {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
it('handles onChange gracefully when onChange is not a function', () => {
|
|
61
|
-
// Should not throw
|
|
61
|
+
// Should not throw - error is reported and a no-op is used
|
|
62
62
|
expect(() => {
|
|
63
63
|
const obj = reactive({ x: 1 }, 'not a function');
|
|
64
64
|
obj.x = 2;
|
|
@@ -231,7 +231,7 @@ describe('effect()', () => {
|
|
|
231
231
|
log(toggle.value ? a.value : b.value);
|
|
232
232
|
});
|
|
233
233
|
expect(log).toHaveBeenCalledWith('A');
|
|
234
|
-
// Change b
|
|
234
|
+
// Change b - should NOT re-run because b is not tracked when toggle=true
|
|
235
235
|
b.value = 'B2';
|
|
236
236
|
// After toggle switches, b becomes tracked
|
|
237
237
|
toggle.value = false;
|
|
@@ -241,10 +241,10 @@ describe('effect()', () => {
|
|
|
241
241
|
|
|
242
242
|
|
|
243
243
|
// ---------------------------------------------------------------------------
|
|
244
|
-
// reactive
|
|
244
|
+
// reactive - array mutations
|
|
245
245
|
// ---------------------------------------------------------------------------
|
|
246
246
|
|
|
247
|
-
describe('reactive
|
|
247
|
+
describe('reactive - arrays', () => {
|
|
248
248
|
it('detects push on a reactive array', () => {
|
|
249
249
|
const fn = vi.fn();
|
|
250
250
|
const obj = reactive({ items: [1, 2, 3] }, fn);
|
|
@@ -262,10 +262,10 @@ describe('reactive — arrays', () => {
|
|
|
262
262
|
|
|
263
263
|
|
|
264
264
|
// ---------------------------------------------------------------------------
|
|
265
|
-
// computed
|
|
265
|
+
// computed - advanced
|
|
266
266
|
// ---------------------------------------------------------------------------
|
|
267
267
|
|
|
268
|
-
describe('computed
|
|
268
|
+
describe('computed - advanced', () => {
|
|
269
269
|
it('chains computed signals', () => {
|
|
270
270
|
const count = signal(2);
|
|
271
271
|
const doubled = computed(() => count.value * 2);
|
|
@@ -302,10 +302,10 @@ describe('computed — advanced', () => {
|
|
|
302
302
|
|
|
303
303
|
|
|
304
304
|
// ---------------------------------------------------------------------------
|
|
305
|
-
// Signal
|
|
305
|
+
// Signal - batch behavior
|
|
306
306
|
// ---------------------------------------------------------------------------
|
|
307
307
|
|
|
308
|
-
describe('Signal
|
|
308
|
+
describe('Signal - multiple subscribers', () => {
|
|
309
309
|
it('notifies all subscribers', () => {
|
|
310
310
|
const s = signal(0);
|
|
311
311
|
const fn1 = vi.fn();
|
|
@@ -343,7 +343,7 @@ describe('Signal — multiple subscribers', () => {
|
|
|
343
343
|
// BUG FIX: effect() dispose must not corrupt _activeEffect
|
|
344
344
|
// ---------------------------------------------------------------------------
|
|
345
345
|
|
|
346
|
-
describe('effect
|
|
346
|
+
describe('effect - dispose safety', () => {
|
|
347
347
|
it('disposing inside another effect does not break tracking', () => {
|
|
348
348
|
const a = signal(1);
|
|
349
349
|
const b = signal(2);
|
|
@@ -376,10 +376,10 @@ describe('effect — dispose safety', () => {
|
|
|
376
376
|
// PERF FIX: computed() should not notify when value unchanged
|
|
377
377
|
// ---------------------------------------------------------------------------
|
|
378
378
|
|
|
379
|
-
describe('computed
|
|
379
|
+
describe('computed - skip notification on same value', () => {
|
|
380
380
|
it('does not notify subscribers when computed result is the same', () => {
|
|
381
381
|
const s = signal(5);
|
|
382
|
-
// Computed that clamps to a range
|
|
382
|
+
// Computed that clamps to a range - returns same value if within bounds
|
|
383
383
|
const clamped = computed(() => Math.min(Math.max(s.value, 0), 10));
|
|
384
384
|
expect(clamped.value).toBe(5);
|
|
385
385
|
|
|
@@ -392,19 +392,19 @@ describe('computed — skip notification on same value', () => {
|
|
|
392
392
|
expect(subscriber).toHaveBeenCalledTimes(1);
|
|
393
393
|
|
|
394
394
|
subscriber.mockClear();
|
|
395
|
-
// Changing s from 7 to 15
|
|
395
|
+
// Changing s from 7 to 15 - clamped stays at 10
|
|
396
396
|
s.value = 15;
|
|
397
397
|
expect(clamped.value).toBe(10);
|
|
398
|
-
s.value = 20; // clamped still 10
|
|
398
|
+
s.value = 20; // clamped still 10 - should NOT notify again
|
|
399
399
|
expect(subscriber).toHaveBeenCalledTimes(1); // only the 7→10 change
|
|
400
400
|
});
|
|
401
401
|
});
|
|
402
402
|
|
|
403
403
|
// ===========================================================================
|
|
404
|
-
// reactive()
|
|
404
|
+
// reactive() - advanced edge cases
|
|
405
405
|
// ===========================================================================
|
|
406
406
|
|
|
407
|
-
describe('reactive
|
|
407
|
+
describe('reactive - edge cases', () => {
|
|
408
408
|
it('returns primitive as-is', () => {
|
|
409
409
|
expect(reactive(42, () => {})).toBe(42);
|
|
410
410
|
expect(reactive('hello', () => {})).toBe('hello');
|
|
@@ -473,7 +473,7 @@ describe('reactive — edge cases', () => {
|
|
|
473
473
|
|
|
474
474
|
it('onChange throwing does not prevent set', () => {
|
|
475
475
|
const r = reactive({ a: 1 }, () => { throw new Error('boom'); });
|
|
476
|
-
// Should not throw externally
|
|
476
|
+
// Should not throw externally - error is reported via reportError
|
|
477
477
|
r.a = 2;
|
|
478
478
|
expect(r.__raw.a).toBe(2);
|
|
479
479
|
});
|
|
@@ -488,10 +488,10 @@ describe('reactive — edge cases', () => {
|
|
|
488
488
|
|
|
489
489
|
|
|
490
490
|
// ===========================================================================
|
|
491
|
-
// Signal
|
|
491
|
+
// Signal - advanced
|
|
492
492
|
// ===========================================================================
|
|
493
493
|
|
|
494
|
-
describe('Signal
|
|
494
|
+
describe('Signal - advanced', () => {
|
|
495
495
|
it('peek() does not trigger tracking', () => {
|
|
496
496
|
const s = signal(1);
|
|
497
497
|
const fn = vi.fn(() => { s.peek(); });
|
|
@@ -549,10 +549,10 @@ describe('Signal — advanced', () => {
|
|
|
549
549
|
|
|
550
550
|
|
|
551
551
|
// ===========================================================================
|
|
552
|
-
// effect()
|
|
552
|
+
// effect() - advanced
|
|
553
553
|
// ===========================================================================
|
|
554
554
|
|
|
555
|
-
describe('effect
|
|
555
|
+
describe('effect - advanced', () => {
|
|
556
556
|
it('returns dispose function', () => {
|
|
557
557
|
const s = signal(0);
|
|
558
558
|
const fn = vi.fn(() => s.value);
|
|
@@ -625,17 +625,17 @@ describe('effect — advanced', () => {
|
|
|
625
625
|
|
|
626
626
|
|
|
627
627
|
// ===========================================================================
|
|
628
|
-
// computed()
|
|
628
|
+
// computed() - advanced
|
|
629
629
|
// ===========================================================================
|
|
630
630
|
|
|
631
|
-
describe('computed
|
|
631
|
+
describe('computed - advanced', () => {
|
|
632
632
|
it('computed does not notify when value unchanged', () => {
|
|
633
633
|
const s = signal(5);
|
|
634
634
|
const c = computed(() => s.value > 3);
|
|
635
635
|
const fn = vi.fn();
|
|
636
636
|
c.subscribe(fn);
|
|
637
637
|
|
|
638
|
-
s.value = 10; // c still true
|
|
638
|
+
s.value = 10; // c still true - no change
|
|
639
639
|
expect(fn).not.toHaveBeenCalled();
|
|
640
640
|
});
|
|
641
641
|
|
|
@@ -656,4 +656,165 @@ describe('computed — advanced', () => {
|
|
|
656
656
|
first.value = 'Jane';
|
|
657
657
|
expect(full.value).toBe('Jane Doe');
|
|
658
658
|
});
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
// ===========================================================================
|
|
663
|
+
// batch()
|
|
664
|
+
// ===========================================================================
|
|
665
|
+
|
|
666
|
+
describe('batch()', () => {
|
|
667
|
+
it('defers effect execution until batch completes', () => {
|
|
668
|
+
const a = signal(1);
|
|
669
|
+
const b = signal(2);
|
|
670
|
+
const fn = vi.fn();
|
|
671
|
+
|
|
672
|
+
effect(() => {
|
|
673
|
+
fn(a.value + b.value);
|
|
674
|
+
});
|
|
675
|
+
fn.mockClear(); // clear initial run
|
|
676
|
+
|
|
677
|
+
batch(() => {
|
|
678
|
+
a.value = 10;
|
|
679
|
+
b.value = 20;
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
// Effect should run once after the batch, not twice
|
|
683
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
684
|
+
expect(fn).toHaveBeenCalledWith(30);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it('subscribers see the final value, not intermediate', () => {
|
|
688
|
+
const s = signal(0);
|
|
689
|
+
const values = [];
|
|
690
|
+
s.subscribe(() => values.push(s.value));
|
|
691
|
+
|
|
692
|
+
batch(() => {
|
|
693
|
+
s.value = 1;
|
|
694
|
+
s.value = 2;
|
|
695
|
+
s.value = 3;
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
expect(values).toEqual([3]);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it('nested batch runs inner immediately, flushes at outer', () => {
|
|
702
|
+
const s = signal(0);
|
|
703
|
+
const fn = vi.fn();
|
|
704
|
+
s.subscribe(fn);
|
|
705
|
+
|
|
706
|
+
batch(() => {
|
|
707
|
+
s.value = 1;
|
|
708
|
+
batch(() => {
|
|
709
|
+
s.value = 2;
|
|
710
|
+
});
|
|
711
|
+
// inner batch should not have flushed
|
|
712
|
+
expect(fn).not.toHaveBeenCalled();
|
|
713
|
+
s.value = 3;
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Outer batch flushes once
|
|
717
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
718
|
+
expect(s.value).toBe(3);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
it('computed values update correctly after batch', () => {
|
|
722
|
+
const a = signal(1);
|
|
723
|
+
const b = signal(2);
|
|
724
|
+
const sum = computed(() => a.value + b.value);
|
|
725
|
+
|
|
726
|
+
batch(() => {
|
|
727
|
+
a.value = 10;
|
|
728
|
+
b.value = 20;
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
expect(sum.value).toBe(30);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it('effects still run if batch throws', () => {
|
|
735
|
+
const s = signal(0);
|
|
736
|
+
const fn = vi.fn();
|
|
737
|
+
s.subscribe(() => fn(s.value));
|
|
738
|
+
|
|
739
|
+
try {
|
|
740
|
+
batch(() => {
|
|
741
|
+
s.value = 42;
|
|
742
|
+
throw new Error('oops');
|
|
743
|
+
});
|
|
744
|
+
} catch {}
|
|
745
|
+
|
|
746
|
+
// Batch should still flush on error via finally
|
|
747
|
+
expect(fn).toHaveBeenCalledWith(42);
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
// ===========================================================================
|
|
753
|
+
// untracked()
|
|
754
|
+
// ===========================================================================
|
|
755
|
+
|
|
756
|
+
describe('untracked()', () => {
|
|
757
|
+
it('reads signals without creating dependencies', () => {
|
|
758
|
+
const a = signal(1);
|
|
759
|
+
const b = signal(10);
|
|
760
|
+
const fn = vi.fn();
|
|
761
|
+
|
|
762
|
+
effect(() => {
|
|
763
|
+
const aVal = a.value; // tracked
|
|
764
|
+
const bVal = untracked(() => b.value); // not tracked
|
|
765
|
+
fn(aVal + bVal);
|
|
766
|
+
});
|
|
767
|
+
fn.mockClear();
|
|
768
|
+
|
|
769
|
+
// Changing b should NOT re-run the effect
|
|
770
|
+
b.value = 20;
|
|
771
|
+
expect(fn).not.toHaveBeenCalled();
|
|
772
|
+
|
|
773
|
+
// Changing a should re-run and pick up new b
|
|
774
|
+
a.value = 2;
|
|
775
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
776
|
+
expect(fn).toHaveBeenCalledWith(22); // a=2 + b=20
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
it('returns the value from the callback', () => {
|
|
780
|
+
const s = signal(42);
|
|
781
|
+
const result = untracked(() => s.value);
|
|
782
|
+
expect(result).toBe(42);
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
it('does not break tracking for outer effect', () => {
|
|
786
|
+
const tracked = signal('hello');
|
|
787
|
+
const notTracked = signal('world');
|
|
788
|
+
const runs = [];
|
|
789
|
+
|
|
790
|
+
effect(() => {
|
|
791
|
+
const t = tracked.value;
|
|
792
|
+
const u = untracked(() => notTracked.value);
|
|
793
|
+
runs.push(`${t} ${u}`);
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
expect(runs).toEqual(['hello world']);
|
|
797
|
+
|
|
798
|
+
tracked.value = 'hi';
|
|
799
|
+
expect(runs).toEqual(['hello world', 'hi world']);
|
|
800
|
+
|
|
801
|
+
notTracked.value = 'earth';
|
|
802
|
+
expect(runs).toEqual(['hello world', 'hi world']); // no re-run
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
it('works inside computed', () => {
|
|
806
|
+
const a = signal(5);
|
|
807
|
+
const b = signal(10);
|
|
808
|
+
const c = computed(() => a.value + untracked(() => b.value));
|
|
809
|
+
|
|
810
|
+
expect(c.value).toBe(15);
|
|
811
|
+
|
|
812
|
+
b.value = 100;
|
|
813
|
+
// computed shouldn't re-evaluate from b change
|
|
814
|
+
expect(c.value).toBe(15);
|
|
815
|
+
|
|
816
|
+
a.value = 1;
|
|
817
|
+
// Now recomputes, picks up new b
|
|
818
|
+
expect(c.value).toBe(101);
|
|
819
|
+
});
|
|
659
820
|
});
|