@luna_ui/luna 0.11.0 → 0.17.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/api-DAWeanTX.js +1 -0
- package/dist/api-qXll116-.d.ts +80 -0
- package/dist/cli.mjs +27 -22
- package/dist/css/index.js +1 -0
- package/dist/event-utils.d.ts +1 -1
- package/dist/event-utils.js +1 -1
- package/dist/{index-BZoM-af5.d.ts → index-VY8G32hr.d.ts} +16 -76
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +1 -1
- package/dist/jsx-runtime.js +1 -1
- package/dist/raw.d.ts +2 -0
- package/dist/raw.js +1 -0
- package/dist/resource.d.ts +41 -0
- package/dist/resource.js +1 -0
- package/dist/router-lite.d.ts +44 -0
- package/dist/router-lite.js +1 -0
- package/dist/signals-shared.d.ts +12 -0
- package/dist/signals-shared.js +1 -0
- package/dist/signals.d.ts +2 -3
- package/dist/signals.js +1 -1
- package/dist/vite-plugin.d.ts +7708 -2
- package/dist/vite-plugin.js +7 -6
- package/package.json +30 -11
- package/dist/event-utils-9cHYnvun.js +0 -1
- package/dist/src-BFWjzzPo.js +0 -1
- package/src/css/extract.ts +0 -798
- package/src/css/index.ts +0 -10
- package/src/css/inject.ts +0 -205
- package/src/css/inline.ts +0 -182
- package/src/css/minify.ts +0 -70
- package/src/css/optimizer.ts +0 -6
- package/src/css/runtime.ts +0 -344
- package/src/css-optimizer/README.md +0 -353
- package/src/css-optimizer/cooccurrence.ts +0 -100
- package/src/css-optimizer/core.ts +0 -263
- package/src/css-optimizer/extractors.ts +0 -243
- package/src/css-optimizer/hash.ts +0 -54
- package/src/css-optimizer/index.ts +0 -129
- package/src/css-optimizer/merge.ts +0 -109
- package/src/css-optimizer/moonbit-analyzer.ts +0 -210
- package/src/css-optimizer/parser.ts +0 -120
- package/src/css-optimizer/pattern.ts +0 -171
- package/src/css-optimizer/transformers.ts +0 -301
- package/src/css-optimizer/types.ts +0 -128
- package/src/event-utils.ts +0 -227
- package/src/hydration/createHydrator.ts +0 -62
- package/src/hydration/delegate.ts +0 -62
- package/src/hydration/drag.ts +0 -214
- package/src/hydration/index.ts +0 -12
- package/src/hydration/keyboard.ts +0 -64
- package/src/hydration/toggle.ts +0 -101
- package/src/index.ts +0 -908
- package/src/jsx-dev-runtime.ts +0 -2
- package/src/jsx-runtime.ts +0 -398
- package/src/signals.ts +0 -113
- package/src/vite-plugin.ts +0 -718
- package/tests/__screenshots__/apg.test.ts/APG-Components---Accessibility-Tests-Button-Pattern-disabled-button-has-aria-disabled-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--createResource-error-is-undefined-when-pending-1.png +0 -0
- package/tests/__screenshots__/resource.test.ts/Resource-API--SolidJS-style--createResource-transitions-to-success-on-resolve-1.png +0 -0
- package/tests/apg.test.ts +0 -466
- package/tests/context.test.ts +0 -118
- package/tests/css-optimizer-extractors.test.ts +0 -264
- package/tests/css-optimizer-integration.test.ts +0 -566
- package/tests/css-optimizer-transformers.test.ts +0 -301
- package/tests/css-optimizer.test.ts +0 -646
- package/tests/css-runtime.bench.ts +0 -442
- package/tests/css-runtime.test.ts +0 -342
- package/tests/debounced.test.ts +0 -165
- package/tests/dom.test.ts +0 -873
- package/tests/integration.test.ts +0 -405
- package/tests/issue-11-show-null-to-truthy.test.ts +0 -176
- package/tests/issue-5-for-infinite-loop.test.ts +0 -516
- package/tests/jsx-runtime.test.tsx +0 -393
- package/tests/lifecycle.test.ts +0 -833
- package/tests/move-before.bench.ts +0 -304
- package/tests/preact-signals-comparison.test.ts +0 -1608
- package/tests/resource.test.ts +0 -170
- package/tests/router.test.ts +0 -117
- package/tests/show-initial-mount-leak.test.tsx +0 -182
- package/tests/solidjs-api.test.ts +0 -660
- package/tests/static-perf.bench.ts +0 -64
- package/tests/store.test.ts +0 -263
- package/tests/tsx-syntax.test.tsx +0 -404
- /package/dist/{event-utils-BkTM7rk5.d.ts → event-utils-BvAf0NwN.d.ts} +0 -0
package/tests/dom.test.ts
DELETED
|
@@ -1,873 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
text,
|
|
4
|
-
textDyn,
|
|
5
|
-
createElement,
|
|
6
|
-
createElementNs,
|
|
7
|
-
svgNs,
|
|
8
|
-
mathmlNs,
|
|
9
|
-
render,
|
|
10
|
-
mount,
|
|
11
|
-
show,
|
|
12
|
-
jsx,
|
|
13
|
-
jsxs,
|
|
14
|
-
Fragment,
|
|
15
|
-
events,
|
|
16
|
-
forEach,
|
|
17
|
-
For,
|
|
18
|
-
Show,
|
|
19
|
-
createSignal,
|
|
20
|
-
createRenderEffect,
|
|
21
|
-
} from "../src/index";
|
|
22
|
-
import { jsx as jsxRuntime } from "../src/jsx-runtime";
|
|
23
|
-
|
|
24
|
-
// MoonBit tuple representation for attrs: [name, value] -> { _0: name, _1: value }
|
|
25
|
-
// AttrValue constructors: $tag: 0 = Static, 1 = Dynamic, 2 = Handler
|
|
26
|
-
function attr(name: string, value: unknown) {
|
|
27
|
-
return { _0: name, _1: value };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const AttrValue = {
|
|
31
|
-
Static: (value: string) => ({ $tag: 0, _0: value }),
|
|
32
|
-
Dynamic: (getter: () => string) => ({ $tag: 1, _0: getter }),
|
|
33
|
-
Handler: (handler: (e: unknown) => void) => ({ $tag: 2, _0: handler }),
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
describe("DOM API", () => {
|
|
37
|
-
let container: HTMLElement;
|
|
38
|
-
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
container = document.createElement("div");
|
|
41
|
-
document.body.appendChild(container);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("text nodes", () => {
|
|
45
|
-
test("text creates a text node", () => {
|
|
46
|
-
const node = text("hello");
|
|
47
|
-
expect(node).toBeDefined();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("text node renders to DOM", () => {
|
|
51
|
-
const node = text("hello world");
|
|
52
|
-
render(container, node);
|
|
53
|
-
expect(container.textContent).toBe("hello world");
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("textDyn creates reactive text node", () => {
|
|
57
|
-
const [value, setValue] = createSignal("initial");
|
|
58
|
-
const node = textDyn(value);
|
|
59
|
-
render(container, node);
|
|
60
|
-
expect(container.textContent).toBe("initial");
|
|
61
|
-
|
|
62
|
-
setValue("updated");
|
|
63
|
-
expect(container.textContent).toBe("updated");
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
describe("createElement", () => {
|
|
68
|
-
test("createElement creates element with no attrs", () => {
|
|
69
|
-
const node = createElement("div", [], []);
|
|
70
|
-
expect(node).toBeDefined();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("createElement renders to DOM", () => {
|
|
74
|
-
const node = createElement("div", [], [text("content")]);
|
|
75
|
-
render(container, node);
|
|
76
|
-
expect(container.innerHTML).toBe("<div>content</div>");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("createElement with static attributes", () => {
|
|
80
|
-
const node = createElement(
|
|
81
|
-
"div",
|
|
82
|
-
[
|
|
83
|
-
attr("id", AttrValue.Static("my-id")),
|
|
84
|
-
attr("className", AttrValue.Static("my-class")),
|
|
85
|
-
],
|
|
86
|
-
[]
|
|
87
|
-
);
|
|
88
|
-
render(container, node);
|
|
89
|
-
const div = container.querySelector("div");
|
|
90
|
-
expect(div?.id).toBe("my-id");
|
|
91
|
-
expect(div?.className).toBe("my-class");
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("createElement with style attribute", () => {
|
|
95
|
-
const node = createElement(
|
|
96
|
-
"div",
|
|
97
|
-
[attr("style", AttrValue.Static("color: red; margin: 10px"))],
|
|
98
|
-
[]
|
|
99
|
-
);
|
|
100
|
-
render(container, node);
|
|
101
|
-
const div = container.querySelector("div");
|
|
102
|
-
expect(div?.getAttribute("style")).toBe("color: red; margin: 10px");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("createElement with nested children", () => {
|
|
106
|
-
const node = createElement("div", [], [
|
|
107
|
-
createElement("span", [], [text("child1")]),
|
|
108
|
-
createElement("span", [], [text("child2")]),
|
|
109
|
-
]);
|
|
110
|
-
render(container, node);
|
|
111
|
-
expect(container.querySelectorAll("span").length).toBe(2);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("createElement with dynamic attribute", () => {
|
|
115
|
-
const [className, setClassName] = createSignal("initial-class");
|
|
116
|
-
const node = createElement(
|
|
117
|
-
"div",
|
|
118
|
-
[attr("className", AttrValue.Dynamic(className))],
|
|
119
|
-
[]
|
|
120
|
-
);
|
|
121
|
-
render(container, node);
|
|
122
|
-
const div = container.querySelector("div");
|
|
123
|
-
expect(div?.className).toBe("initial-class");
|
|
124
|
-
|
|
125
|
-
setClassName("updated-class");
|
|
126
|
-
expect(div?.className).toBe("updated-class");
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("createElement with event handler", () => {
|
|
130
|
-
let clicked = false;
|
|
131
|
-
const node = createElement(
|
|
132
|
-
"button",
|
|
133
|
-
[attr("click", AttrValue.Handler(() => { clicked = true; }))],
|
|
134
|
-
[text("Click me")]
|
|
135
|
-
);
|
|
136
|
-
render(container, node);
|
|
137
|
-
const button = container.querySelector("button");
|
|
138
|
-
button?.click();
|
|
139
|
-
expect(clicked).toBe(true);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
test("createElement with value attribute (input)", () => {
|
|
143
|
-
const node = createElement(
|
|
144
|
-
"input",
|
|
145
|
-
[attr("value", AttrValue.Static("test-value"))],
|
|
146
|
-
[]
|
|
147
|
-
);
|
|
148
|
-
render(container, node);
|
|
149
|
-
const input = container.querySelector("input") as HTMLInputElement;
|
|
150
|
-
expect(input?.value).toBe("test-value");
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("createElement with checked attribute", () => {
|
|
154
|
-
const node = createElement(
|
|
155
|
-
"input",
|
|
156
|
-
[
|
|
157
|
-
attr("type", AttrValue.Static("checkbox")),
|
|
158
|
-
attr("checked", AttrValue.Static("true")),
|
|
159
|
-
],
|
|
160
|
-
[]
|
|
161
|
-
);
|
|
162
|
-
render(container, node);
|
|
163
|
-
const input = container.querySelector("input") as HTMLInputElement;
|
|
164
|
-
expect(input?.checked).toBe(true);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test("createElement with disabled attribute true", () => {
|
|
168
|
-
const nodeDisabled = createElement(
|
|
169
|
-
"button",
|
|
170
|
-
[attr("disabled", AttrValue.Static("true"))],
|
|
171
|
-
[]
|
|
172
|
-
);
|
|
173
|
-
render(container, nodeDisabled);
|
|
174
|
-
const btn = container.querySelector("button");
|
|
175
|
-
expect(btn?.hasAttribute("disabled")).toBe(true);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
test("createElement with disabled attribute false", () => {
|
|
179
|
-
const nodeEnabled = createElement(
|
|
180
|
-
"button",
|
|
181
|
-
[attr("disabled", AttrValue.Static("false"))],
|
|
182
|
-
[]
|
|
183
|
-
);
|
|
184
|
-
render(container, nodeEnabled);
|
|
185
|
-
const btn = container.querySelector("button");
|
|
186
|
-
expect(btn?.hasAttribute("disabled")).toBe(false);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test("createElement with dynamic style", () => {
|
|
190
|
-
const [style, setStyle] = createSignal("color: blue");
|
|
191
|
-
const node = createElement(
|
|
192
|
-
"div",
|
|
193
|
-
[attr("style", AttrValue.Dynamic(style))],
|
|
194
|
-
[]
|
|
195
|
-
);
|
|
196
|
-
render(container, node);
|
|
197
|
-
const div = container.querySelector("div");
|
|
198
|
-
expect(div?.getAttribute("style")).toBe("color: blue");
|
|
199
|
-
|
|
200
|
-
setStyle("color: green");
|
|
201
|
-
expect(div?.getAttribute("style")).toBe("color: green");
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
describe("jsx/jsxs", () => {
|
|
206
|
-
test("jsx creates element", () => {
|
|
207
|
-
const node = jsx("div", [], [text("jsx content")]);
|
|
208
|
-
render(container, node);
|
|
209
|
-
expect(container.innerHTML).toBe("<div>jsx content</div>");
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("jsxs creates element with multiple children", () => {
|
|
213
|
-
const node = jsxs("div", [], [text("child1"), text("child2")]);
|
|
214
|
-
render(container, node);
|
|
215
|
-
expect(container.textContent).toBe("child1child2");
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
describe("Fragment", () => {
|
|
220
|
-
test("Fragment wraps multiple children", () => {
|
|
221
|
-
const node = Fragment([text("a"), text("b"), text("c")]);
|
|
222
|
-
render(container, node);
|
|
223
|
-
expect(container.textContent).toBe("abc");
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
describe("render and mount", () => {
|
|
228
|
-
test("render clears container first", () => {
|
|
229
|
-
container.innerHTML = "<p>existing</p>";
|
|
230
|
-
const node = text("new content");
|
|
231
|
-
render(container, node);
|
|
232
|
-
expect(container.textContent).toBe("new content");
|
|
233
|
-
expect(container.querySelector("p")).toBeNull();
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
test("mount appends without clearing", () => {
|
|
237
|
-
container.innerHTML = "<p>existing</p>";
|
|
238
|
-
const node = text("appended");
|
|
239
|
-
mount(container, node);
|
|
240
|
-
expect(container.textContent).toBe("existingappended");
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
describe("show (conditional rendering)", () => {
|
|
245
|
-
test("show creates a node", () => {
|
|
246
|
-
const [visible] = createSignal(true);
|
|
247
|
-
const node = show(visible, () =>
|
|
248
|
-
createElement("div", [attr("id", AttrValue.Static("shown"))], [text("visible")])
|
|
249
|
-
);
|
|
250
|
-
expect(node).toBeDefined();
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
test("show with false condition creates placeholder", () => {
|
|
254
|
-
const [visible] = createSignal(false);
|
|
255
|
-
const node = show(visible, () =>
|
|
256
|
-
createElement("div", [], [text("hidden")])
|
|
257
|
-
);
|
|
258
|
-
mount(container, node);
|
|
259
|
-
// When false, only a comment placeholder is rendered
|
|
260
|
-
expect(container.childNodes.length).toBeGreaterThanOrEqual(1);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
describe("forEach (list rendering)", () => {
|
|
265
|
-
test("forEach renders initial list", () => {
|
|
266
|
-
const [items] = createSignal(["a", "b", "c"]);
|
|
267
|
-
const node = forEach(items, (item: string, _index: number) =>
|
|
268
|
-
createElement("span", [], [text(item)])
|
|
269
|
-
);
|
|
270
|
-
mount(container, node);
|
|
271
|
-
expect(container.querySelectorAll("span").length).toBe(3);
|
|
272
|
-
expect(container.textContent).toBe("abc");
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
test("forEach updates when items change", () => {
|
|
276
|
-
const [items, setItems] = createSignal(["x", "y"]);
|
|
277
|
-
const node = forEach(items, (item: string, _index: number) =>
|
|
278
|
-
createElement("span", [], [text(item)])
|
|
279
|
-
);
|
|
280
|
-
mount(container, node);
|
|
281
|
-
expect(container.textContent).toBe("xy");
|
|
282
|
-
|
|
283
|
-
setItems(["x", "y", "z"]);
|
|
284
|
-
expect(container.querySelectorAll("span").length).toBe(3);
|
|
285
|
-
expect(container.textContent).toBe("xyz");
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
test("forEach removes items", () => {
|
|
289
|
-
const [items, setItems] = createSignal(["1", "2", "3"]);
|
|
290
|
-
const node = forEach(items, (item: string, _index: number) =>
|
|
291
|
-
createElement("span", [], [text(item)])
|
|
292
|
-
);
|
|
293
|
-
mount(container, node);
|
|
294
|
-
expect(container.querySelectorAll("span").length).toBe(3);
|
|
295
|
-
|
|
296
|
-
setItems(["1"]);
|
|
297
|
-
expect(container.querySelectorAll("span").length).toBe(1);
|
|
298
|
-
expect(container.textContent).toBe("1");
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
test("forEach handles empty array", () => {
|
|
302
|
-
const [items] = createSignal<string[]>([]);
|
|
303
|
-
const node = forEach(items, (item: string, _index: number) =>
|
|
304
|
-
createElement("span", [], [text(item)])
|
|
305
|
-
);
|
|
306
|
-
mount(container, node);
|
|
307
|
-
expect(container.querySelectorAll("span").length).toBe(0);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
test("forEach handles clear to empty", () => {
|
|
311
|
-
const [items, setItems] = createSignal(["a", "b"]);
|
|
312
|
-
const node = forEach(items, (item: string, _index: number) =>
|
|
313
|
-
createElement("span", [], [text(item)])
|
|
314
|
-
);
|
|
315
|
-
mount(container, node);
|
|
316
|
-
expect(container.querySelectorAll("span").length).toBe(2);
|
|
317
|
-
|
|
318
|
-
setItems([]);
|
|
319
|
-
expect(container.querySelectorAll("span").length).toBe(0);
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
describe("events helper", () => {
|
|
324
|
-
test("events returns handler map", () => {
|
|
325
|
-
const handlers = events();
|
|
326
|
-
expect(handlers).toBeDefined();
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
describe("effect with DOM", () => {
|
|
331
|
-
test("effect tracks signal changes", () => {
|
|
332
|
-
const [count, setCount] = createSignal(0);
|
|
333
|
-
const log: number[] = [];
|
|
334
|
-
|
|
335
|
-
createRenderEffect(() => {
|
|
336
|
-
log.push(count());
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
expect(log).toEqual([0]);
|
|
340
|
-
setCount(1);
|
|
341
|
-
expect(log).toEqual([0, 1]);
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
describe("For component (SolidJS-style)", () => {
|
|
346
|
-
test("For renders list with getter", () => {
|
|
347
|
-
const [items] = createSignal(["a", "b", "c"]);
|
|
348
|
-
|
|
349
|
-
const node = For({
|
|
350
|
-
each: items,
|
|
351
|
-
children: (item: string, index: () => number) =>
|
|
352
|
-
createElement("li", [], [text(`${index()}: ${item}`)]),
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
mount(container, node);
|
|
356
|
-
expect(container.querySelectorAll("li").length).toBe(3);
|
|
357
|
-
expect(container.textContent).toBe("0: a1: b2: c");
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
test("For updates when signal changes", () => {
|
|
361
|
-
const [items, setItems] = createSignal(["x", "y"]);
|
|
362
|
-
|
|
363
|
-
const node = For({
|
|
364
|
-
each: items,
|
|
365
|
-
children: (item: string) => createElement("span", [], [text(item)]),
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
mount(container, node);
|
|
369
|
-
expect(container.textContent).toBe("xy");
|
|
370
|
-
|
|
371
|
-
setItems(["x", "y", "z"]);
|
|
372
|
-
expect(container.querySelectorAll("span").length).toBe(3);
|
|
373
|
-
expect(container.textContent).toBe("xyz");
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
test("For provides index as getter function", () => {
|
|
377
|
-
const [items] = createSignal(["one", "two"]);
|
|
378
|
-
const indices: number[] = [];
|
|
379
|
-
|
|
380
|
-
const node = For({
|
|
381
|
-
each: items,
|
|
382
|
-
children: (_item: string, index: () => number) => {
|
|
383
|
-
indices.push(index());
|
|
384
|
-
return createElement("div", [], []);
|
|
385
|
-
},
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
mount(container, node);
|
|
389
|
-
expect(indices).toEqual([0, 1]);
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
describe("Show component (SolidJS-style)", () => {
|
|
394
|
-
test("Show hides content when condition is false", () => {
|
|
395
|
-
const [visible] = createSignal(false);
|
|
396
|
-
|
|
397
|
-
const node = Show({
|
|
398
|
-
when: visible,
|
|
399
|
-
children: createElement("div", [], [text("visible")]),
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
mount(container, node);
|
|
403
|
-
expect(container.querySelector("div")).toBeNull();
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
test("Show toggles visibility", () => {
|
|
407
|
-
const [visible, setVisible] = createSignal(false);
|
|
408
|
-
|
|
409
|
-
const node = Show({
|
|
410
|
-
when: visible,
|
|
411
|
-
children: createElement("span", [], [text("content")]),
|
|
412
|
-
});
|
|
413
|
-
|
|
414
|
-
mount(container, node);
|
|
415
|
-
expect(container.querySelector("span")).toBeNull();
|
|
416
|
-
|
|
417
|
-
setVisible(true);
|
|
418
|
-
expect(container.querySelector("span")).not.toBeNull();
|
|
419
|
-
|
|
420
|
-
setVisible(false);
|
|
421
|
-
expect(container.querySelector("span")).toBeNull();
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
test("Show accepts children as function with accessor (SolidJS-style)", () => {
|
|
425
|
-
const [value, setValue] = createSignal<string | null>(null);
|
|
426
|
-
|
|
427
|
-
const node = Show({
|
|
428
|
-
when: value,
|
|
429
|
-
// SolidJS-style: children receives accessor function, call it with ()
|
|
430
|
-
children: (v: () => string) => createElement("span", [], [text(v())]),
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
mount(container, node);
|
|
434
|
-
expect(container.querySelector("span")).toBeNull();
|
|
435
|
-
|
|
436
|
-
setValue("hello");
|
|
437
|
-
expect(container.querySelector("span")?.textContent).toBe("hello");
|
|
438
|
-
});
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
describe("ref callback (JSX style)", () => {
|
|
442
|
-
test("ref callback is called with element (low-level __ref)", () => {
|
|
443
|
-
let capturedElement: HTMLElement | null = null;
|
|
444
|
-
|
|
445
|
-
const node = createElement(
|
|
446
|
-
"div",
|
|
447
|
-
[attr("__ref", AttrValue.Handler((el: unknown) => {
|
|
448
|
-
capturedElement = el as HTMLElement;
|
|
449
|
-
}))],
|
|
450
|
-
[text("content")]
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
render(container, node);
|
|
454
|
-
|
|
455
|
-
expect(capturedElement).not.toBeNull();
|
|
456
|
-
expect(capturedElement?.tagName).toBe("DIV");
|
|
457
|
-
expect(capturedElement?.textContent).toBe("content");
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
test("jsx-runtime converts ref prop to __ref", () => {
|
|
461
|
-
let capturedElement: HTMLElement | null = null;
|
|
462
|
-
|
|
463
|
-
// Simulates: <div ref={(el) => capturedElement = el}>content</div>
|
|
464
|
-
const node = jsxRuntime("div", {
|
|
465
|
-
ref: (el: HTMLElement) => { capturedElement = el; },
|
|
466
|
-
children: "content",
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
render(container, node);
|
|
470
|
-
|
|
471
|
-
expect(capturedElement).not.toBeNull();
|
|
472
|
-
expect(capturedElement?.tagName).toBe("DIV");
|
|
473
|
-
expect(capturedElement?.textContent).toBe("content");
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
test("jsx-runtime ref works with input elements", () => {
|
|
477
|
-
let inputRef: HTMLInputElement | null = null;
|
|
478
|
-
|
|
479
|
-
// Simulates: <input type="text" ref={(el) => inputRef = el} />
|
|
480
|
-
const node = jsxRuntime("input", {
|
|
481
|
-
type: "text",
|
|
482
|
-
ref: (el: HTMLInputElement) => { inputRef = el; },
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
render(container, node);
|
|
486
|
-
|
|
487
|
-
expect(inputRef).not.toBeNull();
|
|
488
|
-
expect(inputRef?.tagName).toBe("INPUT");
|
|
489
|
-
expect(inputRef?.type).toBe("text");
|
|
490
|
-
|
|
491
|
-
// Test that we can use the ref to focus the input
|
|
492
|
-
inputRef?.focus();
|
|
493
|
-
expect(document.activeElement).toBe(inputRef);
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
test("ref callback provides access to DOM properties", () => {
|
|
497
|
-
let capturedId: string | null = null;
|
|
498
|
-
|
|
499
|
-
const node = createElement(
|
|
500
|
-
"input",
|
|
501
|
-
[
|
|
502
|
-
attr("id", AttrValue.Static("test-input")),
|
|
503
|
-
attr("type", AttrValue.Static("text")),
|
|
504
|
-
attr("__ref", AttrValue.Handler((el: unknown) => {
|
|
505
|
-
capturedId = (el as HTMLInputElement).id;
|
|
506
|
-
})),
|
|
507
|
-
],
|
|
508
|
-
[]
|
|
509
|
-
);
|
|
510
|
-
|
|
511
|
-
render(container, node);
|
|
512
|
-
|
|
513
|
-
expect(capturedId).toBe("test-input");
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
test("ref callback can call DOM methods", () => {
|
|
517
|
-
let inputElement: HTMLInputElement | null = null;
|
|
518
|
-
|
|
519
|
-
const node = createElement(
|
|
520
|
-
"input",
|
|
521
|
-
[
|
|
522
|
-
attr("type", AttrValue.Static("text")),
|
|
523
|
-
attr("__ref", AttrValue.Handler((el: unknown) => {
|
|
524
|
-
inputElement = el as HTMLInputElement;
|
|
525
|
-
})),
|
|
526
|
-
],
|
|
527
|
-
[]
|
|
528
|
-
);
|
|
529
|
-
|
|
530
|
-
render(container, node);
|
|
531
|
-
|
|
532
|
-
expect(inputElement).not.toBeNull();
|
|
533
|
-
// Call focus() via ref
|
|
534
|
-
inputElement?.focus();
|
|
535
|
-
expect(document.activeElement).toBe(inputElement);
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
test("ref callback with nested elements", () => {
|
|
539
|
-
const refs: HTMLElement[] = [];
|
|
540
|
-
|
|
541
|
-
const node = createElement(
|
|
542
|
-
"div",
|
|
543
|
-
[attr("__ref", AttrValue.Handler((el: unknown) => refs.push(el as HTMLElement)))],
|
|
544
|
-
[
|
|
545
|
-
createElement(
|
|
546
|
-
"span",
|
|
547
|
-
[attr("__ref", AttrValue.Handler((el: unknown) => refs.push(el as HTMLElement)))],
|
|
548
|
-
[text("nested")]
|
|
549
|
-
),
|
|
550
|
-
]
|
|
551
|
-
);
|
|
552
|
-
|
|
553
|
-
render(container, node);
|
|
554
|
-
|
|
555
|
-
// Both refs should be captured
|
|
556
|
-
expect(refs.length).toBe(2);
|
|
557
|
-
// Note: Children are processed before parent attributes, so span comes first
|
|
558
|
-
const tagNames = refs.map(el => el.tagName).sort();
|
|
559
|
-
expect(tagNames).toEqual(["DIV", "SPAN"]);
|
|
560
|
-
});
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
describe("createElementNs (SVG support)", () => {
|
|
564
|
-
test("svgNs returns correct namespace", () => {
|
|
565
|
-
expect(svgNs()).toBe("http://www.w3.org/2000/svg");
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
test("mathmlNs returns correct namespace", () => {
|
|
569
|
-
expect(mathmlNs()).toBe("http://www.w3.org/1998/Math/MathML");
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
test("createElementNs creates SVG element with correct namespace", () => {
|
|
573
|
-
const node = createElementNs(svgNs(), "svg", [], []);
|
|
574
|
-
render(container, node);
|
|
575
|
-
const svg = container.querySelector("svg");
|
|
576
|
-
expect(svg).not.toBeNull();
|
|
577
|
-
expect(svg?.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
test("createElementNs creates SVG child elements", () => {
|
|
581
|
-
const rect = createElementNs(
|
|
582
|
-
svgNs(),
|
|
583
|
-
"rect",
|
|
584
|
-
[
|
|
585
|
-
attr("x", AttrValue.Static("10")),
|
|
586
|
-
attr("y", AttrValue.Static("10")),
|
|
587
|
-
attr("width", AttrValue.Static("100")),
|
|
588
|
-
attr("height", AttrValue.Static("50")),
|
|
589
|
-
attr("fill", AttrValue.Static("blue")),
|
|
590
|
-
],
|
|
591
|
-
[]
|
|
592
|
-
);
|
|
593
|
-
const svg = createElementNs(
|
|
594
|
-
svgNs(),
|
|
595
|
-
"svg",
|
|
596
|
-
[
|
|
597
|
-
attr("width", AttrValue.Static("200")),
|
|
598
|
-
attr("height", AttrValue.Static("200")),
|
|
599
|
-
],
|
|
600
|
-
[rect]
|
|
601
|
-
);
|
|
602
|
-
render(container, svg);
|
|
603
|
-
|
|
604
|
-
const svgEl = container.querySelector("svg");
|
|
605
|
-
const rectEl = container.querySelector("rect");
|
|
606
|
-
expect(svgEl).not.toBeNull();
|
|
607
|
-
expect(rectEl).not.toBeNull();
|
|
608
|
-
expect(rectEl?.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
|
609
|
-
expect(rectEl?.getAttribute("fill")).toBe("blue");
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
test("createElementNs works with circle and path", () => {
|
|
613
|
-
const circle = createElementNs(
|
|
614
|
-
svgNs(),
|
|
615
|
-
"circle",
|
|
616
|
-
[
|
|
617
|
-
attr("cx", AttrValue.Static("50")),
|
|
618
|
-
attr("cy", AttrValue.Static("50")),
|
|
619
|
-
attr("r", AttrValue.Static("25")),
|
|
620
|
-
],
|
|
621
|
-
[]
|
|
622
|
-
);
|
|
623
|
-
const path = createElementNs(
|
|
624
|
-
svgNs(),
|
|
625
|
-
"path",
|
|
626
|
-
[attr("d", AttrValue.Static("M 10 10 L 50 50"))],
|
|
627
|
-
[]
|
|
628
|
-
);
|
|
629
|
-
const g = createElementNs(svgNs(), "g", [], [circle, path]);
|
|
630
|
-
const svg = createElementNs(svgNs(), "svg", [], [g]);
|
|
631
|
-
render(container, svg);
|
|
632
|
-
|
|
633
|
-
expect(container.querySelector("circle")).not.toBeNull();
|
|
634
|
-
expect(container.querySelector("path")).not.toBeNull();
|
|
635
|
-
expect(container.querySelector("g")).not.toBeNull();
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
test("createElementNs with dynamic attribute", () => {
|
|
639
|
-
const [fill, setFill] = createSignal("red");
|
|
640
|
-
const rect = createElementNs(
|
|
641
|
-
svgNs(),
|
|
642
|
-
"rect",
|
|
643
|
-
[
|
|
644
|
-
attr("width", AttrValue.Static("100")),
|
|
645
|
-
attr("height", AttrValue.Static("100")),
|
|
646
|
-
attr("fill", AttrValue.Dynamic(fill)),
|
|
647
|
-
],
|
|
648
|
-
[]
|
|
649
|
-
);
|
|
650
|
-
const svg = createElementNs(svgNs(), "svg", [], [rect]);
|
|
651
|
-
render(container, svg);
|
|
652
|
-
|
|
653
|
-
const rectEl = container.querySelector("rect");
|
|
654
|
-
expect(rectEl?.getAttribute("fill")).toBe("red");
|
|
655
|
-
|
|
656
|
-
setFill("green");
|
|
657
|
-
expect(rectEl?.getAttribute("fill")).toBe("green");
|
|
658
|
-
});
|
|
659
|
-
|
|
660
|
-
test("SVG can be nested inside regular HTML elements", () => {
|
|
661
|
-
const circle = createElementNs(
|
|
662
|
-
svgNs(),
|
|
663
|
-
"circle",
|
|
664
|
-
[
|
|
665
|
-
attr("cx", AttrValue.Static("50")),
|
|
666
|
-
attr("cy", AttrValue.Static("50")),
|
|
667
|
-
attr("r", AttrValue.Static("25")),
|
|
668
|
-
],
|
|
669
|
-
[]
|
|
670
|
-
);
|
|
671
|
-
const svg = createElementNs(
|
|
672
|
-
svgNs(),
|
|
673
|
-
"svg",
|
|
674
|
-
[
|
|
675
|
-
attr("width", AttrValue.Static("100")),
|
|
676
|
-
attr("height", AttrValue.Static("100")),
|
|
677
|
-
],
|
|
678
|
-
[circle]
|
|
679
|
-
);
|
|
680
|
-
const div = createElement("div", [], [svg]);
|
|
681
|
-
render(container, div);
|
|
682
|
-
|
|
683
|
-
const divEl = container.querySelector("div");
|
|
684
|
-
const svgEl = container.querySelector("svg");
|
|
685
|
-
expect(divEl).not.toBeNull();
|
|
686
|
-
expect(svgEl?.parentElement).toBe(divEl);
|
|
687
|
-
expect(svgEl?.namespaceURI).toBe("http://www.w3.org/2000/svg");
|
|
688
|
-
});
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
describe("forEach with SVG elements", () => {
|
|
692
|
-
test("forEach renders SVG elements", () => {
|
|
693
|
-
const [items, setItems] = createSignal([
|
|
694
|
-
{ id: "1", x: 10, y: 10 },
|
|
695
|
-
{ id: "2", x: 50, y: 50 },
|
|
696
|
-
]);
|
|
697
|
-
|
|
698
|
-
const forEachNode = forEach(
|
|
699
|
-
items,
|
|
700
|
-
(item) =>
|
|
701
|
-
createElementNs(
|
|
702
|
-
svgNs(),
|
|
703
|
-
"rect",
|
|
704
|
-
[
|
|
705
|
-
attr("data-id", AttrValue.Static(item.id)),
|
|
706
|
-
attr("x", AttrValue.Static(String(item.x))),
|
|
707
|
-
attr("y", AttrValue.Static(String(item.y))),
|
|
708
|
-
attr("width", AttrValue.Static("20")),
|
|
709
|
-
attr("height", AttrValue.Static("20")),
|
|
710
|
-
],
|
|
711
|
-
[]
|
|
712
|
-
)
|
|
713
|
-
);
|
|
714
|
-
|
|
715
|
-
const svg = createElementNs(
|
|
716
|
-
svgNs(),
|
|
717
|
-
"svg",
|
|
718
|
-
[
|
|
719
|
-
attr("width", AttrValue.Static("200")),
|
|
720
|
-
attr("height", AttrValue.Static("200")),
|
|
721
|
-
],
|
|
722
|
-
[forEachNode]
|
|
723
|
-
);
|
|
724
|
-
|
|
725
|
-
render(container, svg);
|
|
726
|
-
|
|
727
|
-
const rects = container.querySelectorAll("rect");
|
|
728
|
-
expect(rects.length).toBe(2);
|
|
729
|
-
expect(rects[0].getAttribute("data-id")).toBe("1");
|
|
730
|
-
expect(rects[1].getAttribute("data-id")).toBe("2");
|
|
731
|
-
});
|
|
732
|
-
|
|
733
|
-
test("forEach updates SVG elements when signal changes", () => {
|
|
734
|
-
const [items, setItems] = createSignal([{ id: "1", x: 10 }]);
|
|
735
|
-
|
|
736
|
-
const forEachNode = forEach(
|
|
737
|
-
items,
|
|
738
|
-
(item) =>
|
|
739
|
-
createElementNs(
|
|
740
|
-
svgNs(),
|
|
741
|
-
"circle",
|
|
742
|
-
[
|
|
743
|
-
attr("data-id", AttrValue.Static(item.id)),
|
|
744
|
-
attr("cx", AttrValue.Static(String(item.x))),
|
|
745
|
-
attr("cy", AttrValue.Static("50")),
|
|
746
|
-
attr("r", AttrValue.Static("10")),
|
|
747
|
-
],
|
|
748
|
-
[]
|
|
749
|
-
)
|
|
750
|
-
);
|
|
751
|
-
|
|
752
|
-
const svg = createElementNs(svgNs(), "svg", [], [forEachNode]);
|
|
753
|
-
render(container, svg);
|
|
754
|
-
|
|
755
|
-
expect(container.querySelectorAll("circle").length).toBe(1);
|
|
756
|
-
|
|
757
|
-
// Add item
|
|
758
|
-
setItems([
|
|
759
|
-
{ id: "1", x: 10 },
|
|
760
|
-
{ id: "2", x: 50 },
|
|
761
|
-
]);
|
|
762
|
-
expect(container.querySelectorAll("circle").length).toBe(2);
|
|
763
|
-
|
|
764
|
-
// Remove item
|
|
765
|
-
setItems([{ id: "2", x: 50 }]);
|
|
766
|
-
expect(container.querySelectorAll("circle").length).toBe(1);
|
|
767
|
-
expect(container.querySelector("circle")?.getAttribute("data-id")).toBe("2");
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
test("forEach handles reordering in SVG", () => {
|
|
771
|
-
const item1 = { id: "a", x: 10 };
|
|
772
|
-
const item2 = { id: "b", x: 20 };
|
|
773
|
-
const item3 = { id: "c", x: 30 };
|
|
774
|
-
|
|
775
|
-
const [items, setItems] = createSignal([item1, item2, item3]);
|
|
776
|
-
|
|
777
|
-
const forEachNode = forEach(
|
|
778
|
-
items,
|
|
779
|
-
(item) =>
|
|
780
|
-
createElementNs(
|
|
781
|
-
svgNs(),
|
|
782
|
-
"rect",
|
|
783
|
-
[attr("data-id", AttrValue.Static(item.id))],
|
|
784
|
-
[]
|
|
785
|
-
)
|
|
786
|
-
);
|
|
787
|
-
|
|
788
|
-
const svg = createElementNs(svgNs(), "svg", [], [forEachNode]);
|
|
789
|
-
render(container, svg);
|
|
790
|
-
|
|
791
|
-
const getIds = () =>
|
|
792
|
-
Array.from(container.querySelectorAll("rect")).map((r) =>
|
|
793
|
-
r.getAttribute("data-id")
|
|
794
|
-
);
|
|
795
|
-
|
|
796
|
-
expect(getIds()).toEqual(["a", "b", "c"]);
|
|
797
|
-
|
|
798
|
-
// Reorder
|
|
799
|
-
setItems([item3, item1, item2]);
|
|
800
|
-
expect(getIds()).toEqual(["c", "a", "b"]);
|
|
801
|
-
|
|
802
|
-
// Reverse
|
|
803
|
-
setItems([item2, item1, item3]);
|
|
804
|
-
expect(getIds()).toEqual(["b", "a", "c"]);
|
|
805
|
-
});
|
|
806
|
-
|
|
807
|
-
test("forEach with nested SVG groups", () => {
|
|
808
|
-
const [groups, setGroups] = createSignal([
|
|
809
|
-
{ id: "g1", items: ["a", "b"] },
|
|
810
|
-
{ id: "g2", items: ["c", "d"] },
|
|
811
|
-
]);
|
|
812
|
-
|
|
813
|
-
const forEachNode = forEach(
|
|
814
|
-
groups,
|
|
815
|
-
(group) =>
|
|
816
|
-
createElementNs(
|
|
817
|
-
svgNs(),
|
|
818
|
-
"g",
|
|
819
|
-
[attr("data-group", AttrValue.Static(group.id))],
|
|
820
|
-
group.items.map((item) =>
|
|
821
|
-
createElementNs(
|
|
822
|
-
svgNs(),
|
|
823
|
-
"rect",
|
|
824
|
-
[attr("data-item", AttrValue.Static(item))],
|
|
825
|
-
[]
|
|
826
|
-
)
|
|
827
|
-
)
|
|
828
|
-
)
|
|
829
|
-
);
|
|
830
|
-
|
|
831
|
-
const svg = createElementNs(svgNs(), "svg", [], [forEachNode]);
|
|
832
|
-
render(container, svg);
|
|
833
|
-
|
|
834
|
-
const g1 = container.querySelector('[data-group="g1"]');
|
|
835
|
-
const g2 = container.querySelector('[data-group="g2"]');
|
|
836
|
-
expect(g1?.querySelectorAll("rect").length).toBe(2);
|
|
837
|
-
expect(g2?.querySelectorAll("rect").length).toBe(2);
|
|
838
|
-
|
|
839
|
-
// Update groups
|
|
840
|
-
setGroups([{ id: "g1", items: ["a", "b", "c"] }]);
|
|
841
|
-
expect(container.querySelectorAll("g").length).toBe(1);
|
|
842
|
-
expect(container.querySelectorAll("rect").length).toBe(3);
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
test("forEach handles empty to non-empty transition in SVG", () => {
|
|
846
|
-
const [items, setItems] = createSignal<{ id: string }[]>([]);
|
|
847
|
-
|
|
848
|
-
const forEachNode = forEach(
|
|
849
|
-
items,
|
|
850
|
-
(item) =>
|
|
851
|
-
createElementNs(
|
|
852
|
-
svgNs(),
|
|
853
|
-
"circle",
|
|
854
|
-
[attr("data-id", AttrValue.Static(item.id))],
|
|
855
|
-
[]
|
|
856
|
-
)
|
|
857
|
-
);
|
|
858
|
-
|
|
859
|
-
const svg = createElementNs(svgNs(), "svg", [], [forEachNode]);
|
|
860
|
-
render(container, svg);
|
|
861
|
-
|
|
862
|
-
expect(container.querySelectorAll("circle").length).toBe(0);
|
|
863
|
-
|
|
864
|
-
// Add items
|
|
865
|
-
setItems([{ id: "1" }, { id: "2" }]);
|
|
866
|
-
expect(container.querySelectorAll("circle").length).toBe(2);
|
|
867
|
-
|
|
868
|
-
// Clear again
|
|
869
|
-
setItems([]);
|
|
870
|
-
expect(container.querySelectorAll("circle").length).toBe(0);
|
|
871
|
-
});
|
|
872
|
-
});
|
|
873
|
-
});
|