rask-ui 0.1.1 → 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 (212) hide show
  1. package/dist/component.d.ts +17 -0
  2. package/dist/component.d.ts.map +1 -1
  3. package/dist/createAsync.d.ts +23 -0
  4. package/dist/createAsync.d.ts.map +1 -1
  5. package/dist/createAsync.js +23 -0
  6. package/dist/createContext.d.ts +26 -2
  7. package/dist/createContext.d.ts.map +1 -1
  8. package/dist/createContext.js +31 -5
  9. package/dist/createContext.test.d.ts +2 -0
  10. package/dist/createContext.test.d.ts.map +1 -0
  11. package/dist/createContext.test.js +136 -0
  12. package/dist/createMutation.d.ts +23 -0
  13. package/dist/createMutation.d.ts.map +1 -1
  14. package/dist/createMutation.js +23 -0
  15. package/dist/createQuery.d.ts +23 -0
  16. package/dist/createQuery.d.ts.map +1 -1
  17. package/dist/createQuery.js +23 -0
  18. package/dist/createRef.test.d.ts +2 -0
  19. package/dist/createRef.test.d.ts.map +1 -0
  20. package/dist/createRef.test.js +80 -0
  21. package/dist/createState.d.ts +24 -0
  22. package/dist/createState.d.ts.map +1 -1
  23. package/dist/createState.js +24 -0
  24. package/dist/createView.d.ts +54 -0
  25. package/dist/createView.d.ts.map +1 -0
  26. package/dist/createView.js +68 -0
  27. package/dist/createView.test.d.ts +2 -0
  28. package/dist/createView.test.d.ts.map +1 -0
  29. package/dist/createView.test.js +203 -0
  30. package/dist/error.d.ts.map +1 -1
  31. package/dist/error.js +4 -5
  32. package/dist/error.test.d.ts +2 -0
  33. package/dist/error.test.d.ts.map +1 -0
  34. package/dist/error.test.js +144 -0
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +3 -2
  38. package/dist/integration.test.d.ts +2 -0
  39. package/dist/integration.test.d.ts.map +1 -0
  40. package/dist/integration.test.js +155 -0
  41. package/dist/jsx-dev-runtime.d.ts +3 -3
  42. package/dist/jsx-dev-runtime.d.ts.map +1 -1
  43. package/dist/jsx-dev-runtime.js +2 -2
  44. package/dist/jsx-runtime.d.ts +1 -4
  45. package/dist/jsx-runtime.d.ts.map +1 -1
  46. package/dist/jsx-runtime.js +3 -14
  47. package/dist/observation.d.ts +1 -0
  48. package/dist/observation.d.ts.map +1 -1
  49. package/dist/observation.js +5 -0
  50. package/dist/test-setup.d.ts +3 -3
  51. package/dist/test-setup.d.ts.map +1 -1
  52. package/dist/test-setup.js +7 -8
  53. package/dist/tests/class.test.d.ts +2 -0
  54. package/dist/tests/class.test.d.ts.map +1 -0
  55. package/dist/tests/class.test.js +143 -0
  56. package/dist/tests/complex-rendering.test.d.ts +2 -0
  57. package/dist/tests/complex-rendering.test.d.ts.map +1 -0
  58. package/dist/tests/complex-rendering.test.js +400 -0
  59. package/dist/tests/component.cleanup.test.d.ts +2 -0
  60. package/dist/tests/component.cleanup.test.d.ts.map +1 -0
  61. package/dist/tests/component.cleanup.test.js +325 -0
  62. package/dist/tests/component.counter.test.d.ts +2 -0
  63. package/dist/tests/component.counter.test.d.ts.map +1 -0
  64. package/dist/tests/component.counter.test.js +124 -0
  65. package/dist/tests/component.interaction.test.d.ts +2 -0
  66. package/dist/tests/component.interaction.test.d.ts.map +1 -0
  67. package/dist/tests/component.interaction.test.js +73 -0
  68. package/dist/tests/component.props.test.d.ts +2 -0
  69. package/dist/tests/component.props.test.d.ts.map +1 -0
  70. package/dist/tests/component.props.test.js +88 -0
  71. package/dist/tests/component.return-types.test.d.ts +2 -0
  72. package/dist/tests/component.return-types.test.d.ts.map +1 -0
  73. package/dist/tests/component.return-types.test.js +357 -0
  74. package/dist/tests/component.state.test.d.ts +2 -0
  75. package/dist/tests/component.state.test.d.ts.map +1 -0
  76. package/dist/tests/component.state.test.js +129 -0
  77. package/dist/tests/component.test.d.ts +2 -0
  78. package/dist/tests/component.test.d.ts.map +1 -0
  79. package/dist/tests/component.test.js +63 -0
  80. package/dist/tests/createAsync.test.d.ts +2 -0
  81. package/dist/tests/createAsync.test.d.ts.map +1 -0
  82. package/dist/tests/createAsync.test.js +110 -0
  83. package/dist/tests/createContext.test.d.ts +2 -0
  84. package/dist/tests/createContext.test.d.ts.map +1 -0
  85. package/dist/tests/createContext.test.js +141 -0
  86. package/dist/tests/createMutation.test.d.ts +2 -0
  87. package/dist/tests/createMutation.test.d.ts.map +1 -0
  88. package/dist/tests/createMutation.test.js +168 -0
  89. package/dist/tests/createQuery.test.d.ts +2 -0
  90. package/dist/tests/createQuery.test.d.ts.map +1 -0
  91. package/dist/tests/createQuery.test.js +156 -0
  92. package/dist/tests/createRef.test.d.ts +2 -0
  93. package/dist/tests/createRef.test.d.ts.map +1 -0
  94. package/dist/tests/createRef.test.js +84 -0
  95. package/dist/tests/createState.test.d.ts +2 -0
  96. package/dist/tests/createState.test.d.ts.map +1 -0
  97. package/dist/tests/createState.test.js +111 -0
  98. package/dist/tests/createView.test.d.ts +2 -0
  99. package/dist/tests/createView.test.d.ts.map +1 -0
  100. package/dist/tests/createView.test.js +203 -0
  101. package/dist/tests/edge-cases.test.d.ts +2 -0
  102. package/dist/tests/edge-cases.test.d.ts.map +1 -0
  103. package/dist/tests/edge-cases.test.js +637 -0
  104. package/dist/tests/error-no-boundary.test.d.ts +2 -0
  105. package/dist/tests/error-no-boundary.test.d.ts.map +1 -0
  106. package/dist/tests/error-no-boundary.test.js +174 -0
  107. package/dist/tests/error.test.d.ts +2 -0
  108. package/dist/tests/error.test.d.ts.map +1 -0
  109. package/dist/tests/error.test.js +199 -0
  110. package/dist/tests/fragment.test.d.ts +2 -0
  111. package/dist/tests/fragment.test.d.ts.map +1 -0
  112. package/dist/tests/fragment.test.js +618 -0
  113. package/dist/tests/integration.test.d.ts +2 -0
  114. package/dist/tests/integration.test.d.ts.map +1 -0
  115. package/dist/tests/integration.test.js +192 -0
  116. package/dist/tests/keys.test.d.ts +2 -0
  117. package/dist/tests/keys.test.d.ts.map +1 -0
  118. package/dist/tests/keys.test.js +293 -0
  119. package/dist/tests/mount.test.d.ts +2 -0
  120. package/dist/tests/mount.test.d.ts.map +1 -0
  121. package/dist/tests/mount.test.js +91 -0
  122. package/dist/tests/observation.test.d.ts +2 -0
  123. package/dist/tests/observation.test.d.ts.map +1 -0
  124. package/dist/tests/observation.test.js +150 -0
  125. package/dist/tests/patch.test.d.ts +2 -0
  126. package/dist/tests/patch.test.d.ts.map +1 -0
  127. package/dist/tests/patch.test.js +498 -0
  128. package/dist/tests/patchChildren.test.d.ts +2 -0
  129. package/dist/tests/patchChildren.test.d.ts.map +1 -0
  130. package/dist/tests/patchChildren.test.js +387 -0
  131. package/dist/tests/primitives.test.d.ts +2 -0
  132. package/dist/tests/primitives.test.d.ts.map +1 -0
  133. package/dist/tests/primitives.test.js +132 -0
  134. package/dist/vdom/AbstractVNode.d.ts +22 -0
  135. package/dist/vdom/AbstractVNode.d.ts.map +1 -0
  136. package/dist/vdom/AbstractVNode.js +106 -0
  137. package/dist/vdom/ComponentVNode.d.ts +48 -0
  138. package/dist/vdom/ComponentVNode.d.ts.map +1 -0
  139. package/dist/vdom/ComponentVNode.js +209 -0
  140. package/dist/vdom/ElementVNode.d.ts +24 -0
  141. package/dist/vdom/ElementVNode.d.ts.map +1 -0
  142. package/dist/vdom/ElementVNode.js +126 -0
  143. package/dist/vdom/FragmentVNode.d.ts +13 -0
  144. package/dist/vdom/FragmentVNode.d.ts.map +1 -0
  145. package/dist/vdom/FragmentVNode.js +34 -0
  146. package/dist/vdom/RootVNode.d.ts +22 -0
  147. package/dist/vdom/RootVNode.d.ts.map +1 -0
  148. package/dist/vdom/RootVNode.js +55 -0
  149. package/dist/vdom/TextVNode.d.ts +11 -0
  150. package/dist/vdom/TextVNode.d.ts.map +1 -0
  151. package/dist/vdom/TextVNode.js +32 -0
  152. package/dist/vdom/class.test.d.ts +2 -0
  153. package/dist/vdom/class.test.d.ts.map +1 -0
  154. package/dist/vdom/class.test.js +143 -0
  155. package/dist/vdom/complex-rendering.test.d.ts +2 -0
  156. package/dist/vdom/complex-rendering.test.d.ts.map +1 -0
  157. package/dist/vdom/complex-rendering.test.js +400 -0
  158. package/dist/vdom/component.cleanup.test.d.ts +2 -0
  159. package/dist/vdom/component.cleanup.test.d.ts.map +1 -0
  160. package/dist/vdom/component.cleanup.test.js +323 -0
  161. package/dist/vdom/component.counter.test.d.ts +2 -0
  162. package/dist/vdom/component.counter.test.d.ts.map +1 -0
  163. package/dist/vdom/component.counter.test.js +124 -0
  164. package/dist/vdom/component.interaction.test.d.ts +2 -0
  165. package/dist/vdom/component.interaction.test.d.ts.map +1 -0
  166. package/dist/vdom/component.interaction.test.js +73 -0
  167. package/dist/vdom/component.props.test.d.ts +2 -0
  168. package/dist/vdom/component.props.test.d.ts.map +1 -0
  169. package/dist/vdom/component.props.test.js +88 -0
  170. package/dist/vdom/component.return-types.test.d.ts +2 -0
  171. package/dist/vdom/component.return-types.test.d.ts.map +1 -0
  172. package/dist/vdom/component.return-types.test.js +357 -0
  173. package/dist/vdom/component.state.test.d.ts +2 -0
  174. package/dist/vdom/component.state.test.d.ts.map +1 -0
  175. package/dist/vdom/component.state.test.js +129 -0
  176. package/dist/vdom/component.test.d.ts +2 -0
  177. package/dist/vdom/component.test.d.ts.map +1 -0
  178. package/dist/vdom/component.test.js +63 -0
  179. package/dist/vdom/dom-utils.d.ts +9 -0
  180. package/dist/vdom/dom-utils.d.ts.map +1 -0
  181. package/dist/vdom/dom-utils.js +74 -0
  182. package/dist/vdom/edge-cases.test.d.ts +2 -0
  183. package/dist/vdom/edge-cases.test.d.ts.map +1 -0
  184. package/dist/vdom/edge-cases.test.js +637 -0
  185. package/dist/vdom/fragment.test.d.ts +2 -0
  186. package/dist/vdom/fragment.test.d.ts.map +1 -0
  187. package/dist/vdom/fragment.test.js +618 -0
  188. package/dist/vdom/index.d.ts +10 -0
  189. package/dist/vdom/index.d.ts.map +1 -0
  190. package/dist/vdom/index.js +26 -0
  191. package/dist/vdom/keys.test.d.ts +2 -0
  192. package/dist/vdom/keys.test.d.ts.map +1 -0
  193. package/dist/vdom/keys.test.js +293 -0
  194. package/dist/vdom/mount.test.d.ts +2 -0
  195. package/dist/vdom/mount.test.d.ts.map +1 -0
  196. package/dist/vdom/mount.test.js +91 -0
  197. package/dist/vdom/patch.test.d.ts +2 -0
  198. package/dist/vdom/patch.test.d.ts.map +1 -0
  199. package/dist/vdom/patch.test.js +498 -0
  200. package/dist/vdom/patchChildren.test.d.ts +2 -0
  201. package/dist/vdom/patchChildren.test.d.ts.map +1 -0
  202. package/dist/vdom/patchChildren.test.js +392 -0
  203. package/dist/vdom/primitives.test.d.ts +2 -0
  204. package/dist/vdom/primitives.test.d.ts.map +1 -0
  205. package/dist/vdom/primitives.test.js +132 -0
  206. package/dist/vdom/types.d.ts +8 -0
  207. package/dist/vdom/types.d.ts.map +1 -0
  208. package/dist/vdom/types.js +1 -0
  209. package/dist/vdom/utils.d.ts +6 -0
  210. package/dist/vdom/utils.d.ts.map +1 -0
  211. package/dist/vdom/utils.js +63 -0
  212. package/package.json +1 -4
