rask-ui 0.1.0 → 0.2.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.
Files changed (234) hide show
  1. package/dist/component.d.ts +17 -0
  2. package/dist/component.d.ts.map +1 -1
  3. package/dist/component.js +4 -2
  4. package/dist/createAsync.d.ts +23 -0
  5. package/dist/createAsync.d.ts.map +1 -1
  6. package/dist/createAsync.js +23 -0
  7. package/dist/createAsync.test.d.ts +2 -0
  8. package/dist/createAsync.test.d.ts.map +1 -0
  9. package/dist/createAsync.test.js +110 -0
  10. package/dist/createContext.d.ts +26 -2
  11. package/dist/createContext.d.ts.map +1 -1
  12. package/dist/createContext.js +31 -5
  13. package/dist/createContext.test.d.ts +2 -0
  14. package/dist/createContext.test.d.ts.map +1 -0
  15. package/dist/createContext.test.js +136 -0
  16. package/dist/createMutation.d.ts +23 -0
  17. package/dist/createMutation.d.ts.map +1 -1
  18. package/dist/createMutation.js +23 -0
  19. package/dist/createMutation.test.d.ts +2 -0
  20. package/dist/createMutation.test.d.ts.map +1 -0
  21. package/dist/createMutation.test.js +168 -0
  22. package/dist/createQuery.d.ts +23 -0
  23. package/dist/createQuery.d.ts.map +1 -1
  24. package/dist/createQuery.js +23 -0
  25. package/dist/createQuery.test.d.ts +2 -0
  26. package/dist/createQuery.test.d.ts.map +1 -0
  27. package/dist/createQuery.test.js +156 -0
  28. package/dist/createRef.test.d.ts +2 -0
  29. package/dist/createRef.test.d.ts.map +1 -0
  30. package/dist/createRef.test.js +80 -0
  31. package/dist/createState.d.ts +24 -0
  32. package/dist/createState.d.ts.map +1 -1
  33. package/dist/createState.js +24 -0
  34. package/dist/createState.test.d.ts +2 -0
  35. package/dist/createState.test.d.ts.map +1 -0
  36. package/dist/createState.test.js +111 -0
  37. package/dist/createView.d.ts +54 -0
  38. package/dist/createView.d.ts.map +1 -0
  39. package/dist/createView.js +68 -0
  40. package/dist/createView.test.d.ts +2 -0
  41. package/dist/createView.test.d.ts.map +1 -0
  42. package/dist/createView.test.js +203 -0
  43. package/dist/error.d.ts.map +1 -1
  44. package/dist/error.js +15 -2
  45. package/dist/error.test.d.ts +2 -0
  46. package/dist/error.test.d.ts.map +1 -0
  47. package/dist/error.test.js +144 -0
  48. package/dist/index.d.ts +3 -2
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +3 -2
  51. package/dist/integration.test.d.ts +2 -0
  52. package/dist/integration.test.d.ts.map +1 -0
  53. package/dist/integration.test.js +155 -0
  54. package/dist/jsx-dev-runtime.d.ts +3 -3
  55. package/dist/jsx-dev-runtime.d.ts.map +1 -1
  56. package/dist/jsx-dev-runtime.js +2 -2
  57. package/dist/jsx-runtime.d.ts +1 -4
  58. package/dist/jsx-runtime.d.ts.map +1 -1
  59. package/dist/jsx-runtime.js +3 -14
  60. package/dist/observation.d.ts +1 -0
  61. package/dist/observation.d.ts.map +1 -1
  62. package/dist/observation.js +5 -0
  63. package/dist/observation.test.d.ts +2 -0
  64. package/dist/observation.test.d.ts.map +1 -0
  65. package/dist/observation.test.js +150 -0
  66. package/dist/render-test.d.ts +2 -0
  67. package/dist/render-test.d.ts.map +1 -0
  68. package/dist/render-test.js +21 -0
  69. package/dist/render.d.ts +1 -1
  70. package/dist/render.d.ts.map +1 -1
  71. package/dist/render.js +13 -1
  72. package/dist/test-setup.d.ts +16 -0
  73. package/dist/test-setup.d.ts.map +1 -0
  74. package/dist/test-setup.js +39 -0
  75. package/dist/tests/class.test.d.ts +2 -0
  76. package/dist/tests/class.test.d.ts.map +1 -0
  77. package/dist/tests/class.test.js +143 -0
  78. package/dist/tests/complex-rendering.test.d.ts +2 -0
  79. package/dist/tests/complex-rendering.test.d.ts.map +1 -0
  80. package/dist/tests/complex-rendering.test.js +400 -0
  81. package/dist/tests/component.cleanup.test.d.ts +2 -0
  82. package/dist/tests/component.cleanup.test.d.ts.map +1 -0
  83. package/dist/tests/component.cleanup.test.js +325 -0
  84. package/dist/tests/component.counter.test.d.ts +2 -0
  85. package/dist/tests/component.counter.test.d.ts.map +1 -0
  86. package/dist/tests/component.counter.test.js +124 -0
  87. package/dist/tests/component.interaction.test.d.ts +2 -0
  88. package/dist/tests/component.interaction.test.d.ts.map +1 -0
  89. package/dist/tests/component.interaction.test.js +73 -0
  90. package/dist/tests/component.props.test.d.ts +2 -0
  91. package/dist/tests/component.props.test.d.ts.map +1 -0
  92. package/dist/tests/component.props.test.js +88 -0
  93. package/dist/tests/component.return-types.test.d.ts +2 -0
  94. package/dist/tests/component.return-types.test.d.ts.map +1 -0
  95. package/dist/tests/component.return-types.test.js +357 -0
  96. package/dist/tests/component.state.test.d.ts +2 -0
  97. package/dist/tests/component.state.test.d.ts.map +1 -0
  98. package/dist/tests/component.state.test.js +129 -0
  99. package/dist/tests/component.test.d.ts +2 -0
  100. package/dist/tests/component.test.d.ts.map +1 -0
  101. package/dist/tests/component.test.js +63 -0
  102. package/dist/tests/createAsync.test.d.ts +2 -0
  103. package/dist/tests/createAsync.test.d.ts.map +1 -0
  104. package/dist/tests/createAsync.test.js +110 -0
  105. package/dist/tests/createContext.test.d.ts +2 -0
  106. package/dist/tests/createContext.test.d.ts.map +1 -0
  107. package/dist/tests/createContext.test.js +141 -0
  108. package/dist/tests/createMutation.test.d.ts +2 -0
  109. package/dist/tests/createMutation.test.d.ts.map +1 -0
  110. package/dist/tests/createMutation.test.js +168 -0
  111. package/dist/tests/createQuery.test.d.ts +2 -0
  112. package/dist/tests/createQuery.test.d.ts.map +1 -0
  113. package/dist/tests/createQuery.test.js +156 -0
  114. package/dist/tests/createRef.test.d.ts +2 -0
  115. package/dist/tests/createRef.test.d.ts.map +1 -0
  116. package/dist/tests/createRef.test.js +84 -0
  117. package/dist/tests/createState.test.d.ts +2 -0
  118. package/dist/tests/createState.test.d.ts.map +1 -0
  119. package/dist/tests/createState.test.js +111 -0
  120. package/dist/tests/createView.test.d.ts +2 -0
  121. package/dist/tests/createView.test.d.ts.map +1 -0
  122. package/dist/tests/createView.test.js +203 -0
  123. package/dist/tests/edge-cases.test.d.ts +2 -0
  124. package/dist/tests/edge-cases.test.d.ts.map +1 -0
  125. package/dist/tests/edge-cases.test.js +637 -0
  126. package/dist/tests/error-no-boundary.test.d.ts +2 -0
  127. package/dist/tests/error-no-boundary.test.d.ts.map +1 -0
  128. package/dist/tests/error-no-boundary.test.js +174 -0
  129. package/dist/tests/error.test.d.ts +2 -0
  130. package/dist/tests/error.test.d.ts.map +1 -0
  131. package/dist/tests/error.test.js +199 -0
  132. package/dist/tests/fragment.test.d.ts +2 -0
  133. package/dist/tests/fragment.test.d.ts.map +1 -0
  134. package/dist/tests/fragment.test.js +618 -0
  135. package/dist/tests/integration.test.d.ts +2 -0
  136. package/dist/tests/integration.test.d.ts.map +1 -0
  137. package/dist/tests/integration.test.js +192 -0
  138. package/dist/tests/keys.test.d.ts +2 -0
  139. package/dist/tests/keys.test.d.ts.map +1 -0
  140. package/dist/tests/keys.test.js +293 -0
  141. package/dist/tests/mount.test.d.ts +2 -0
  142. package/dist/tests/mount.test.d.ts.map +1 -0
  143. package/dist/tests/mount.test.js +91 -0
  144. package/dist/tests/observation.test.d.ts +2 -0
  145. package/dist/tests/observation.test.d.ts.map +1 -0
  146. package/dist/tests/observation.test.js +150 -0
  147. package/dist/tests/patch.test.d.ts +2 -0
  148. package/dist/tests/patch.test.d.ts.map +1 -0
  149. package/dist/tests/patch.test.js +498 -0
  150. package/dist/tests/patchChildren.test.d.ts +2 -0
  151. package/dist/tests/patchChildren.test.d.ts.map +1 -0
  152. package/dist/tests/patchChildren.test.js +387 -0
  153. package/dist/tests/primitives.test.d.ts +2 -0
  154. package/dist/tests/primitives.test.d.ts.map +1 -0
  155. package/dist/tests/primitives.test.js +132 -0
  156. package/dist/vdom/AbstractVNode.d.ts +22 -0
  157. package/dist/vdom/AbstractVNode.d.ts.map +1 -0
  158. package/dist/vdom/AbstractVNode.js +106 -0
  159. package/dist/vdom/ComponentVNode.d.ts +48 -0
  160. package/dist/vdom/ComponentVNode.d.ts.map +1 -0
  161. package/dist/vdom/ComponentVNode.js +209 -0
  162. package/dist/vdom/ElementVNode.d.ts +24 -0
  163. package/dist/vdom/ElementVNode.d.ts.map +1 -0
  164. package/dist/vdom/ElementVNode.js +126 -0
  165. package/dist/vdom/FragmentVNode.d.ts +13 -0
  166. package/dist/vdom/FragmentVNode.d.ts.map +1 -0
  167. package/dist/vdom/FragmentVNode.js +34 -0
  168. package/dist/vdom/RootVNode.d.ts +22 -0
  169. package/dist/vdom/RootVNode.d.ts.map +1 -0
  170. package/dist/vdom/RootVNode.js +55 -0
  171. package/dist/vdom/TextVNode.d.ts +11 -0
  172. package/dist/vdom/TextVNode.d.ts.map +1 -0
  173. package/dist/vdom/TextVNode.js +32 -0
  174. package/dist/vdom/class.test.d.ts +2 -0
  175. package/dist/vdom/class.test.d.ts.map +1 -0
  176. package/dist/vdom/class.test.js +143 -0
  177. package/dist/vdom/complex-rendering.test.d.ts +2 -0
  178. package/dist/vdom/complex-rendering.test.d.ts.map +1 -0
  179. package/dist/vdom/complex-rendering.test.js +400 -0
  180. package/dist/vdom/component.cleanup.test.d.ts +2 -0
  181. package/dist/vdom/component.cleanup.test.d.ts.map +1 -0
  182. package/dist/vdom/component.cleanup.test.js +323 -0
  183. package/dist/vdom/component.counter.test.d.ts +2 -0
  184. package/dist/vdom/component.counter.test.d.ts.map +1 -0
  185. package/dist/vdom/component.counter.test.js +124 -0
  186. package/dist/vdom/component.interaction.test.d.ts +2 -0
  187. package/dist/vdom/component.interaction.test.d.ts.map +1 -0
  188. package/dist/vdom/component.interaction.test.js +73 -0
  189. package/dist/vdom/component.props.test.d.ts +2 -0
  190. package/dist/vdom/component.props.test.d.ts.map +1 -0
  191. package/dist/vdom/component.props.test.js +88 -0
  192. package/dist/vdom/component.return-types.test.d.ts +2 -0
  193. package/dist/vdom/component.return-types.test.d.ts.map +1 -0
  194. package/dist/vdom/component.return-types.test.js +357 -0
  195. package/dist/vdom/component.state.test.d.ts +2 -0
  196. package/dist/vdom/component.state.test.d.ts.map +1 -0
  197. package/dist/vdom/component.state.test.js +129 -0
  198. package/dist/vdom/component.test.d.ts +2 -0
  199. package/dist/vdom/component.test.d.ts.map +1 -0
  200. package/dist/vdom/component.test.js +63 -0
  201. package/dist/vdom/dom-utils.d.ts +9 -0
  202. package/dist/vdom/dom-utils.d.ts.map +1 -0
  203. package/dist/vdom/dom-utils.js +74 -0
  204. package/dist/vdom/edge-cases.test.d.ts +2 -0
  205. package/dist/vdom/edge-cases.test.d.ts.map +1 -0
  206. package/dist/vdom/edge-cases.test.js +637 -0
  207. package/dist/vdom/fragment.test.d.ts +2 -0
  208. package/dist/vdom/fragment.test.d.ts.map +1 -0
  209. package/dist/vdom/fragment.test.js +618 -0
  210. package/dist/vdom/index.d.ts +10 -0
  211. package/dist/vdom/index.d.ts.map +1 -0
  212. package/dist/vdom/index.js +26 -0
  213. package/dist/vdom/keys.test.d.ts +2 -0
  214. package/dist/vdom/keys.test.d.ts.map +1 -0
  215. package/dist/vdom/keys.test.js +293 -0
  216. package/dist/vdom/mount.test.d.ts +2 -0
  217. package/dist/vdom/mount.test.d.ts.map +1 -0
  218. package/dist/vdom/mount.test.js +91 -0
  219. package/dist/vdom/patch.test.d.ts +2 -0
  220. package/dist/vdom/patch.test.d.ts.map +1 -0
  221. package/dist/vdom/patch.test.js +498 -0
  222. package/dist/vdom/patchChildren.test.d.ts +2 -0
  223. package/dist/vdom/patchChildren.test.d.ts.map +1 -0
  224. package/dist/vdom/patchChildren.test.js +392 -0
  225. package/dist/vdom/primitives.test.d.ts +2 -0
  226. package/dist/vdom/primitives.test.d.ts.map +1 -0
  227. package/dist/vdom/primitives.test.js +132 -0
  228. package/dist/vdom/types.d.ts +8 -0
  229. package/dist/vdom/types.d.ts.map +1 -0
  230. package/dist/vdom/types.js +1 -0
  231. package/dist/vdom/utils.d.ts +6 -0
  232. package/dist/vdom/utils.d.ts.map +1 -0
  233. package/dist/vdom/utils.js +63 -0
  234. package/package.json +1 -4
