@ryupold/vode 1.8.7 → 1.8.10

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.
@@ -0,0 +1,86 @@
1
+ import { delay, expect } from "./helper";
2
+ import { app, createState, DIV } from "../index";
3
+
4
+ function setup() {
5
+ const root = document.createElement("div");
6
+ const container = document.createElement("div");
7
+ root.appendChild(container);
8
+ return container;
9
+ }
10
+
11
+ export default {
12
+ "patch(): generator function yields multiple state updates": async () => {
13
+ const container = setup();
14
+ const state: any = createState({ count: 0 });
15
+ app(container, state, (s: any) => [DIV, String(s.count)]);
16
+
17
+ await expect(state.count).toEqual(0);
18
+
19
+ state.patch(function* () {
20
+ yield { count: 1 };
21
+ yield { count: 2 };
22
+ return { count: 3 };
23
+ });
24
+
25
+ await new Promise(r => setTimeout(r, 0));
26
+
27
+ await expect(state.count).toEqual(3);
28
+ await expect(container).toMatch([DIV, "3"]);
29
+ },
30
+
31
+ "patch(): async generator yields over time": async () => {
32
+ const container = setup();
33
+ const state: any = createState({ phase: "start", value: 0 });
34
+ app(container, state, (s: any) => [DIV, s.phase, String(s.value)]);
35
+
36
+ await expect(state.phase).toEqual("start");
37
+
38
+ state.patch(async function* () {
39
+ yield { phase: "working", value: 10 };
40
+ yield { phase: "almost", value: 20 };
41
+ return { phase: "done", value: 30 };
42
+ }());
43
+
44
+ await new Promise(r => setTimeout(r, 0));
45
+
46
+ await expect(state.phase).toEqual("done");
47
+ await expect(state.value).toEqual(30);
48
+ await expect(container).toMatch([DIV, "done", "30"]);
49
+ },
50
+
51
+ "patch(): Promise resolves and applies patch": async () => {
52
+ const container = setup();
53
+ const state: any = createState({ msg: "before" });
54
+ app(container, state, (s: any) => [DIV, s.msg]);
55
+
56
+ state.patch(Promise.resolve({ msg: "after" }));
57
+
58
+ await delay(10);
59
+
60
+ await expect(state.msg).toEqual("after");
61
+ await expect(container).toMatch([DIV, "after"]);
62
+ },
63
+
64
+ "patch(): array with empty patches applies nothing": async () => {
65
+ const container = setup();
66
+ const state: any = createState({ x: 1, y: 2 });
67
+ app(container, state, (s: any) => [DIV]);
68
+
69
+ state.patch([{}, {}]);
70
+ await expect(state.x).toEqual(1);
71
+ await expect(state.y).toEqual(2);
72
+ },
73
+
74
+ "patch(): array with null/undefined items skips them": async () => {
75
+ const container = setup();
76
+ const state: any = createState({ x: 0, y: 0 });
77
+ app(container, state, (s: any) => [DIV, String(s.x), String(s.y)]);
78
+
79
+ state.patch([null, { x: 10 }, undefined, { y: 20 }]);
80
+
81
+ await delay(10);
82
+
83
+ await expect(state.x).toEqual(10);
84
+ await expect(state.y).toEqual(20);
85
+ },
86
+ };
@@ -0,0 +1,66 @@
1
+ import { expect } from "./helper";
2
+ import { app, createState, DIV } from "../index";
3
+
4
+ function setup() {
5
+ const root = document.createElement("div");
6
+ const container = document.createElement("div");
7
+ root.appendChild(container);
8
+ return container;
9
+ }
10
+
11
+ export default {
12
+ "patch-merge: array property replaces existing array": async () => {
13
+ const container = setup();
14
+ const state: any = createState({ items: [1, 2, 3] });
15
+ app(container, state, () => [DIV]);
16
+
17
+ state.patch({ items: [4, 5, 6] });
18
+
19
+ await expect(state.items).toEqual([4, 5, 6]);
20
+ },
21
+
22
+ "patch-merge: Date property stores correctly": async () => {
23
+ const container = setup();
24
+ const state: any = createState({ date: new Date("2024-01-01") });
25
+ app(container, state, () => [DIV]);
26
+
27
+ state.patch({ date: new Date("2025-06-15") });
28
+
29
+ await expect(state.date instanceof Date).toEqual(true);
30
+ await expect(state.date.getFullYear()).toEqual(2025);
31
+ await expect(state.date.getMonth()).toEqual(5);
32
+ await expect(state.date.getDate()).toEqual(15);
33
+ },
34
+
35
+ "patch-merge: object replaces existing array property": async () => {
36
+ const container = setup();
37
+ const state: any = createState({ data: [1, 2, 3] });
38
+ app(container, state, () => [DIV]);
39
+
40
+ state.patch({ data: { key: "value" } });
41
+
42
+ await expect(Array.isArray(state.data)).toEqual(false);
43
+ await expect(state.data.key).toEqual("value");
44
+ },
45
+
46
+ "patch-merge: object replaces existing primitive property": async () => {
47
+ const container = setup();
48
+ const state: any = createState({ value: 42 });
49
+ app(container, state, () => [DIV]);
50
+
51
+ state.patch({ value: { nested: true } });
52
+
53
+ await expect(state.value.nested).toEqual(true);
54
+ },
55
+
56
+ "patch-merge: new array property via patch": async () => {
57
+ const container = setup();
58
+ const state: any = createState({ name: "test" });
59
+ app(container, state, () => [DIV]);
60
+
61
+ state.patch({ tags: ["a", "b", "c"] });
62
+
63
+ await expect(Array.isArray(state.tags)).toEqual(true);
64
+ await expect(state.tags).toEqual(["a", "b", "c"]);
65
+ },
66
+ };
@@ -2,33 +2,33 @@ import { props, DIV, SPAN } from "../index";
2
2
  import { expect } from "./helper";