@@ -0,0 +1,618 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { jsx, render } from "./index";
3
+ import { Fragment } from "./FragmentVNode";
4
+ import { createState } from "../createState";
5
+ describe("VDOM Fragments", () => {
6
+ describe("Mounting", () => {
7
+ it("should mount a simple fragment with multiple children", () => {
8
+ const container = document.createElement("div");
9
+ const vnode = jsx(Fragment, {
10
+ children: [
11
+ jsx("span", { children: ["First"] }),
12
+ jsx("span", { children: ["Second"] }),
13
+ jsx("span", { children: ["Third"] }),
14
+ ],
15
+ });
16
+ render(vnode, container);
17
+ expect(container.children.length).toBe(3);
18
+ expect(container.children[0].tagName).toBe("SPAN");
19
+ expect(container.children[0].textContent).toBe("First");
20
+ expect(container.children[1].tagName).toBe("SPAN");
21
+ expect(container.children[1].textContent).toBe("Second");
22
+ expect(container.children[2].tagName).toBe("SPAN");
23
+ expect(container.children[2].textContent).toBe("Third");
24
+ });
25
+ it("should mount fragment inside an element", () => {
26
+ const container = document.createElement("div");
27
+ const vnode = jsx("div", {
28
+ className: "wrapper",
29
+ children: [
30
+ jsx(Fragment, {
31
+ children: [
32
+ jsx("span", { children: ["A"] }),
33
+ jsx("span", { children: ["B"] }),
34
+ ],
35
+ }),
36
+ ],
37
+ });
38
+ render(vnode, container);
39
+ const wrapper = container.children[0];
40
+ expect(wrapper.className).toBe("wrapper");
41
+ expect(wrapper.children.length).toBe(2);
42
+ expect(wrapper.children[0].textContent).toBe("A");
43
+ expect(wrapper.children[1].textContent).toBe("B");
44
+ });
45
+ it("should mount nested fragments", () => {
46
+ const container = document.createElement("div");
47
+ const vnode = jsx(Fragment, {
48
+ children: [
49
+ jsx("span", { children: ["Before"] }),
50
+ jsx(Fragment, {
51
+ children: [
52
+ jsx("span", { children: ["Nested 1"] }),
53
+ jsx("span", { children: ["Nested 2"] }),
54
+ ],
55
+ }),
56
+ jsx("span", { children: ["After"] }),
57
+ ],
58
+ });
59
+ render(vnode, container);
60
+ expect(container.children.length).toBe(4);
61
+ expect(container.children[0].textContent).toBe("Before");
62
+ expect(container.children[1].textContent).toBe("Nested 1");
63
+ expect(container.children[2].textContent).toBe("Nested 2");
64
+ expect(container.children[3].textContent).toBe("After");
65
+ });
66
+ it("should mount deeply nested fragments", () => {
67
+ const container = document.createElement("div");
68
+ const vnode = jsx(Fragment, {
69
+ children: [
70
+ jsx(Fragment, {
71
+ children: [
72
+ jsx(Fragment, {
73
+ children: [
74
+ jsx("span", { children: ["Deep 1"] }),
75
+ jsx("span", { children: ["Deep 2"] }),
76
+ ],
77
+ }),
78
+ jsx("span", { children: ["Middle"] }),
79
+ ],
80
+ }),
81
+ jsx("span", { children: ["Outer"] }),
82
+ ],
83
+ });
84
+ render(vnode, container);
85
+ expect(container.children.length).toBe(4);
86
+ expect(container.children[0].textContent).toBe("Deep 1");
87
+ expect(container.children[1].textContent).toBe("Deep 2");
88
+ expect(container.children[2].textContent).toBe("Middle");
89
+ expect(container.children[3].textContent).toBe("Outer");
90
+ });
91
+ it("should mount fragment with mixed element types", () => {
92
+ const container = document.createElement("div");
93
+ const vnode = jsx(Fragment, {
94
+ children: [
95
+ jsx("div", { children: ["Div"] }),
96
+ jsx("span", { children: ["Span"] }),
97
+ jsx("button", { children: ["Button"] }),
98
+ ],
99
+ });
100
+ render(vnode, container);
101
+ expect(container.children.length).toBe(3);
102
+ expect(container.children[0].tagName).toBe("DIV");
103
+ expect(container.children[1].tagName).toBe("SPAN");
104
+ expect(container.children[2].tagName).toBe("BUTTON");
105
+ });
106
+ });
107
+ describe("Patching", () => {
108
+ beforeEach(() => {
109
+ vi.useFakeTimers();
110
+ });
111
+ afterEach(() => {
112
+ vi.clearAllTimers();
113
+ });
114
+ it("should patch fragment by adding children", async () => {
115
+ const container = document.createElement("div");
116
+ let stateFn;
117
+ const App = () => {
118
+ const state = createState({ childCount: 1 });
119
+ stateFn = state;
120
+ return () => {
121
+ const children = [];
122
+ for (let i = 0; i < state.childCount; i++) {
123
+ const labels = ["First", "Second", "Third"];
124
+ children.push(jsx("span", { children: [labels[i]] }));
125
+ }
126
+ return jsx(Fragment, { children });
127
+ };
128
+ };
129
+ render(jsx(App, {}), container);
130
+ expect(container.children.length).toBe(1);
131
+ expect(container.children[0].textContent).toBe("First");
132
+ stateFn.childCount = 3;
133
+ await vi.runAllTimersAsync();
134
+ expect(container.children.length).toBe(3);
135
+ expect(container.children[0].textContent).toBe("First");
136
+ expect(container.children[1].textContent).toBe("Second");
137
+ expect(container.children[2].textContent).toBe("Third");
138
+ });
139
+ it("should patch fragment by removing children", async () => {
140
+ const container = document.createElement("div");
141
+ let stateFn;
142
+ const App = () => {
143
+ const state = createState({ childCount: 3 });
144
+ stateFn = state;
145
+ return () => {
146
+ const children = [];
147
+ for (let i = 0; i < state.childCount; i++) {
148
+ const labels = ["First", "Second", "Third"];
149
+ children.push(jsx("span", { children: [labels[i]] }));
150
+ }
151
+ return jsx(Fragment, { children });
152
+ };
153
+ };
154
+ render(jsx(App, {}), container);
155
+ expect(container.children.length).toBe(3);
156
+ stateFn.childCount = 1;
157
+ await vi.runAllTimersAsync();
158
+ expect(container.children.length).toBe(1);
159
+ expect(container.children[0].textContent).toBe("First");
160
+ });
161
+ it("should patch fragment by replacing children", async () => {
162
+ const container = document.createElement("div");
163
+ let stateFn;
164
+ const App = () => {
165
+ const state = createState({ useDiv: false });
166
+ stateFn = state;
167
+ return () => {
168
+ if (state.useDiv) {
169
+ return jsx(Fragment, {
170
+ children: [
171
+ jsx("div", { children: ["New 1"] }),
172
+ jsx("div", { children: ["New 2"] }),
173
+ ],
174
+ });
175
+ }
176
+ return jsx(Fragment, {
177
+ children: [
178
+ jsx("span", { children: ["Old 1"] }),
179
+ jsx("span", { children: ["Old 2"] }),
180
+ ],
181
+ });
182
+ };
183
+ };
184
+ render(jsx(App, {}), container);
185
+ expect(container.children.length).toBe(2);
186
+ expect(container.children[0].tagName).toBe("SPAN");
187
+ stateFn.useDiv = true;
188
+ await vi.runAllTimersAsync();
189
+ expect(container.children.length).toBe(2);
190
+ expect(container.children[0].tagName).toBe("DIV");
191
+ expect(container.children[0].textContent).toBe("New 1");
192
+ expect(container.children[1].tagName).toBe("DIV");
193
+ expect(container.children[1].textContent).toBe("New 2");
194
+ });
195
+ it("should patch fragment inside an element", async () => {
196
+ const container = document.createElement("div");
197
+ let stateFn;
198
+ const App = () => {
199
+ const state = createState({ showC: false });
200
+ stateFn = state;
201
+ return () => jsx("div", {
202
+ children: [
203
+ jsx(Fragment, {
204
+ children: [
205
+ jsx("span", { children: ["A"] }),
206
+ jsx("span", { children: ["B"] }),
207
+ ...(state.showC ? [jsx("span", { children: ["C"] })] : []),
208
+ ],
209
+ }),
210
+ ],
211
+ });
212
+ };
213
+ render(jsx(App, {}), container);
214
+ const wrapper = container.children[0];
215
+ expect(wrapper.children.length).toBe(2);
216
+ stateFn.showC = true;
217
+ await vi.runAllTimersAsync();
218
+ expect(wrapper.children.length).toBe(3);
219
+ expect(wrapper.children[0].textContent).toBe("A");
220
+ expect(wrapper.children[1].textContent).toBe("B");
221
+ expect(wrapper.children[2].textContent).toBe("C");
222
+ });
223
+ it("should patch nested fragments", async () => {
224
+ const container = document.createElement("div");
225
+ let stateFn;
226
+ const App = () => {
227
+ const state = createState({ showInner2: false });
228
+ stateFn = state;
229
+ return () => jsx(Fragment, {
230
+ children: [
231
+ jsx("span", { children: ["Outer"] }),
232
+ jsx(Fragment, {
233
+ children: [
234
+ jsx("span", { children: ["Inner 1"] }),
235
+ ...(state.showInner2
236
+ ? [jsx("span", { children: ["Inner 2"] })]
237
+ : []),
238
+ ],
239
+ }),
240
+ ],
241
+ });
242
+ };
243
+ render(jsx(App, {}), container);
244
+ expect(container.children.length).toBe(2);
245
+ stateFn.showInner2 = true;
246
+ await vi.runAllTimersAsync();
247
+ expect(container.children.length).toBe(3);
248
+ expect(container.children[0].textContent).toBe("Outer");
249
+ expect(container.children[1].textContent).toBe("Inner 1");
250
+ expect(container.children[2].textContent).toBe("Inner 2");
251
+ });
252
+ it("should patch by replacing element with fragment", async () => {
253
+ const container = document.createElement("div");
254
+ let stateFn;
255
+ const App = () => {
256
+ const state = createState({ useFragment: false });
257
+ stateFn = state;
258
+ return () => jsx("div", {
259
+ children: [
260
+ state.useFragment
261
+ ? jsx(Fragment, {
262
+ children: [
263
+ jsx("span", { children: ["First"] }),
264
+ jsx("span", { children: ["Second"] }),
265
+ ],
266
+ })
267
+ : jsx("span", { children: ["Single"] }),
268
+ ],
269
+ });
270
+ };
271
+ render(jsx(App, {}), container);
272
+ const wrapper = container.children[0];
273
+ expect(wrapper.children.length).toBe(1);
274
+ stateFn.useFragment = true;
275
+ await vi.runAllTimersAsync();
276
+ expect(wrapper.children.length).toBe(2);
277
+ expect(wrapper.children[0].textContent).toBe("First");
278
+ expect(wrapper.children[1].textContent).toBe("Second");
279
+ });
280
+ it("should patch by replacing fragment with element", async () => {
281
+ const container = document.createElement("div");
282
+ let stateFn;
283
+ const App = () => {
284
+ const state = createState({ useFragment: true });
285
+ stateFn = state;
286
+ return () => jsx("div", {
287
+ children: [
288
+ state.useFragment
289
+ ? jsx(Fragment, {
290
+ children: [
291
+ jsx("span", { children: ["First"] }),
292
+ jsx("span", { children: ["Second"] }),
293
+ ],
294
+ })
295
+ : jsx("span", { children: ["Single"] }),
296
+ ],
297
+ });
298
+ };
299
+ render(jsx(App, {}), container);
300
+ const wrapper = container.children[0];
301
+ expect(wrapper.children.length).toBe(2);
302
+ stateFn.useFragment = false;
303
+ await vi.runAllTimersAsync();
304
+ expect(wrapper.children.length).toBe(1);
305
+ expect(wrapper.children[0].textContent).toBe("Single");
306
+ });
307
+ it("should patch deeply nested fragments", async () => {
308
+ const container = document.createElement("div");
309
+ let stateFn;
310
+ const App = () => {
311
+ const state = createState({ showDeep2: false });
312
+ stateFn = state;
313
+ return () => jsx(Fragment, {
314
+ children: [
315
+ jsx(Fragment, {
316
+ children: [
317
+ jsx(Fragment, {
318
+ children: [
319
+ jsx("span", { children: ["Deep 1"] }),
320
+ ...(state.showDeep2
321
+ ? [jsx("span", { children: ["Deep 2"] })]
322
+ : []),
323
+ ],
324
+ }),
325
+ ],
326
+ }),
327
+ ],
328
+ });
329
+ };
330
+ render(jsx(App, {}), container);
331
+ expect(container.children.length).toBe(1);
332
+ expect(container.children[0].textContent).toBe("Deep 1");
333
+ stateFn.showDeep2 = true;
334
+ await vi.runAllTimersAsync();
335
+ expect(container.children.length).toBe(2);
336
+ expect(container.children[0].textContent).toBe("Deep 1");
337
+ expect(container.children[1].textContent).toBe("Deep 2");
338
+ });
339
+ it("should patch fragment with keys", async () => {
340
+ const container = document.createElement("div");
341
+ let stateFn;
342
+ const App = () => {
343
+ const state = createState({ reordered: false });
344
+ stateFn = state;
345
+ return () => jsx(Fragment, {
346
+ children: state.reordered
347
+ ? [
348
+ jsx("span", { children: ["C"] }, "c"),
349
+ jsx("span", { children: ["A"] }, "a"),
350
+ jsx("span", { children: ["B"] }, "b"),
351
+ ]
352
+ : [
353
+ jsx("span", { children: ["A"] }, "a"),
354
+ jsx("span", { children: ["B"] }, "b"),
355
+ jsx("span", { children: ["C"] }, "c"),
356
+ ],
357
+ });
358
+ };
359
+ render(jsx(App, {}), container);
360
+ const initialChildren = Array.from(container.children);
361
+ const itemA = initialChildren[0];
362
+ const itemB = initialChildren[1];
363
+ const itemC = initialChildren[2];
364
+ stateFn.reordered = true;
365
+ await vi.runAllTimersAsync();
366
+ const newChildren = Array.from(container.children);
367
+ // Elements should be reordered, not recreated
368
+ expect(newChildren[0]).toBe(itemC);
369
+ expect(newChildren[1]).toBe(itemA);
370
+ expect(newChildren[2]).toBe(itemB);
371
+ });
372
+ it("should patch complex nested structure with fragments", async () => {
373
+ const container = document.createElement("div");
374
+ let stateFn;
375
+ const App = () => {
376
+ const state = createState({ showAsides: false });
377
+ stateFn = state;
378
+ return () => jsx("div", {
379
+ children: [
380
+ jsx("header", { children: ["Header"] }),
381
+ jsx(Fragment, {
382
+ children: [
383
+ jsx("section", { children: ["Section 1"] }),
384
+ ...(state.showAsides
385
+ ? [
386
+ jsx(Fragment, {
387
+ children: [
388
+ jsx("aside", { children: ["Aside 1"] }),
389
+ jsx("aside", { children: ["Aside 2"] }),
390
+ ],
391
+ }),
392
+ ]
393
+ : []),
394
+ jsx("section", { children: ["Section 2"] }),
395
+ ],
396
+ }),
397
+ jsx("footer", { children: ["Footer"] }),
398
+ ],
399
+ });
400
+ };
401
+ render(jsx(App, {}), container);
402
+ const wrapper = container.children[0];
403
+ expect(wrapper.children.length).toBe(4);
404
+ stateFn.showAsides = true;
405
+ await vi.runAllTimersAsync();
406
+ expect(wrapper.children.length).toBe(6);
407
+ expect(wrapper.children[0].tagName).toBe("HEADER");
408
+ expect(wrapper.children[1].tagName).toBe("SECTION");
409
+ expect(wrapper.children[1].textContent).toBe("Section 1");
410
+ expect(wrapper.children[2].tagName).toBe("ASIDE");
411
+ expect(wrapper.children[2].textContent).toBe("Aside 1");
412
+ expect(wrapper.children[3].tagName).toBe("ASIDE");
413
+ expect(wrapper.children[3].textContent).toBe("Aside 2");
414
+ expect(wrapper.children[4].tagName).toBe("SECTION");
415
+ expect(wrapper.children[4].textContent).toBe("Section 2");
416
+ expect(wrapper.children[5].tagName).toBe("FOOTER");
417
+ });
418
+ it("should patch fragment removing all children", async () => {
419
+ const container = document.createElement("div");
420
+ let stateFn;
421
+ const App = () => {
422
+ const state = createState({ hasChildren: true });
423
+ stateFn = state;
424
+ return () => jsx("div", {
425
+ children: [
426
+ jsx(Fragment, {
427
+ children: state.hasChildren
428
+ ? [
429
+ jsx("span", { children: ["A"] }),
430
+ jsx("span", { children: ["B"] }),
431
+ jsx("span", { children: ["C"] }),
432
+ ]
433
+ : [],
434
+ }),
435
+ ],
436
+ });
437
+ };
438
+ render(jsx(App, {}), container);
439
+ const wrapper = container.children[0];
440
+ expect(wrapper.children.length).toBe(3);
441
+ stateFn.hasChildren = false;
442
+ await vi.runAllTimersAsync();
443
+ expect(wrapper.children.length).toBe(0);
444
+ });
445
+ it("should patch multiple sequential fragment changes", async () => {
446
+ const container = document.createElement("div");
447
+ let stateFn;
448
+ const App = () => {
449
+ const state = createState({ childCount: 1 });
450
+ stateFn = state;
451
+ return () => {
452
+ const children = [];
453
+ for (let i = 0; i < state.childCount; i++) {
454
+ const labels = ["One", "Two", "Three"];
455
+ children.push(jsx("span", { children: [labels[i]] }));
456
+ }
457
+ return jsx(Fragment, { children });
458
+ };
459
+ };
460
+ render(jsx(App, {}), container);
461
+ expect(container.children.length).toBe(1);
462
+ expect(container.children[0].textContent).toBe("One");
463
+ // Patch 1: Add element
464
+ stateFn.childCount = 2;
465
+ await vi.runAllTimersAsync();
466
+ expect(container.children.length).toBe(2);
467
+ expect(container.children[1].textContent).toBe("Two");
468
+ // Patch 2: Add another element
469
+ stateFn.childCount = 3;
470
+ await vi.runAllTimersAsync();
471
+ expect(container.children.length).toBe(3);
472
+ expect(container.children[2].textContent).toBe("Three");
473
+ // Patch 3: Remove middle element (simulate by updating the logic)
474
+ // We need to adjust the test to remove the middle element
475
+ // Let's create a different approach for this test
476
+ });
477
+ it("should patch multiple sequential fragment changes with removal", async () => {
478
+ const container = document.createElement("div");
479
+ let stateFn;
480
+ const App = () => {
481
+ const state = createState({ items: ["One"] });
482
+ stateFn = state;
483
+ return () => jsx(Fragment, {
484
+ children: state.items.map((item) => jsx("span", { children: [item] })),
485
+ });
486
+ };
487
+ render(jsx(App, {}), container);
488
+ expect(container.children.length).toBe(1);
489
+ expect(container.children[0].textContent).toBe("One");
490
+ // Patch 1: Add element
491
+ stateFn.items = ["One", "Two"];
492
+ await vi.runAllTimersAsync();
493
+ expect(container.children.length).toBe(2);
494
+ expect(container.children[1].textContent).toBe("Two");
495
+ // Patch 2: Add another element
496
+ stateFn.items = ["One", "Two", "Three"];
497
+ await vi.runAllTimersAsync();
498
+ expect(container.children.length).toBe(3);
499
+ expect(container.children[2].textContent).toBe("Three");
500
+ // Patch 3: Remove middle element
501
+ stateFn.items = ["One", "Three"];
502
+ await vi.runAllTimersAsync();
503
+ expect(container.children.length).toBe(2);
504
+ expect(container.children[0].textContent).toBe("One");
505
+ expect(container.children[1].textContent).toBe("Three");
506
+ });
507
+ });
508
+ describe("Component-Fragment interaction", () => {
509
+ beforeEach(() => {
510
+ vi.useFakeTimers();
511
+ });
512
+ afterEach(() => {
513
+ vi.clearAllTimers();
514
+ });
515
+ it("should patch by replacing element with component returning fragment", async () => {
516
+ const container = document.createElement("div");
517
+ let stateFn;
518
+ const FragmentComponent = () => {
519
+ return () => jsx(Fragment, {
520
+ children: [
521
+ jsx("p", { children: ["First"] }),
522
+ jsx("p", { children: ["Second"] }),
523
+ ],
524
+ });
525
+ };
526
+ const App = () => {
527
+ const state = createState({ useComponent: false });
528
+ stateFn = state;
529
+ return () => jsx("div", {
530
+ children: [
531
+ state.useComponent
532
+ ? jsx(FragmentComponent, {})
533
+ : jsx("p", { children: ["Single"] }),
534
+ ],
535
+ });
536
+ };
537
+ render(jsx(App, {}), container);
538
+ const wrapper = container.children[0];
539
+ expect(wrapper.children.length).toBe(1);
540
+ expect(wrapper.children[0].textContent).toBe("Single");
541
+ stateFn.useComponent = true;
542
+ await vi.runAllTimersAsync();
543
+ expect(wrapper.children.length).toBe(2);
544
+ expect(wrapper.children[0].textContent).toBe("First");
545
+ expect(wrapper.children[1].textContent).toBe("Second");
546
+ });
547
+ it("should patch by replacing component returning fragment with element", async () => {
548
+ const container = document.createElement("div");
549
+ let stateFn;
550
+ const FragmentComponent = () => {
551
+ return () => jsx(Fragment, {
552
+ children: [
553
+ jsx("p", { children: ["First"] }),
554
+ jsx("p", { children: ["Second"] }),
555
+ ],
556
+ });
557
+ };
558
+ const App = () => {
559
+ const state = createState({ useComponent: true });
560
+ stateFn = state;
561
+ return () => jsx("div", {
562
+ children: [
563
+ state.useComponent
564
+ ? jsx(FragmentComponent, {})
565
+ : jsx("p", { children: ["Single"] }),
566
+ ],
567
+ });
568
+ };
569
+ render(jsx(App, {}), container);
570
+ const wrapper = container.children[0];
571
+ expect(wrapper.children.length).toBe(2);
572
+ expect(wrapper.children[0].textContent).toBe("First");
573
+ expect(wrapper.children[1].textContent).toBe("Second");
574
+ stateFn.useComponent = false;
575
+ await vi.runAllTimersAsync();
576
+ expect(wrapper.children.length).toBe(1);
577
+ expect(wrapper.children[0].textContent).toBe("Single");
578
+ });
579
+ it("should conditionally switch between element and component returning fragment", async () => {
580
+ const container = document.createElement("div");
581
+ let globalState;
582
+ const FragmentComponent = () => {
583
+ return () => jsx(Fragment, {
584
+ children: [
585
+ jsx("p", { children: ["Fragment 1"] }),
586
+ jsx("p", { children: ["Fragment 2"] }),
587
+ ],
588
+ });
589
+ };
590
+ const App = () => {
591
+ const state = createState({ showFragment: false });
592
+ globalState = state;
593
+ return () => jsx("div", {
594
+ children: [
595
+ state.showFragment
596
+ ? jsx(FragmentComponent, {})
597
+ : jsx("p", { children: ["Single"] }),
598
+ ],
599
+ });
600
+ };
601
+ render(jsx(App, {}), container);
602
+ const wrapper = container.children[0];
603
+ expect(wrapper.children.length).toBe(1);
604
+ expect(wrapper.children[0].textContent).toBe("Single");
605
+ // Switch to fragment
606
+ globalState.showFragment = true;
607
+ await vi.runAllTimersAsync();
608
+ expect(wrapper.children.length).toBe(2);
609
+ expect(wrapper.children[0].textContent).toBe("Fragment 1");
610
+ expect(wrapper.children[1].textContent).toBe("Fragment 2");
611
+ // Switch back to element
612
+ globalState.showFragment = false;
613
+ await vi.runAllTimersAsync();
614
+ expect(wrapper.children.length).toBe(1);
615
+ expect(wrapper.children[0].textContent).toBe("Single");
616
+ });
617
+ });
618
+ });
@@ -0,0 +1,10 @@
1
+ import { Component, ComponentVNode } from "./ComponentVNode";
2
+ import { ElementVNode } from "./ElementVNode";
3
+ import { Fragment, FragmentVNode } from "./FragmentVNode";
4
+ import { RootVNode } from "./RootVNode";
5
+ import { Props, VNode } from "./types";
6
+ export declare function jsx(type: string | typeof Fragment | Component<any>, props?: Props, key?: string): ElementVNode | FragmentVNode | ComponentVNode;
7
+ export declare const jsxs: typeof jsx;
8
+ export declare const jsxDEV: typeof jsx;
9
+ export declare function render(vnode: VNode, container: HTMLElement): RootVNode;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/vdom/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGvC,wBAAgB,GAAG,CACjB,IAAI,EAAE,MAAM,GAAG,OAAO,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,EAC/C,KAAK,CAAC,EAAE,KAAK,EACb,GAAG,CAAC,EAAE,MAAM,iDAeb;AACD,eAAO,MAAM,IAAI,YAAM,CAAC;AACxB,eAAO,MAAM,MAAM,YAAM,CAAC;AAE1B,wBAAgB,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,aAM1D"}
@@ -0,0 +1,26 @@
1
+ import { ComponentVNode } from "./ComponentVNode";
2
+ import { elementsToFragment } from "./dom-utils";
3
+ import { ElementVNode } from "./ElementVNode";
4
+ import { Fragment, FragmentVNode } from "./FragmentVNode";
5
+ import { RootVNode } from "./RootVNode";
6
+ import { normalizeChildren } from "./utils";
7
+ export function jsx(type, props, key) {
8
+ const { children, ...normalizedProps } = props || {};
9
+ const normalizedChildren = children === undefined ? [] : normalizeChildren(children);
10
+ if (typeof type === "string") {
11
+ return new ElementVNode(type, normalizedProps, normalizedChildren, key);
12
+ }
13
+ if (type === Fragment) {
14
+ return new FragmentVNode(normalizedChildren, key);
15
+ }
16
+ return new ComponentVNode(type, normalizedProps, normalizedChildren, key);
17
+ }
18
+ export const jsxs = jsx;
19
+ export const jsxDEV = jsx;
20
+ export function render(vnode, container) {
21
+ const rootNode = new RootVNode(vnode, container);
22
+ const elms = rootNode.mount();
23
+ container.appendChild(elementsToFragment(elms));
24
+ rootNode.flushLifecycle();
25
+ return rootNode;
26
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=keys.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.test.d.ts","sourceRoot":"","sources":["../../src/vdom/keys.test.tsx"],"names":[],"mappings":""}