@ryupold/vode 1.8.11 → 1.9.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/dist/vode.cjs.min.js +3 -3
- package/dist/vode.d.ts +23 -25
- package/dist/vode.es5.min.js +6 -6
- package/dist/vode.js +32 -9
- package/dist/vode.min.js +1 -1
- package/dist/vode.min.mjs +1 -1
- package/dist/vode.mjs +32 -9
- package/dist/vode.tests.mjs +281 -45
- package/package.json +4 -4
- package/src/merge-class.ts +3 -3
- package/src/state-context.ts +65 -38
- package/src/vode.ts +10 -3
- package/test/helper.ts +1 -1
- package/test/tests-app.ts +42 -2
- package/test/tests-catch.ts +61 -1
- package/test/tests-examples.ts +3 -3
- package/test/tests-mergeClass.ts +11 -4
- package/test/tests-mergeProps.ts +5 -0
- package/test/tests-patch-advanced.ts +20 -21
- package/test/tests-state-context.ts +181 -21
package/dist/vode.tests.mjs
CHANGED
|
@@ -96,9 +96,8 @@ function app(container, state, dom, ...initialPatches) {
|
|
|
96
96
|
});
|
|
97
97
|
function renderDom(isAnimated) {
|
|
98
98
|
const sw = performance.now();
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (container.tagName.toUpperCase() !== vom[0].toUpperCase()) {
|
|
99
|
+
_vode.vode = render(_vode.state, container.parentElement, 0, 0, _vode.vode, dom(_vode.state));
|
|
100
|
+
if (container.tagName.toLowerCase() !== _vode.vode[0].toLowerCase()) {
|
|
102
101
|
container = _vode.vode.node;
|
|
103
102
|
container._vode = _vode;
|
|
104
103
|
}
|
|
@@ -160,6 +159,10 @@ function app(container, state, dom, ...initialPatches) {
|
|
|
160
159
|
hydrate(container, true),
|
|
161
160
|
dom(state)
|
|
162
161
|
);
|
|
162
|
+
if (container.tagName.toLowerCase() !== _vode.vode[0].toLowerCase()) {
|
|
163
|
+
container = _vode.vode.node;
|
|
164
|
+
container._vode = _vode;
|
|
165
|
+
}
|
|
163
166
|
const continueRendering = _vode.stats.syncRenderPatchCount !== patchCountBefore;
|
|
164
167
|
_vode.isRendering = 0;
|
|
165
168
|
_vode.stats.syncRenderCount++;
|
|
@@ -688,8 +691,6 @@ function mergeClass(...classes) {
|
|
|
688
691
|
finalClass = { [a]: true, ...b };
|
|
689
692
|
} else if (typeof a === "object" && typeof b === "string") {
|
|
690
693
|
finalClass = { ...a, [b]: true };
|
|
691
|
-
} else if (typeof a === "object" && typeof b === "object") {
|
|
692
|
-
finalClass = { ...a, ...b };
|
|
693
694
|
} else if (typeof a === "object" && Array.isArray(b)) {
|
|
694
695
|
const aa = { ...a };
|
|
695
696
|
for (const item of b) {
|
|
@@ -705,6 +706,8 @@ function mergeClass(...classes) {
|
|
|
705
706
|
aa[bKey] = b[bKey];
|
|
706
707
|
}
|
|
707
708
|
finalClass = aa;
|
|
709
|
+
} else if (typeof a === "object" && typeof b === "object") {
|
|
710
|
+
finalClass = { ...a, ...b };
|
|
708
711
|
} else throw new Error(`cannot merge classes of ${a} (${typeof a}) and ${b} (${typeof b})`);
|
|
709
712
|
}
|
|
710
713
|
return finalClass;
|
|
@@ -755,7 +758,12 @@ function mergeProps(...props2) {
|
|
|
755
758
|
}
|
|
756
759
|
|
|
757
760
|
// src/state-context.ts
|
|
758
|
-
function context(state) {
|
|
761
|
+
function context(state, producePath) {
|
|
762
|
+
if (producePath) {
|
|
763
|
+
const proxy = producePath(proxyState(state, []));
|
|
764
|
+
const keys = proxy["___KeYs___"];
|
|
765
|
+
return new ProxyStateContextImpl(state, keys);
|
|
766
|
+
}
|
|
759
767
|
return new ProxyStateContextImpl(state, []);
|
|
760
768
|
}
|
|
761
769
|
var ProxyStateContextImpl = class _ProxyStateContextImpl {
|
|
@@ -813,16 +821,17 @@ var ProxyStateContextImpl = class _ProxyStateContextImpl {
|
|
|
813
821
|
}
|
|
814
822
|
return new Proxy(this, {
|
|
815
823
|
get: (target, prop, receiver) => {
|
|
816
|
-
if (prop === "state")
|
|
817
|
-
return state;
|
|
818
824
|
if (prop === "get")
|
|
819
825
|
return get;
|
|
820
826
|
if (prop === "put")
|
|
821
827
|
return put;
|
|
822
828
|
if (prop === "patch")
|
|
823
829
|
return patch;
|
|
824
|
-
const newKeys = [...
|
|
830
|
+
const newKeys = [...keys, String(prop)];
|
|
825
831
|
return new _ProxyStateContextImpl(target.state, newKeys);
|
|
832
|
+
},
|
|
833
|
+
set: (target, p, newValue, receiver) => {
|
|
834
|
+
throw new Error("ProxyStateContext is not meant to be directly mutated. Use put() or patch() methods on the StateContext instead");
|
|
826
835
|
}
|
|
827
836
|
});
|
|
828
837
|
}
|
|
@@ -836,6 +845,20 @@ var ProxyStateContextImpl = class _ProxyStateContextImpl {
|
|
|
836
845
|
patch(value) {
|
|
837
846
|
}
|
|
838
847
|
};
|
|
848
|
+
function proxyState(state, keys) {
|
|
849
|
+
return new Proxy(state, {
|
|
850
|
+
get: (target, prop, receiver) => {
|
|
851
|
+
if (prop === "___KeYs___") {
|
|
852
|
+
return keys;
|
|
853
|
+
}
|
|
854
|
+
const newKeys = [...keys, String(prop)];
|
|
855
|
+
return proxyState(state, newKeys);
|
|
856
|
+
},
|
|
857
|
+
set: (target, p, newValue, receiver) => {
|
|
858
|
+
throw new Error("ProxyState is not meant to be directly mutated");
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
}
|
|
839
862
|
|
|
840
863
|
// test/mocks.ts
|
|
841
864
|
var NodeConstants = {
|
|
@@ -1345,7 +1368,7 @@ but got <${tagName.toUpperCase()}>${failSuffix}`);
|
|
|
1345
1368
|
} else {
|
|
1346
1369
|
attributeValue = e.getAttribute(k);
|
|
1347
1370
|
}
|
|
1348
|
-
if (
|
|
1371
|
+
if (attributeValue === null) {
|
|
1349
1372
|
throw new ExpectationError(that, `expected at
|
|
1350
1373
|
${path.join(" > ")}
|
|
1351
1374
|
|
|
@@ -1627,8 +1650,7 @@ var tests_app_default = {
|
|
|
1627
1650
|
const state = { a: 1, b: 2 };
|
|
1628
1651
|
app(container, state, () => [DIV]);
|
|
1629
1652
|
await state.patch([{ a: 10 }, { b: 20 }]);
|
|
1630
|
-
await expect(state
|
|
1631
|
-
await expect(state.b).toEqual(20);
|
|
1653
|
+
await expect(state).toEqual({ a: 10, b: 20 });
|
|
1632
1654
|
},
|
|
1633
1655
|
"app(): multiple sequential patches both apply": async () => {
|
|
1634
1656
|
const root = document.createElement("div");
|
|
@@ -1814,6 +1836,42 @@ var tests_app_default = {
|
|
|
1814
1836
|
);
|
|
1815
1837
|
const el = container._vode.vode.node;
|
|
1816
1838
|
expect(el.onclick).toBeA("function");
|
|
1839
|
+
},
|
|
1840
|
+
"app(): class as array renders correctly": async () => {
|
|
1841
|
+
const root = document.createElement("div");
|
|
1842
|
+
const container = document.createElement("div");
|
|
1843
|
+
root.appendChild(container);
|
|
1844
|
+
app(
|
|
1845
|
+
container,
|
|
1846
|
+
{},
|
|
1847
|
+
() => [DIV, { class: ["foo", "bar", "baz"] }, "text"]
|
|
1848
|
+
);
|
|
1849
|
+
await expect(container).toMatch([DIV, { class: "foo bar baz" }, "text"]);
|
|
1850
|
+
},
|
|
1851
|
+
"app(): class as number becomes empty string": async () => {
|
|
1852
|
+
const root = document.createElement("div");
|
|
1853
|
+
const container = document.createElement("div");
|
|
1854
|
+
root.appendChild(container);
|
|
1855
|
+
app(
|
|
1856
|
+
container,
|
|
1857
|
+
{},
|
|
1858
|
+
() => [DIV, { class: 123 }, "text"]
|
|
1859
|
+
);
|
|
1860
|
+
await expect(container).toMatch([DIV, { class: "" }, "text"]);
|
|
1861
|
+
},
|
|
1862
|
+
"app(): style object to string transition": async () => {
|
|
1863
|
+
const root = document.createElement("div");
|
|
1864
|
+
const container = document.createElement("div");
|
|
1865
|
+
root.appendChild(container);
|
|
1866
|
+
const state = { useObject: true };
|
|
1867
|
+
app(
|
|
1868
|
+
container,
|
|
1869
|
+
state,
|
|
1870
|
+
(s) => [DIV, { style: s.useObject ? { color: "red" } : "color: blue" }, "text"]
|
|
1871
|
+
);
|
|
1872
|
+
await expect(container).toMatch([DIV, "text"]);
|
|
1873
|
+
state.patch({ useObject: false });
|
|
1874
|
+
await expect(container).toMatch([DIV, "text"]);
|
|
1817
1875
|
}
|
|
1818
1876
|
};
|
|
1819
1877
|
|
|
@@ -2288,11 +2346,13 @@ var tests_mergeClass_default = {
|
|
|
2288
2346
|
"mergeClass(): two objects": async () => {
|
|
2289
2347
|
await expect(mergeClass({ foo: true, bar: true }, { bar: false, baz: true })).toEqual({ foo: true, bar: false, baz: true });
|
|
2290
2348
|
},
|
|
2291
|
-
"mergeClass(): object and array": async () => {
|
|
2292
|
-
await expect(mergeClass({ foo: true }, ["bar", "baz"])).toEqual({ foo: true,
|
|
2349
|
+
"mergeClass(): object and array (array items become class names with true)": async () => {
|
|
2350
|
+
await expect(mergeClass({ foo: true }, ["bar", "baz"])).toEqual({ foo: true, bar: true, baz: true });
|
|
2351
|
+
await expect(mergeClass({ active: true }, ["btn", "primary"])).toEqual({ active: true, btn: true, primary: true });
|
|
2293
2352
|
},
|
|
2294
|
-
"mergeClass(): array and object": async () => {
|
|
2295
|
-
await expect(mergeClass(["foo", "bar"], { baz: true, qux: false })).toEqual({
|
|
2353
|
+
"mergeClass(): array and object (object keys become class names)": async () => {
|
|
2354
|
+
await expect(mergeClass(["foo", "bar"], { baz: true, qux: false })).toEqual({ foo: true, bar: true, baz: true, qux: false });
|
|
2355
|
+
await expect(mergeClass(["a", "b"], { c: true, d: false })).toEqual({ a: true, b: true, c: true, d: false });
|
|
2296
2356
|
},
|
|
2297
2357
|
"mergeClass(): falsy entries are skipped": async () => {
|
|
2298
2358
|
await expect(mergeClass("foo", null, "bar")).toEqual("foo bar");
|
|
@@ -2301,6 +2361,10 @@ var tests_mergeClass_default = {
|
|
|
2301
2361
|
"mergeClass(): multiple args (3+)": async () => {
|
|
2302
2362
|
await expect(mergeClass("a", "b", "c")).toEqual("a b c");
|
|
2303
2363
|
await expect(mergeClass("x", null, ["y", "z"], "w")).toEqual("y z x w");
|
|
2364
|
+
},
|
|
2365
|
+
"mergeClass(): incompatible types throw": async () => {
|
|
2366
|
+
await expect(() => mergeClass(123, "foo")).toFail();
|
|
2367
|
+
await expect(() => mergeClass("foo", 456)).toFail();
|
|
2304
2368
|
}
|
|
2305
2369
|
};
|
|
2306
2370
|
|
|
@@ -2356,6 +2420,10 @@ var tests_mergeProps_default = {
|
|
|
2356
2420
|
const p = { class: "foo" };
|
|
2357
2421
|
await expect(mergeProps(p) === p).toEqual(true);
|
|
2358
2422
|
},
|
|
2423
|
+
"mergeProps(): single falsy arg returns undefined": async () => {
|
|
2424
|
+
await expect(mergeProps(null)).toEqual(void 0);
|
|
2425
|
+
await expect(mergeProps(void 0)).toEqual(void 0);
|
|
2426
|
+
},
|
|
2359
2427
|
"mergeProps(): two plain objects merged": async () => {
|
|
2360
2428
|
await expect(mergeProps({ a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 });
|
|
2361
2429
|
},
|
|
@@ -2383,45 +2451,40 @@ var tests_mergeProps_default = {
|
|
|
2383
2451
|
|
|
2384
2452
|
// test/tests-state-context.ts
|
|
2385
2453
|
var tests_state_context_default = {
|
|
2386
|
-
"
|
|
2387
|
-
const state = createState({ x: 10 });
|
|
2388
|
-
const ctx = context(state);
|
|
2389
|
-
await expect(ctx.state === state).toEqual(true);
|
|
2390
|
-
},
|
|
2391
|
-
"StateContext.get() returns whole state": async () => {
|
|
2454
|
+
"context(s)...get(): returns whole state": async () => {
|
|
2392
2455
|
const state = createState({ a: 1, b: 2 });
|
|
2393
2456
|
const ctx = context(state);
|
|
2394
2457
|
await expect(ctx.get()).toEqual({ a: 1, b: 2 });
|
|
2395
2458
|
},
|
|
2396
|
-
"
|
|
2459
|
+
"context(s)...get(): deep nested": async () => {
|
|
2397
2460
|
const state = createState({ a: { b: { c: 42 } } });
|
|
2398
2461
|
const ctx = context(state);
|
|
2399
2462
|
await expect(ctx.a.b.c.get()).toEqual(42);
|
|
2400
2463
|
},
|
|
2401
|
-
"
|
|
2464
|
+
"context(s)...get(): missing nested path returns undefined": async () => {
|
|
2402
2465
|
const state = createState({ a: {} });
|
|
2403
2466
|
const ctx = context(state);
|
|
2404
2467
|
await expect(ctx.a.b.get()).toEqual(void 0);
|
|
2405
2468
|
},
|
|
2406
|
-
"
|
|
2469
|
+
"context(s)...put(): silently mutates state": async () => {
|
|
2407
2470
|
const state = createState({ a: { b: 1 } });
|
|
2408
2471
|
const ctx = context(state);
|
|
2409
2472
|
ctx.a.b.put(2);
|
|
2410
2473
|
await expect(state.a.b).toEqual(2);
|
|
2411
2474
|
},
|
|
2412
|
-
"
|
|
2475
|
+
"context(s)...put(): on nested object replaces the sub-object": async () => {
|
|
2413
2476
|
const state = createState({ a: { b: { x: 1, y: 2 } } });
|
|
2414
2477
|
const ctx = context(state);
|
|
2415
2478
|
ctx.a.b.put({ y: 99 });
|
|
2416
2479
|
await expect(state.a.b).toEqual({ y: 99 });
|
|
2417
2480
|
},
|
|
2418
|
-
"
|
|
2481
|
+
"context(s)...put(): at root level with empty keys": async () => {
|
|
2419
2482
|
const state = createState({ a: 1, b: 2 });
|
|
2420
2483
|
const ctx = context(state);
|
|
2421
2484
|
ctx.put({ b: void 0 });
|
|
2422
2485
|
await expect(state).toEqual({ a: 1 });
|
|
2423
2486
|
},
|
|
2424
|
-
"
|
|
2487
|
+
"context(s)...patch(): calls state.patch with proper deep partial": async () => {
|
|
2425
2488
|
const state = createState({ a: { b: 1 } });
|
|
2426
2489
|
const ctx = context(state);
|
|
2427
2490
|
ctx.a.b.patch(2);
|
|
@@ -2429,7 +2492,7 @@ var tests_state_context_default = {
|
|
|
2429
2492
|
await expect(patches.length).toEqual(1);
|
|
2430
2493
|
await expect(patches[0]).toEqual({ a: { b: 2 } });
|
|
2431
2494
|
},
|
|
2432
|
-
"
|
|
2495
|
+
"context(s)...patch(): async wraps in array": async () => {
|
|
2433
2496
|
const state = createState({ a: { b: 1 } });
|
|
2434
2497
|
const ctx = context(state);
|
|
2435
2498
|
ctx.a.b.patch(2, true);
|
|
@@ -2438,7 +2501,7 @@ var tests_state_context_default = {
|
|
|
2438
2501
|
await expect(Array.isArray(patches[0])).toEqual(true);
|
|
2439
2502
|
await expect(patches[0][0]).toEqual({ a: { b: 2 } });
|
|
2440
2503
|
},
|
|
2441
|
-
"
|
|
2504
|
+
"context(s)...patch(): on nested deep path three levels": async () => {
|
|
2442
2505
|
const state = createState({ x: { y: { z: 0 } } });
|
|
2443
2506
|
const ctx = context(state);
|
|
2444
2507
|
ctx.x.y.z.patch(100);
|
|
@@ -2446,7 +2509,7 @@ var tests_state_context_default = {
|
|
|
2446
2509
|
await expect(patches.length).toEqual(1);
|
|
2447
2510
|
await expect(patches[0]).toEqual({ x: { y: { z: 100 } } });
|
|
2448
2511
|
},
|
|
2449
|
-
"
|
|
2512
|
+
"context(s)...put(): with intermediate null creates objects along the path": async () => {
|
|
2450
2513
|
const state = createState({ a: null });
|
|
2451
2514
|
const ctx = context(state);
|
|
2452
2515
|
ctx.a.b.put(42);
|
|
@@ -2455,18 +2518,133 @@ var tests_state_context_default = {
|
|
|
2455
2518
|
await expect(state.a).toEqual(null);
|
|
2456
2519
|
await expect(state.a?.b).toEqual(void 0);
|
|
2457
2520
|
},
|
|
2458
|
-
"
|
|
2521
|
+
"context(s)...put(): with three-level intermediate null": async () => {
|
|
2459
2522
|
const state = createState({ a: null });
|
|
2460
2523
|
const ctx = context(state);
|
|
2461
2524
|
ctx.a.b.c.put(99);
|
|
2462
2525
|
await expect(state.a?.b.c).toEqual(99);
|
|
2463
2526
|
},
|
|
2464
|
-
"
|
|
2527
|
+
"context(s)...put(): with multiple intermediate nulls": async () => {
|
|
2465
2528
|
const state = createState({ a: { x: null, y: 1 } });
|
|
2466
2529
|
const ctx = context(state);
|
|
2467
2530
|
ctx.a.x.z.put("deep");
|
|
2468
2531
|
await expect(state.a.x?.z).toEqual("deep");
|
|
2469
2532
|
await expect(state.a.y).toEqual(1);
|
|
2533
|
+
},
|
|
2534
|
+
"context(s)...put(): merges into existing object properties via Object.assign": async () => {
|
|
2535
|
+
const state = createState({ items: { count: 0, name: "test", hidden: false } });
|
|
2536
|
+
const ctx = context(state);
|
|
2537
|
+
ctx.items.put({ count: 5 });
|
|
2538
|
+
await expect(state.items).toEqual({ count: 5, name: "test", hidden: false });
|
|
2539
|
+
},
|
|
2540
|
+
"context(state, s => s).get(): returns whole state": async () => {
|
|
2541
|
+
const state = createState({ a: 1, b: 2 });
|
|
2542
|
+
const ctx = context(state, (s) => s);
|
|
2543
|
+
await expect(ctx.get()).toEqual({ a: 1, b: 2 });
|
|
2544
|
+
},
|
|
2545
|
+
"context(state, s => s.a.b.c).get(): deep nested": async () => {
|
|
2546
|
+
const state = createState({ a: { b: { c: 42 } } });
|
|
2547
|
+
const ctx = context(state, (s) => s.a.b.c);
|
|
2548
|
+
await expect(ctx.get()).toEqual(42);
|
|
2549
|
+
},
|
|
2550
|
+
"context(state, s => s.a.b).get(): missing nested path returns undefined": async () => {
|
|
2551
|
+
const state = createState({ a: {} });
|
|
2552
|
+
const ctx = context(state, (s) => s.a.b);
|
|
2553
|
+
await expect(ctx.get()).toEqual(void 0);
|
|
2554
|
+
},
|
|
2555
|
+
"context(state, s => s.a.b).put(): silently mutates state": async () => {
|
|
2556
|
+
const state = createState({ a: { b: 1 } });
|
|
2557
|
+
const ctx = context(state, (s) => s.a.b);
|
|
2558
|
+
ctx.put(2);
|
|
2559
|
+
await expect(state.a.b).toEqual(2);
|
|
2560
|
+
},
|
|
2561
|
+
"context(state, s => s.a.b).put(): on nested object replaces the sub-object": async () => {
|
|
2562
|
+
const state = createState({ a: { b: { x: 1, y: 2 } } });
|
|
2563
|
+
const ctx = context(state, (s) => s.a.b);
|
|
2564
|
+
ctx.put({ y: 99 });
|
|
2565
|
+
await expect(state.a.b).toEqual({ y: 99 });
|
|
2566
|
+
},
|
|
2567
|
+
"context(state, s => s).put(): at root level with empty keys": async () => {
|
|
2568
|
+
const state = createState({ a: 1, b: 2 });
|
|
2569
|
+
const ctx = context(state, (s) => s);
|
|
2570
|
+
ctx.put({ b: void 0 });
|
|
2571
|
+
await expect(state).toEqual({ a: 1 });
|
|
2572
|
+
},
|
|
2573
|
+
"context(state, s => s.a.b).patch(): calls state.patch with proper deep partial": async () => {
|
|
2574
|
+
const state = createState({ a: { b: 1 } });
|
|
2575
|
+
const ctx = context(state, (s) => s.a.b);
|
|
2576
|
+
ctx.patch(2);
|
|
2577
|
+
const patches = state.patch.initialPatches;
|
|
2578
|
+
await expect(patches.length).toEqual(1);
|
|
2579
|
+
await expect(patches[0]).toEqual({ a: { b: 2 } });
|
|
2580
|
+
},
|
|
2581
|
+
"context(state, s => s.a.b).patch(): async wraps in array": async () => {
|
|
2582
|
+
const state = createState({ a: { b: 1 } });
|
|
2583
|
+
const ctx = context(state, (s) => s.a.b);
|
|
2584
|
+
ctx.patch(2, true);
|
|
2585
|
+
const patches = state.patch.initialPatches;
|
|
2586
|
+
await expect(patches.length).toEqual(1);
|
|
2587
|
+
await expect(Array.isArray(patches[0])).toEqual(true);
|
|
2588
|
+
await expect(patches[0][0]).toEqual({ a: { b: 2 } });
|
|
2589
|
+
},
|
|
2590
|
+
"context(state, s => s.x.y.z).patch(): on nested deep path three levels": async () => {
|
|
2591
|
+
const state = createState({ x: { y: { z: 0 } } });
|
|
2592
|
+
const ctx = context(state, (s) => s.x.y.z);
|
|
2593
|
+
ctx.patch(100);
|
|
2594
|
+
const patches = state.patch.initialPatches;
|
|
2595
|
+
await expect(patches.length).toEqual(1);
|
|
2596
|
+
await expect(patches[0]).toEqual({ x: { y: { z: 100 } } });
|
|
2597
|
+
},
|
|
2598
|
+
"context(state, s => s.x).y.z: continue proxy sub-state targeting": async () => {
|
|
2599
|
+
const state = createState({ x: { y: { z: 0 } } });
|
|
2600
|
+
const ctx = context(state, (s) => s.x).y.z;
|
|
2601
|
+
ctx.patch(100);
|
|
2602
|
+
const patches = state.patch.initialPatches;
|
|
2603
|
+
await expect(patches.length).toEqual(1);
|
|
2604
|
+
await expect(patches[0]).toEqual({ x: { y: { z: 100 } } });
|
|
2605
|
+
},
|
|
2606
|
+
"context(state, s => s.a.b).put(): with intermediate null creates objects": async () => {
|
|
2607
|
+
const state = createState({ a: null });
|
|
2608
|
+
const ctx = context(state, (s) => s.a.b);
|
|
2609
|
+
ctx.put(42);
|
|
2610
|
+
await expect(state.a?.b).toEqual(42);
|
|
2611
|
+
},
|
|
2612
|
+
"context(state, s => s.a.b.c).put(): with three-level intermediate null": async () => {
|
|
2613
|
+
const state = createState({ a: null });
|
|
2614
|
+
const ctx = context(state, (s) => s.a.b.c);
|
|
2615
|
+
ctx.put(99);
|
|
2616
|
+
await expect(state.a?.b.c).toEqual(99);
|
|
2617
|
+
},
|
|
2618
|
+
"context(state, s => s.a.x.z).put(): with multiple intermediate nulls": async () => {
|
|
2619
|
+
const state = createState({ a: { x: null, y: 1 } });
|
|
2620
|
+
const ctx = context(state, (s) => s.a.x.z);
|
|
2621
|
+
ctx.put("deep");
|
|
2622
|
+
await expect(state.a.x?.z).toEqual("deep");
|
|
2623
|
+
await expect(state.a.y).toEqual(1);
|
|
2624
|
+
},
|
|
2625
|
+
"context(state, s => s.items).put(): merges into existing object properties via Object.assign": async () => {
|
|
2626
|
+
const state = createState({ items: { count: 0, name: "test", hidden: false } });
|
|
2627
|
+
const ctx = context(state, (s) => s.items);
|
|
2628
|
+
ctx.put({ count: 5 });
|
|
2629
|
+
await expect(state.items).toEqual({ count: 5, name: "test", hidden: false });
|
|
2630
|
+
},
|
|
2631
|
+
"context(state, s => s.get|put|patch...): 'get','put','patch' as intermediate properties without conflict": async () => {
|
|
2632
|
+
const state = createState({
|
|
2633
|
+
endpoints: {
|
|
2634
|
+
get: { count: 1 },
|
|
2635
|
+
put: { count: 2 },
|
|
2636
|
+
patch: { count: 3 }
|
|
2637
|
+
}
|
|
2638
|
+
});
|
|
2639
|
+
const getCtx = context(state, (s) => s.endpoints.get);
|
|
2640
|
+
await expect(getCtx.get()).toEqual({ count: 1 });
|
|
2641
|
+
const putCtx = context(state, (s) => s.endpoints.put);
|
|
2642
|
+
putCtx.put({ count: 99 });
|
|
2643
|
+
await expect(state.endpoints.put).toEqual({ count: 99 });
|
|
2644
|
+
const patchCtx = context(state, (s) => s.endpoints.patch);
|
|
2645
|
+
patchCtx.patch({ count: 42 });
|
|
2646
|
+
const patches = state.patch.initialPatches;
|
|
2647
|
+
await expect(patches[0]).toEqual({ endpoints: { patch: { count: 42 } } });
|
|
2470
2648
|
}
|
|
2471
2649
|
};
|
|
2472
2650
|
|
|
@@ -4176,7 +4354,7 @@ var tests_examples_default = {
|
|
|
4176
4354
|
[BUTTON, "Add"],
|
|
4177
4355
|
[
|
|
4178
4356
|
NAV,
|
|
4179
|
-
[BUTTON, "All"],
|
|
4357
|
+
[BUTTON, { class: "active" }, "All"],
|
|
4180
4358
|
[BUTTON, "Active"],
|
|
4181
4359
|
[BUTTON, "Done"]
|
|
4182
4360
|
],
|
|
@@ -4200,7 +4378,7 @@ var tests_examples_default = {
|
|
|
4200
4378
|
[
|
|
4201
4379
|
NAV,
|
|
4202
4380
|
[BUTTON, "All"],
|
|
4203
|
-
[BUTTON, "Active"],
|
|
4381
|
+
[BUTTON, { class: "active" }, "Active"],
|
|
4204
4382
|
[BUTTON, "Done"]
|
|
4205
4383
|
],
|
|
4206
4384
|
[
|
|
@@ -4221,7 +4399,7 @@ var tests_examples_default = {
|
|
|
4221
4399
|
NAV,
|
|
4222
4400
|
[BUTTON, "All"],
|
|
4223
4401
|
[BUTTON, "Active"],
|
|
4224
|
-
[BUTTON, "Done"]
|
|
4402
|
+
[BUTTON, { class: "active" }, "Done"]
|
|
4225
4403
|
],
|
|
4226
4404
|
[
|
|
4227
4405
|
UL,
|
|
@@ -5246,6 +5424,66 @@ var tests_catch_default = {
|
|
|
5246
5424
|
[ARTICLE, "i am fine"]
|
|
5247
5425
|
]
|
|
5248
5426
|
);
|
|
5427
|
+
},
|
|
5428
|
+
"catch: bubbles up to the root component if deeply nested vodes dont catch it earlier": async () => {
|
|
5429
|
+
const root = document.createElement("div");
|
|
5430
|
+
const container = document.createElement("div");
|
|
5431
|
+
root.appendChild(container);
|
|
5432
|
+
app(
|
|
5433
|
+
container,
|
|
5434
|
+
{},
|
|
5435
|
+
() => [
|
|
5436
|
+
DIV,
|
|
5437
|
+
{
|
|
5438
|
+
catch: (s, err) => [DIV, `caught: ${err.message}`]
|
|
5439
|
+
},
|
|
5440
|
+
[
|
|
5441
|
+
MAIN,
|
|
5442
|
+
[
|
|
5443
|
+
SECTION,
|
|
5444
|
+
[ARTICLE, {
|
|
5445
|
+
onMount: () => {
|
|
5446
|
+
throw new Error("boom");
|
|
5447
|
+
}
|
|
5448
|
+
}]
|
|
5449
|
+
]
|
|
5450
|
+
]
|
|
5451
|
+
]
|
|
5452
|
+
);
|
|
5453
|
+
await expect(container).toMatch(
|
|
5454
|
+
[DIV, "caught: boom"]
|
|
5455
|
+
);
|
|
5456
|
+
},
|
|
5457
|
+
"catch: if catching in root vode with different Tag -> container will be replaced": async () => {
|
|
5458
|
+
const root = document.createElement("div");
|
|
5459
|
+
const container = document.createElement("div");
|
|
5460
|
+
root.appendChild(container);
|
|
5461
|
+
await expect(root.firstChild === container).toEqual(true);
|
|
5462
|
+
app(
|
|
5463
|
+
container,
|
|
5464
|
+
{},
|
|
5465
|
+
() => [
|
|
5466
|
+
DIV,
|
|
5467
|
+
{
|
|
5468
|
+
catch: (s, err) => [P, `caught: ${err.message}`]
|
|
5469
|
+
},
|
|
5470
|
+
[
|
|
5471
|
+
MAIN,
|
|
5472
|
+
[
|
|
5473
|
+
SECTION,
|
|
5474
|
+
[ARTICLE, {
|
|
5475
|
+
onMount: () => {
|
|
5476
|
+
throw new Error("boom");
|
|
5477
|
+
}
|
|
5478
|
+
}]
|
|
5479
|
+
]
|
|
5480
|
+
]
|
|
5481
|
+
]
|
|
5482
|
+
);
|
|
5483
|
+
await expect(root.firstChild === container).toEqual(false);
|
|
5484
|
+
await expect(root.firstChild).toMatch(
|
|
5485
|
+
[P, "caught: boom"]
|
|
5486
|
+
);
|
|
5249
5487
|
}
|
|
5250
5488
|
};
|
|
5251
5489
|
|
|
@@ -5293,27 +5531,25 @@ var tests_patch_advanced_default = {
|
|
|
5293
5531
|
const container = setup4();
|
|
5294
5532
|
const state = createState({ msg: "before" });
|
|
5295
5533
|
app(container, state, (s) => [DIV, s.msg]);
|
|
5296
|
-
state.patch(Promise.resolve({ msg: "after" }));
|
|
5297
|
-
await
|
|
5298
|
-
await expect(state.msg).toEqual("after");
|
|
5534
|
+
await state.patch(Promise.resolve({ msg: "after" }));
|
|
5535
|
+
await expect(state).toEqual({ msg: "after" });
|
|
5299
5536
|
await expect(container).toMatch([DIV, "after"]);
|
|
5300
5537
|
},
|
|
5301
5538
|
"patch(): array with empty patches applies nothing": async () => {
|
|
5302
5539
|
const container = setup4();
|
|
5303
5540
|
const state = createState({ x: 1, y: 2 });
|
|
5304
5541
|
app(container, state, (s) => [DIV]);
|
|
5305
|
-
state.patch([{}, {}]);
|
|
5306
|
-
await
|
|
5307
|
-
await expect(state
|
|
5542
|
+
await state.patch([{}, {}]);
|
|
5543
|
+
await delay(10);
|
|
5544
|
+
await expect(state).toEqual({ x: 1, y: 2 });
|
|
5308
5545
|
},
|
|
5309
5546
|
"patch(): array with null/undefined items skips them": async () => {
|
|
5310
5547
|
const container = setup4();
|
|
5311
5548
|
const state = createState({ x: 0, y: 0 });
|
|
5312
5549
|
app(container, state, (s) => [DIV, String(s.x), String(s.y)]);
|
|
5313
5550
|
state.patch([null, { x: 10 }, void 0, { y: 20 }]);
|
|
5314
|
-
await
|
|
5315
|
-
await expect(state.
|
|
5316
|
-
await expect(state.y).toEqual(20);
|
|
5551
|
+
await expect(() => expect(state.x).toEqual(10)).toSucceedAsync();
|
|
5552
|
+
await expect(() => expect(state.y).toEqual(20)).toSucceedAsync();
|
|
5317
5553
|
},
|
|
5318
5554
|
"patch(): returns Promise for generator functions, can be awaited": async () => {
|
|
5319
5555
|
const container = setup4();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ryupold/vode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "a minimalist web framework",
|
|
5
5
|
"author": "Michael Scherbakow (ryupold)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"build-tests": "esbuild test/index.ts --bundle --format=esm --outfile=dist/vode.tests.mjs"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@babel/cli": "7.
|
|
54
|
-
"@babel/core": "7.29.
|
|
55
|
-
"@babel/preset-env": "7.29.
|
|
53
|
+
"@babel/cli": "7.29.7",
|
|
54
|
+
"@babel/core": "7.29.7",
|
|
55
|
+
"@babel/preset-env": "7.29.7",
|
|
56
56
|
"babel-preset-minify": "0.5.2",
|
|
57
57
|
"dts-bundle-generator": "9.5.1",
|
|
58
58
|
"esbuild": "0.28.0",
|
package/src/merge-class.ts
CHANGED
|
@@ -38,9 +38,7 @@ export function mergeClass(...classes: ClassProp[]): ClassProp {
|
|
|
38
38
|
else if (typeof a === "object" && typeof b === "string") {
|
|
39
39
|
finalClass = { ...a, [b]: true };
|
|
40
40
|
}
|
|
41
|
-
else if (typeof a === "object" &&
|
|
42
|
-
finalClass = { ...a, ...b };
|
|
43
|
-
} else if (typeof a === "object" && Array.isArray(b)) {
|
|
41
|
+
else if (typeof a === "object" && Array.isArray(b)) {
|
|
44
42
|
const aa = { ...a };
|
|
45
43
|
for (const item of b as string[]) {
|
|
46
44
|
(<Record<string, boolean | null | undefined>>aa)[item] = true;
|
|
@@ -55,6 +53,8 @@ export function mergeClass(...classes: ClassProp[]): ClassProp {
|
|
|
55
53
|
aa[bKey] = (<Record<string, boolean | null | undefined>>b)[bKey];
|
|
56
54
|
}
|
|
57
55
|
finalClass = aa;
|
|
56
|
+
} else if (typeof a === "object" && typeof b === "object") {
|
|
57
|
+
finalClass = { ...a, ...b };
|
|
58
58
|
}
|
|
59
59
|
else throw new Error(`cannot merge classes of ${a} (${typeof a}) and ${b} (${typeof b})`);
|
|
60
60
|
}
|