@@ -0,0 +1,150 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { Signal, Observer, getCurrentObserver } from "../observation";
3
+ describe("Signal", () => {
4
+ it("should allow subscribing to notifications", () => {
5
+ const signal = new Signal();
6
+ const callback = vi.fn();
7
+ signal.subscribe(callback);
8
+ signal.notify();
9
+ expect(callback).toHaveBeenCalledTimes(1);
10
+ });
11
+ it("should return a disposer function", () => {
12
+ const signal = new Signal();
13
+ const callback = vi.fn();
14
+ const dispose = signal.subscribe(callback);
15
+ dispose();
16
+ signal.notify();
17
+ expect(callback).not.toHaveBeenCalled();
18
+ });
19
+ it("should handle multiple subscribers", () => {
20
+ const signal = new Signal();
21
+ const callback1 = vi.fn();
22
+ const callback2 = vi.fn();
23
+ signal.subscribe(callback1);
24
+ signal.subscribe(callback2);
25
+ signal.notify();
26
+ expect(callback1).toHaveBeenCalledTimes(1);
27
+ expect(callback2).toHaveBeenCalledTimes(1);
28
+ });
29
+ it("should allow unsubscribing individual callbacks", () => {
30
+ const signal = new Signal();
31
+ const callback1 = vi.fn();
32
+ const callback2 = vi.fn();
33
+ const dispose1 = signal.subscribe(callback1);
34
+ signal.subscribe(callback2);
35
+ dispose1();
36
+ signal.notify();
37
+ expect(callback1).not.toHaveBeenCalled();
38
+ expect(callback2).toHaveBeenCalledTimes(1);
39
+ });
40
+ });
41
+ describe("Observer", () => {
42
+ it("should queue notifications in microtasks", async () => {
43
+ let callCount = 0;
44
+ const observer = new Observer(() => {
45
+ callCount++;
46
+ });
47
+ const signal = new Signal();
48
+ const dispose = observer.observe();
49
+ observer.subscribeSignal(signal);
50
+ dispose();
51
+ // Trigger multiple notifications
52
+ signal.notify();
53
+ signal.notify();
54
+ signal.notify();
55
+ // Should not be called synchronously
56
+ expect(callCount).toBe(0);
57
+ // Wait for microtask
58
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
59
+ // Should be called only once due to queuing
60
+ expect(callCount).toBe(1);
61
+ });
62
+ it("should track signals during observation", () => {
63
+ const callback = vi.fn();
64
+ const observer = new Observer(callback);
65
+ const signal = new Signal();
66
+ const dispose = observer.observe();
67
+ observer.subscribeSignal(signal);
68
+ dispose();
69
+ signal.notify();
70
+ return new Promise((resolve) => {
71
+ queueMicrotask(() => {
72
+ expect(callback).toHaveBeenCalledTimes(1);
73
+ resolve(undefined);
74
+ });
75
+ });
76
+ });
77
+ it("should clear signals when observing again", async () => {
78
+ let callCount = 0;
79
+ const observer = new Observer(() => {
80
+ callCount++;
81
+ });
82
+ const signal1 = new Signal();
83
+ const signal2 = new Signal();
84
+ // First observation
85
+ let dispose = observer.observe();
86
+ observer.subscribeSignal(signal1);
87
+ dispose();
88
+ // Second observation - should clear previous signals
89
+ dispose = observer.observe();
90
+ observer.subscribeSignal(signal2);
91
+ dispose();
92
+ // Notify first signal - should not trigger observer
93
+ signal1.notify();
94
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
95
+ expect(callCount).toBe(0);
96
+ // Notify second signal - should trigger observer
97
+ signal2.notify();
98
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
99
+ expect(callCount).toBe(1);
100
+ });
101
+ it("should dispose of all signal subscriptions", async () => {
102
+ const callback = vi.fn();
103
+ const observer = new Observer(callback);
104
+ const signal = new Signal();
105
+ const dispose = observer.observe();
106
+ observer.subscribeSignal(signal);
107
+ dispose();
108
+ observer.dispose();
109
+ signal.notify();
110
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
111
+ expect(callback).not.toHaveBeenCalled();
112
+ });
113
+ it("should set current observer during observation", () => {
114
+ const observer = new Observer(() => { });
115
+ expect(getCurrentObserver()).toBeUndefined();
116
+ const dispose = observer.observe();
117
+ expect(getCurrentObserver()).toBe(observer);
118
+ dispose();
119
+ expect(getCurrentObserver()).toBeUndefined();
120
+ });
121
+ it("should handle nested observations with stack", () => {
122
+ const observer1 = new Observer(() => { });
123
+ const observer2 = new Observer(() => { });
124
+ const dispose1 = observer1.observe();
125
+ expect(getCurrentObserver()).toBe(observer1);
126
+ const dispose2 = observer2.observe();
127
+ expect(getCurrentObserver()).toBe(observer2);
128
+ dispose2();
129
+ expect(getCurrentObserver()).toBe(observer1);
130
+ dispose1();
131
+ expect(getCurrentObserver()).toBeUndefined();
132
+ });
133
+ it("should prevent duplicate notifications while queued", async () => {
134
+ let callCount = 0;
135
+ const observer = new Observer(() => {
136
+ callCount++;
137
+ });
138
+ const signal = new Signal();
139
+ const dispose = observer.observe();
140
+ observer.subscribeSignal(signal);
141
+ dispose();
142
+ // Rapid-fire notifications
143
+ for (let i = 0; i < 100; i++) {
144
+ signal.notify();
145
+ }
146
+ await new Promise((resolve) => queueMicrotask(() => resolve()));
147
+ // Should only be called once
148
+ expect(callCount).toBe(1);
149
+ });
150
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=patch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch.test.d.ts","sourceRoot":"","sources":["../../src/tests/patch.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,498 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { jsx, render } from "../vdom/index";
3
+ import { createState } from "../createState";
4
+ describe("VDOM Patch", () => {
5
+ it("should patch a div element with a span element", async () => {
6
+ const container = document.createElement("div");
7
+ let stateFn;
8
+ const App = () => {
9
+ const state = createState({ useSpan: false });
10
+ stateFn = state;
11
+ return () => state.useSpan
12
+ ? jsx("span", { id: "patched" })
13
+ : jsx("div", { id: "initial" });
14
+ };
15
+ render(jsx(App, {}), container);
16
+ // Verify initial render
17
+ expect(container.children.length).toBe(1);
18
+ expect(container.children[0].tagName).toBe("DIV");
19
+ // Trigger patch by changing state
20
+ stateFn.useSpan = true;
21
+ await new Promise((resolve) => setTimeout(resolve, 0));
22
+ // Verify the element was replaced
23
+ expect(container.children.length).toBe(1);
24
+ expect(container.children[0].tagName).toBe("SPAN");
25
+ });
26
+ it("should patch element by adding children", async () => {
27
+ const container = document.createElement("div");
28
+ let stateFn;
29
+ const App = () => {
30
+ const state = createState({ hasChildren: false });
31
+ stateFn = state;
32
+ return () => state.hasChildren
33
+ ? jsx("div", {
34
+ id: "parent",
35
+ children: [
36
+ jsx("span", { id: "child1" }),
37
+ jsx("div", { id: "child2" }),
38
+ ],
39
+ })
40
+ : jsx("div", { id: "parent" });
41
+ };
42
+ render(jsx(App, {}), container);
43
+ // Verify initial state (no children)
44
+ const parent = container.children[0];
45
+ expect(parent.children.length).toBe(0);
46
+ // Trigger patch to add children
47
+ stateFn.hasChildren = true;
48
+ await new Promise((resolve) => setTimeout(resolve, 0));
49
+ // Verify children were added
50
+ expect(parent.children.length).toBe(2);
51
+ expect(parent.children[0].tagName).toBe("SPAN");
52
+ expect(parent.children[1].tagName).toBe("DIV");
53
+ });
54
+ it("should patch element by removing children", async () => {
55
+ const container = document.createElement("div");
56
+ let stateFn;
57
+ const App = () => {
58
+ const state = createState({ hasChildren: true });
59
+ stateFn = state;
60
+ return () => state.hasChildren
61
+ ? jsx("div", {
62
+ id: "parent",
63
+ children: [
64
+ jsx("span", { id: "child1" }),
65
+ jsx("div", { id: "child2" }),
66
+ jsx("span", { id: "child3" }),
67
+ ],
68
+ })
69
+ : jsx("div", { id: "parent" });
70
+ };
71
+ render(jsx(App, {}), container);
72
+ // Verify initial state (3 children)
73
+ const parent = container.children[0];
74
+ expect(parent.children.length).toBe(3);
75
+ // Trigger patch to remove children
76
+ stateFn.hasChildren = false;
77
+ await new Promise((resolve) => setTimeout(resolve, 0));
78
+ // Verify children were removed
79
+ expect(parent.children.length).toBe(0);
80
+ });
81
+ it("should patch element by changing child types", async () => {
82
+ const container = document.createElement("div");
83
+ let stateFn;
84
+ const App = () => {
85
+ const state = createState({ version: 1 });
86
+ stateFn = state;
87
+ return () => state.version === 1
88
+ ? jsx("div", {
89
+ id: "parent",
90
+ children: [
91
+ jsx("span", { id: "child1" }),
92
+ jsx("span", { id: "child2" }),
93
+ ],
94
+ })
95
+ : jsx("div", {
96
+ id: "parent",
97
+ children: [
98
+ jsx("div", { id: "child1" }),
99
+ jsx("button", { id: "child2" }),
100
+ ],
101
+ });
102
+ };
103
+ render(jsx(App, {}), container);
104
+ // Verify initial state (2 span children)
105
+ const parent = container.children[0];
106
+ expect(parent.children.length).toBe(2);
107
+ expect(parent.children[0].tagName).toBe("SPAN");
108
+ expect(parent.children[1].tagName).toBe("SPAN");
109
+ // Trigger patch to change child types
110
+ stateFn.version = 2;
111
+ await new Promise((resolve) => setTimeout(resolve, 0));
112
+ // Verify children types were changed
113
+ expect(parent.children.length).toBe(2);
114
+ expect(parent.children[0].tagName).toBe("DIV");
115
+ expect(parent.children[1].tagName).toBe("BUTTON");
116
+ });
117
+ it("should patch element by adding data/aria attributes", async () => {
118
+ const container = document.createElement("div");
119
+ let stateFn;
120
+ const App = () => {
121
+ const state = createState({ hasAttributes: false });
122
+ stateFn = state;
123
+ return () => state.hasAttributes
124
+ ? jsx("div", {
125
+ "data-testid": "test-component",
126
+ "aria-label": "Test element",
127
+ })
128
+ : jsx("div", {});
129
+ };
130
+ render(jsx(App, {}), container);
131
+ const div = container.children[0];
132
+ // Verify no attributes initially
133
+ expect(div.getAttribute("data-testid")).toBeNull();
134
+ expect(div.getAttribute("aria-label")).toBeNull();
135
+ // Trigger patch to add attributes
136
+ stateFn.hasAttributes = true;
137
+ await new Promise((resolve) => setTimeout(resolve, 0));
138
+ // Verify attributes were added
139
+ expect(div.getAttribute("data-testid")).toBe("test-component");
140
+ expect(div.getAttribute("aria-label")).toBe("Test element");
141
+ });
142
+ it("should patch element by removing data/aria attributes", async () => {
143
+ const container = document.createElement("div");
144
+ let stateFn;
145
+ const App = () => {
146
+ const state = createState({ hasAttributes: true });
147
+ stateFn = state;
148
+ return () => state.hasAttributes
149
+ ? jsx("div", {
150
+ "data-testid": "test-component",
151
+ "data-value": "123",
152
+ "aria-label": "Test element",
153
+ })
154
+ : jsx("div", {});
155
+ };
156
+ render(jsx(App, {}), container);
157
+ const div = container.children[0];
158
+ // Verify attributes exist
159
+ expect(div.getAttribute("data-testid")).toBe("test-component");
160
+ expect(div.getAttribute("data-value")).toBe("123");
161
+ expect(div.getAttribute("aria-label")).toBe("Test element");
162
+ // Trigger patch to remove attributes
163
+ stateFn.hasAttributes = false;
164
+ await new Promise((resolve) => setTimeout(resolve, 0));
165
+ // Verify attributes were removed
166
+ expect(div.getAttribute("data-testid")).toBeNull();
167
+ expect(div.getAttribute("data-value")).toBeNull();
168
+ expect(div.getAttribute("aria-label")).toBeNull();
169
+ });
170
+ it("should patch element by updating data/aria attributes", async () => {
171
+ const container = document.createElement("div");
172
+ let stateFn;
173
+ const App = () => {
174
+ const state = createState({ version: 1 });
175
+ stateFn = state;
176
+ return () => state.version === 1
177
+ ? jsx("div", {
178
+ "data-testid": "old-id",
179
+ "data-value": "old-value",
180
+ "aria-label": "Old label",
181
+ })
182
+ : jsx("div", {
183
+ "data-testid": "new-id",
184
+ "data-value": "new-value",
185
+ "aria-label": "New label",
186
+ });
187
+ };
188
+ render(jsx(App, {}), container);
189
+ const div = container.children[0];
190
+ // Verify initial attributes
191
+ expect(div.getAttribute("data-testid")).toBe("old-id");
192
+ expect(div.getAttribute("data-value")).toBe("old-value");
193
+ expect(div.getAttribute("aria-label")).toBe("Old label");
194
+ // Trigger patch to update attributes
195
+ stateFn.version = 2;
196
+ await new Promise((resolve) => setTimeout(resolve, 0));
197
+ // Verify attributes were updated
198
+ expect(div.getAttribute("data-testid")).toBe("new-id");
199
+ expect(div.getAttribute("data-value")).toBe("new-value");
200
+ expect(div.getAttribute("aria-label")).toBe("New label");
201
+ });
202
+ it("should patch element by updating event listeners", async () => {
203
+ const container = document.createElement("div");
204
+ let stateFn;
205
+ const oldHandler = vi.fn();
206
+ const newHandler = vi.fn();
207
+ const App = () => {
208
+ const state = createState({ version: 1 });
209
+ stateFn = state;
210
+ return () => jsx("button", {
211
+ onClick: state.version === 1 ? oldHandler : newHandler,
212
+ });
213
+ };
214
+ render(jsx(App, {}), container);
215
+ const button = container.children[0];
216
+ // Trigger click to verify old handler works
217
+ button.click();
218
+ expect(oldHandler).toHaveBeenCalledTimes(1);
219
+ expect(newHandler).toHaveBeenCalledTimes(0);
220
+ // Trigger patch to update event listener
221
+ stateFn.version = 2;
222
+ await new Promise((resolve) => setTimeout(resolve, 0));
223
+ // Trigger click again
224
+ button.click();
225
+ // Verify old handler was not called again and new handler was called
226
+ expect(oldHandler).toHaveBeenCalledTimes(1);
227
+ expect(newHandler).toHaveBeenCalledTimes(1);
228
+ });
229
+ it("should patch complex nested UI structure", async () => {
230
+ const container = document.createElement("div");
231
+ let stateFn;
232
+ const App = () => {
233
+ const state = createState({ version: 1 });
234
+ stateFn = state;
235
+ return () => state.version === 1
236
+ ? jsx("div", {
237
+ className: "app",
238
+ children: [
239
+ jsx("header", {
240
+ className: "header",
241
+ children: [
242
+ jsx("h1", { children: ["Title"] }),
243
+ jsx("nav", {
244
+ children: [
245
+ jsx("a", { href: "#home", children: ["Home"] }),
246
+ jsx("a", { href: "#about", children: ["About"] }),
247
+ ],
248
+ }),
249
+ ],
250
+ }),
251
+ jsx("main", {
252
+ className: "content",
253
+ children: [
254
+ jsx("p", { children: ["Paragraph 1"] }),
255
+ jsx("p", { children: ["Paragraph 2"] }),
256
+ ],
257
+ }),
258
+ ],
259
+ })
260
+ : jsx("div", {
261
+ className: "app-updated",
262
+ children: [
263
+ jsx("header", {
264
+ className: "header-updated",
265
+ children: [
266
+ jsx("h1", { children: ["New Title"] }),
267
+ jsx("nav", {
268
+ children: [
269
+ jsx("a", { href: "#home", children: ["Home"] }),
270
+ jsx("a", { href: "#about", children: ["About"] }),
271
+ jsx("a", { href: "#contact", children: ["Contact"] }),
272
+ ],
273
+ }),
274
+ ],
275
+ }),
276
+ jsx("main", {
277
+ className: "content",
278
+ children: [
279
+ jsx("p", { children: ["Updated Paragraph 1"] }),
280
+ jsx("div", { children: ["New div element"] }),
281
+ jsx("p", { children: ["Paragraph 3"] }),
282
+ ],
283
+ }),
284
+ ],
285
+ });
286
+ };
287
+ render(jsx(App, {}), container);
288
+ const app = container.children[0];
289
+ // Verify initial structure
290
+ expect(app.children.length).toBe(2);
291
+ expect(app.children[0].tagName).toBe("HEADER");
292
+ expect(app.children[1].tagName).toBe("MAIN");
293
+ const initialHeader = app.children[0];
294
+ expect(initialHeader.children.length).toBe(2);
295
+ expect(initialHeader.children[0].textContent).toBe("Title");
296
+ const initialNav = initialHeader.children[1];
297
+ expect(initialNav.children.length).toBe(2);
298
+ const initialMain = app.children[1];
299
+ expect(initialMain.children.length).toBe(2);
300
+ // Trigger patch
301
+ stateFn.version = 2;
302
+ await new Promise((resolve) => setTimeout(resolve, 0));
303
+ // Verify patched structure
304
+ expect(app.className).toBe("app-updated");
305
+ expect(app.children.length).toBe(2);
306
+ const patchedHeader = app.children[0];
307
+ expect(patchedHeader.className).toBe("header-updated");
308
+ expect(patchedHeader.children[0].textContent).toBe("New Title");
309
+ const patchedNav = patchedHeader.children[1];
310
+ expect(patchedNav.children.length).toBe(3);
311
+ expect(patchedNav.children[2].textContent).toBe("Contact");
312
+ const patchedMain = app.children[1];
313
+ expect(patchedMain.children.length).toBe(3);
314
+ expect(patchedMain.children[0].textContent).toBe("Updated Paragraph 1");
315
+ expect(patchedMain.children[1].tagName).toBe("DIV");
316
+ expect(patchedMain.children[2].textContent).toBe("Paragraph 3");
317
+ });
318
+ it("should patch nested elements independently", async () => {
319
+ const container = document.createElement("div");
320
+ let stateFn;
321
+ const App = () => {
322
+ const state = createState({ sidebarVersion: 1, contentVersion: 1 });
323
+ stateFn = state;
324
+ return () => jsx("div", {
325
+ className: "layout",
326
+ children: [
327
+ state.sidebarVersion === 1
328
+ ? jsx("aside", {
329
+ className: "sidebar",
330
+ children: [
331
+ jsx("div", { className: "widget", children: ["Widget 1"] }),
332
+ jsx("div", { className: "widget", children: ["Widget 2"] }),
333
+ ],
334
+ })
335
+ : jsx("aside", {
336
+ className: "sidebar-updated",
337
+ children: [
338
+ jsx("div", {
339
+ className: "widget",
340
+ children: ["Widget 1 Updated"],
341
+ }),
342
+ jsx("div", { className: "widget", children: ["Widget 2"] }),
343
+ jsx("div", { className: "widget", children: ["Widget 3"] }),
344
+ ],
345
+ }),
346
+ state.contentVersion === 1
347
+ ? jsx("main", {
348
+ children: [
349
+ jsx("article", {
350
+ children: [
351
+ jsx("h2", { children: ["Article Title"] }),
352
+ jsx("p", { children: ["Article content"] }),
353
+ ],
354
+ }),
355
+ ],
356
+ })
357
+ : jsx("main", {
358
+ children: [
359
+ jsx("article", {
360
+ children: [
361
+ jsx("h2", { children: ["Updated Article Title"] }),
362
+ jsx("p", { children: ["Updated article content"] }),
363
+ jsx("footer", { children: ["Article footer"] }),
364
+ ],
365
+ }),
366
+ ],
367
+ }),
368
+ ],
369
+ });
370
+ };
371
+ render(jsx(App, {}), container);
372
+ const layout = container.children[0];
373
+ const initialSidebar = layout.children[0];
374
+ const initialMain = layout.children[1];
375
+ // First patch: Update only the sidebar
376
+ stateFn.sidebarVersion = 2;
377
+ await new Promise((resolve) => setTimeout(resolve, 0));
378
+ // Verify only sidebar changed
379
+ expect(initialSidebar.className).toBe("sidebar-updated");
380
+ expect(initialSidebar.children.length).toBe(3);
381
+ expect(initialSidebar.children[0].textContent).toBe("Widget 1 Updated");
382
+ expect(initialSidebar.children[2].textContent).toBe("Widget 3");
383
+ // Main content should be unchanged
384
+ const article = initialMain.children[0];
385
+ expect(article.children[0].textContent).toBe("Article Title");
386
+ expect(article.children[1].textContent).toBe("Article content");
387
+ // Second patch: Update only the content
388
+ stateFn.contentVersion = 2;
389
+ await new Promise((resolve) => setTimeout(resolve, 0));
390
+ // Sidebar should remain from first patch
391
+ expect(initialSidebar.className).toBe("sidebar-updated");
392
+ expect(initialSidebar.children.length).toBe(3);
393
+ // Main content should now be updated
394
+ const updatedArticle = initialMain.children[0];
395
+ expect(updatedArticle.children.length).toBe(3);
396
+ expect(updatedArticle.children[0].textContent).toBe("Updated Article Title");
397
+ expect(updatedArticle.children[1].textContent).toBe("Updated article content");
398
+ expect(updatedArticle.children[2].tagName).toBe("FOOTER");
399
+ });
400
+ it("should handle multiple sequential patches", async () => {
401
+ const container = document.createElement("div");
402
+ let stateFn;
403
+ const App = () => {
404
+ const state = createState({ count: 0 });
405
+ stateFn = state;
406
+ return () => jsx("div", {
407
+ className: "counter",
408
+ children: [
409
+ jsx("span", { children: [`Count: ${state.count}`] }),
410
+ jsx("button", {
411
+ children: [state.count < 3 ? "Increment" : "Reset"],
412
+ }),
413
+ ],
414
+ });
415
+ };
416
+ render(jsx(App, {}), container);
417
+ const counterDiv = container.children[0];
418
+ // Verify initial state
419
+ expect(counterDiv.children[0].textContent).toBe("Count: 0");
420
+ // Patch 1: Update count to 1
421
+ stateFn.count = 1;
422
+ await new Promise((resolve) => setTimeout(resolve, 0));
423
+ expect(counterDiv.children[0].textContent).toBe("Count: 1");
424
+ // Patch 2: Update count to 2
425
+ stateFn.count = 2;
426
+ await new Promise((resolve) => setTimeout(resolve, 0));
427
+ expect(counterDiv.children[0].textContent).toBe("Count: 2");
428
+ // Patch 3: Update count to 3 and change button text
429
+ stateFn.count = 3;
430
+ await new Promise((resolve) => setTimeout(resolve, 0));
431
+ expect(counterDiv.children[0].textContent).toBe("Count: 3");
432
+ expect(counterDiv.children[1].textContent).toBe("Reset");
433
+ // Patch 4: Reset to 0 and add a new element
434
+ stateFn.count = 0;
435
+ await new Promise((resolve) => setTimeout(resolve, 0));
436
+ expect(counterDiv.children[0].textContent).toBe("Count: 0");
437
+ expect(counterDiv.children[1].textContent).toBe("Increment");
438
+ });
439
+ it("should patch deeply nested child independently", async () => {
440
+ const container = document.createElement("div");
441
+ let stateFn;
442
+ const App = () => {
443
+ const state = createState({ version: 1 });
444
+ stateFn = state;
445
+ return () => jsx("div", {
446
+ className: "root",
447
+ children: [
448
+ jsx("div", {
449
+ className: "level-1",
450
+ children: [
451
+ jsx("div", {
452
+ className: "level-2",
453
+ children: [
454
+ jsx("div", {
455
+ className: "level-3",
456
+ children: [
457
+ jsx("span", {
458
+ children: [
459
+ state.version === 1
460
+ ? "Deep content"
461
+ : "Updated deep content",
462
+ ],
463
+ id: state.version === 1 ? "target" : "target-updated",
464
+ }),
465
+ ],
466
+ }),
467
+ jsx("div", { children: ["Sibling at level-3"] }),
468
+ ],
469
+ }),
470
+ jsx("div", { children: ["Sibling at level-2"] }),
471
+ ],
472
+ }),
473
+ jsx("div", { children: ["Sibling at level-1"] }),
474
+ ],
475
+ });
476
+ };
477
+ render(jsx(App, {}), container);
478
+ const root = container.children[0];
479
+ // Navigate to the deep element
480
+ const level1 = root.children[0];
481
+ const level2 = level1.children[0];
482
+ const level3 = level2.children[0];
483
+ const targetSpan = level3.children[0];
484
+ // Verify initial state
485
+ expect(targetSpan.textContent).toBe("Deep content");
486
+ expect(targetSpan.id).toBe("target");
487
+ // Trigger patch
488
+ stateFn.version = 2;
489
+ await new Promise((resolve) => setTimeout(resolve, 0));
490
+ // Verify the deep element was updated
491
+ expect(targetSpan.textContent).toBe("Updated deep content");
492
+ expect(targetSpan.id).toBe("target-updated");
493
+ // Verify siblings remained unchanged
494
+ expect(level2.children[1].textContent).toBe("Sibling at level-3");
495
+ expect(level1.children[1].textContent).toBe("Sibling at level-2");
496
+ expect(root.children[1].textContent).toBe("Sibling at level-1");
497
+ });
498
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=patchChildren.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patchChildren.test.d.ts","sourceRoot":"","sources":["../../src/tests/patchChildren.test.ts"],"names":[],"mappings":""}