3
3
 
4
4
  export default {
5
- "props(): on vode with props returns props object": () => {
6
- expect(props([DIV, { class: "foo" }, "hello"]))
5
+ "props(): on vode with props returns props object": async () => {
6
+ await expect(props([DIV, { class: "foo" }, "hello"]))
7
7
  .toEqual({ class: "foo" });
8
8
  },
9
9
 
10
- "props(): on just-tag vode returns undefined": () => {
11
- expect(props([DIV])).toEqual(undefined);
10
+ "props(): on just-tag vode returns undefined": async () => {
11
+ await expect(props([DIV])).toEqual(undefined);
12
12
  },
13
13
 
14
- "props(): on text vode returns undefined": () => {
15
- expect(props("hello")).toEqual(undefined);
14
+ "props(): on text vode returns undefined": async () => {
15
+ await expect(props("hello")).toEqual(undefined);
16
16
  },
17
17
 
18
- "props(): on vode where second element is an array (child) returns undefined": () => {
19
- expect(props([DIV, [SPAN]] as any)).toEqual(undefined);
18
+ "props(): on vode where second element is an array (child) returns undefined": async () => {
19
+ await expect(props([DIV, [SPAN]] as any)).toEqual(undefined);
20
20
  },
21
21
 
22
- "props(): on vode where second element is null returns undefined": () => {
23
- expect(props([DIV, null as any, "hi"])).toEqual(undefined);
22
+ "props(): on vode where second element is null returns undefined": async () => {
23
+ await expect(props([DIV, null as any, "hi"])).toEqual(undefined);
24
24
  },
25
25
 
26
- "props(): on falsy input returns undefined": () => {
27
- expect(props(null as any)).toEqual(undefined);
28
- expect(props(undefined as any)).toEqual(undefined);
26
+ "props(): on falsy input returns undefined": async () => {
27
+ await expect(props(null as any)).toEqual(undefined);
28
+ await expect(props(undefined as any)).toEqual(undefined);
29
29
  },
30
30
 
31
- "props(): on vode with length 1 returns undefined": () => {
32
- expect(props([DIV] as any)).toEqual(undefined);
31
+ "props(): on vode with length 1 returns undefined": async () => {
32
+ await expect(props([DIV] as any)).toEqual(undefined);
33
33
  }
34
34
  };
@@ -1,106 +1,137 @@
1
1
  import { expect } from "./helper";
2
- import { context } from "../src/state-context";
2
+ import { context, ProxySubContext } from "../src/state-context";
3
3
  import { createState } from "../src/vode";
4
4
 
5
5
  export default {
6
- "StateContext.state: returns the state reference": () => {
6
+ "StateContext.state: returns the state reference": async () => {
7
7
  const state = createState({ x: 10 });
8
8
  const ctx = context(state);
9
9
 
10
- expect((ctx).state === state)
10
+ await expect((ctx).state === state)
11
11
  .toEqual(true);
12
12
  },
13
13
 
14
- "StateContext.get() returns whole state": () => {
14
+ "StateContext.get() returns whole state": async () => {
15
15
  const state = createState({ a: 1, b: 2 });
16
16
  const ctx = context(state);
17
17
 
18
- expect(ctx.get())
18
+ await expect(ctx.get())
19
19
  .toEqual({ a: 1, b: 2 });
20
20
  },
21
21
 
22
22
 
23
- "StateContext.get(): deep nested": () => {
23
+ "StateContext.get(): deep nested": async () => {
24
24
  const state = createState({ a: { b: { c: 42 } } });
25
25
  const ctx = context(state);
26
26
 
27
- expect(ctx.a.b.c.get())
27
+ await expect(ctx.a.b.c.get())
28
28
  .toEqual(42);
29
29
  },
30
30
 
31
- "StateContext.get(): missing nested path returns undefined": () => {
31
+ "StateContext.get(): missing nested path returns undefined": async () => {
32
32
  const state = createState({ a: {} });
33
33
  const ctx = context(state);
34
34
 
35
- expect((ctx.a as any).b.get())
35
+ await expect((ctx.a as any).b.get())
36
36
  .toEqual(undefined);
37
37
  },
38
38
 
39
- "StateContext.put(): silently mutates state": () => {
39
+ "StateContext.put(): silently mutates state": async () => {
40
40
  const state = createState({ a: { b: 1 } });
41
41
  const ctx = context(state);
42
42
  ctx.a.b.put(2);
43
43
 
44
- expect(state.a.b)
44
+ await expect(state.a.b)
45
45
  .toEqual(2);
46
46
  },
47
47
 
48
- "StateContext.put() on nested object replaces the sub-object": () => {
48
+ "StateContext.put() on nested object replaces the sub-object": async () => {
49
49
  const state = createState({ a: { b: { x: 1, y: 2 } } });
50
50
  const ctx = context(state);
51
51
  ctx.a.b.put({ y: 99 });
52
52
 
53
- expect(state.a.b)
53
+ await expect(state.a.b)
54
54
  .toEqual({ y: 99 });
55
55
  },
56
56
 
57
- "StateContext.put() at root level with empty keys": () => {
57
+ "StateContext.put() at root level with empty keys": async () => {
58
58
  const state = createState({ a: 1, b: 2 });
59
59
  const ctx = context(state);
60
60
  ctx.put({ b: undefined });
61
61
 
62
- expect(state)
62
+ await expect(state)
63
63
  .toEqual({ a: 1 });
64
64
  },
65
65
 
66
- "StateContext.patch(): calls state.patch with proper deep partial": () => {
66
+ "StateContext.patch(): calls state.patch with proper deep partial": async () => {
67
67
  const state = createState({ a: { b: 1 } });
68
68
  const ctx = context(state);
69
69
  ctx.a.b.patch(2);
70
70
 
71
71
  const patches = (state as any).patch.initialPatches;
72
72
 
73
- expect(patches.length)
73
+ await expect(patches.length)
74
74
  .toEqual(1);
75
- expect(patches[0])
75
+ await expect(patches[0])
76
76
  .toEqual({ a: { b: 2 } });
77
77
  },
78
78
 
79
- "StateContext.patch(): async wraps in array": () => {
79
+ "StateContext.patch(): async wraps in array": async () => {
80
80
  const state = createState({ a: { b: 1 } });
81
81
  const ctx = context(state);
82
82
  ctx.a.b.patch(2, true);
83
83
 
84
84
  const patches = (state as any).patch.initialPatches;
85
85
 
86
- expect(patches.length)
86
+ await expect(patches.length)
87
87
  .toEqual(1);
88
- expect(Array.isArray(patches[0]))
88
+ await expect(Array.isArray(patches[0]))
89
89
  .toEqual(true);
90
- expect(patches[0][0])
90
+ await expect(patches[0][0])
91
91
  .toEqual({ a: { b: 2 } });
92
92
  },
93
93
 
94
- "StateContext.patch() on nested deep path three levels": () => {
94
+ "StateContext.patch() on nested deep path three levels": async () => {
95
95
  const state = createState({ x: { y: { z: 0 } } });
96
96
  const ctx = context(state);
97
97
  ctx.x.y.z.patch(100);
98
98
 
99
99
  const patches = (state as any).patch.initialPatches;
100
100
 
101
- expect(patches.length)
101
+ await expect(patches.length)
102
102
  .toEqual(1);
103
- expect(patches[0])
103
+ await expect(patches[0])
104
104
  .toEqual({ x: { y: { z: 100 } } });
105
105
  },
106
+
107
+ "StateContext.put() with intermediate null creates objects along the path": async () => {
108
+ const state = createState({ a: null as { b: number } | null });
109
+ const ctx = context(state);
110
+
111
+ ctx.a.b.put(42);
112
+ await expect(state.a?.b).toEqual(42);
113
+
114
+ ctx.a.put(null);
115
+ await expect(state.a).toEqual(null);
116
+ await expect(state.a?.b).toEqual(undefined);
117
+ },
118
+
119
+ "StateContext.put() with three-level intermediate null": async () => {
120
+ const state = createState({ a: null as { b: { c: number } } | null });
121
+ const ctx = context(state);
122
+
123
+ ctx.a.b.c.put(99);
124
+
125
+ await expect(state.a?.b.c).toEqual(99);
126
+ },
127
+
128
+ "StateContext.put() with multiple intermediate nulls": async () => {
129
+ const state = createState({ a: { x: null as { z: string } | null, y: 1 } });
130
+ const ctx = context(state);
131
+
132
+ ctx.a.x.z.put("deep");
133
+
134
+ await expect(state.a.x?.z).toEqual("deep");
135
+ await expect(state.a.y).toEqual(1);
136
+ },
106
137
  };
package/test/tests-tag.ts CHANGED
@@ -2,32 +2,32 @@ import { tag, DIV, SPAN } from "../index";
2
2
  import { expect } from "./helper";
3
3
 
4
4
  export default {
5
- "tag(): on a vode returns the tag name": () => {
6
- expect(tag([DIV]))
5
+ "tag(): on a vode returns the tag name": async () => {
6
+ await expect(tag([DIV]))
7
7
  .toEqual("div");
8
8
  },
9
9
 
10
- "tag(): on a vode with props and children": () => {
11
- expect(tag([DIV, { class: "foo" }, [SPAN, "hi"]]))
10
+ "tag(): on a vode with props and children": async () => {
11
+ await expect(tag([DIV, { class: "foo" }, [SPAN, "hi"]]))
12
12
  .toEqual("div");
13
13
  },
14
14
 
15
- "tag(): on a text vode (string) returns #text": () => {
16
- expect(tag("hello"))
15
+ "tag(): on a text vode (string) returns #text": async () => {
16
+ await expect(tag("hello"))
17
17
  .toEqual("#text");
18
18
  },
19
19
 
20
- "tag(): on falsy values returns undefined": () => {
21
- expect(tag(null)).toEqual(undefined);
22
- expect(tag(undefined)).toEqual(undefined);
20
+ "tag(): on falsy values returns undefined": async () => {
21
+ await expect(tag(null)).toEqual(undefined);
22
+ await expect(tag(undefined)).toEqual(undefined);
23
23
  },
24
24
 
25
- "tag(): on no-vode values returns undefined": () => {
26
- expect(tag(0)).toEqual(undefined);
27
- expect(tag(true)).toEqual(undefined);
25
+ "tag(): on no-vode values returns undefined": async () => {
26
+ await expect(tag(0)).toEqual(undefined);
27
+ await expect(tag(true)).toEqual(undefined);
28
28
  },
29
29
 
30
- "tag(): on empty array returns undefined": () => {
31
- expect(tag([])).toEqual(undefined);
30
+ "tag(): on empty array returns undefined": async () => {
31
+ await expect(tag([])).toEqual(undefined);
32
32
  }
33
33
  };
@@ -2,14 +2,14 @@ import { vode, DIV, Vode, SPAN, STRONG } from "../index";
2
2
  import { expect } from "./helper";
3
3
 
4
4
  export default {
5
- "vode(): passing an already constructed vode returns it": () => {
5
+ "vode(): passing an already constructed vode returns it": async () => {
6
6
  const testVode: Vode = [DIV, { class: 'test' }, "hello world"];
7
7
 
8
- expect(vode(testVode)).toEqual(testVode);
8
+ await expect(vode(testVode)).toEqual(testVode);
9
9
  },
10
10
 
11
- "vode(): constructing a vode from parts": () => {
12
- expect(
11
+ "vode(): constructing a vode from parts": async () => {
12
+ await expect(
13
13
  vode(DIV, { class: 'test' },
14
14
  [SPAN, "hello"],
15
15
  [STRONG, { style: 'color: green' }, "world"])
@@ -20,8 +20,8 @@ export default {
20
20
  );
21
21
  },
22
22
 
23
- "vode(): passing an invalid tag fails": () => {
23
+ "vode(): passing an invalid tag fails": async () => {
24
24
  const err = expect(() => vode(null as any)).toFail();
25
- expect(err.message).toEqual("first argument to vode() must be a tag name or a vode");
25
+ await expect(err.message).toEqual("first argument to vode() must be a tag name or a vode");
26
26
  }
27
27
  };