@luna_ui/luna 0.7.3 → 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.
Files changed (83) hide show
  1. package/dist/api-DAWeanTX.js +1 -0
  2. package/dist/api-qXll116-.d.ts +80 -0
  3. package/dist/cli.mjs +27 -22
  4. package/dist/css/index.js +1 -0
  5. package/dist/event-utils.d.ts +1 -1
  6. package/dist/event-utils.js +1 -1
  7. package/dist/{index-vO066aMd.d.ts → index-VY8G32hr.d.ts} +16 -76
  8. package/dist/index.d.ts +4 -3
  9. package/dist/index.js +1 -1
  10. package/dist/jsx-dev-runtime.js +1 -1
  11. package/dist/jsx-runtime.d.ts +1 -1
  12. package/dist/jsx-runtime.js +1 -1
  13. package/dist/raw.d.ts +2 -0
  14. package/dist/raw.js +1 -0
  15. package/dist/resource.d.ts +41 -0
  16. package/dist/resource.js +1 -0
  17. package/dist/router-lite.d.ts +44 -0
  18. package/dist/router-lite.js +1 -0
  19. package/dist/signals-shared.d.ts +12 -0
  20. package/dist/signals-shared.js +1 -0
  21. package/dist/signals.d.ts +50 -0
  22. package/dist/signals.js +1 -0
  23. package/dist/vite-plugin.d.ts +7708 -2
  24. package/dist/vite-plugin.js +7 -6
  25. package/package.json +34 -11
  26. package/dist/event-utils-C_M2XBNj.js +0 -1
  27. package/dist/src-BbjOW18q.js +0 -1
  28. package/src/css/extract.ts +0 -798
  29. package/src/css/index.ts +0 -10
  30. package/src/css/inject.ts +0 -205
  31. package/src/css/inline.ts +0 -182
  32. package/src/css/minify.ts +0 -70
  33. package/src/css/optimizer.ts +0 -6
  34. package/src/css/runtime.ts +0 -344
  35. package/src/css-optimizer/README.md +0 -353
  36. package/src/css-optimizer/cooccurrence.ts +0 -100
  37. package/src/css-optimizer/core.ts +0 -263
  38. package/src/css-optimizer/extractors.ts +0 -243
  39. package/src/css-optimizer/hash.ts +0 -54
  40. package/src/css-optimizer/index.ts +0 -129
  41. package/src/css-optimizer/merge.ts +0 -109
  42. package/src/css-optimizer/moonbit-analyzer.ts +0 -210
  43. package/src/css-optimizer/parser.ts +0 -120
  44. package/src/css-optimizer/pattern.ts +0 -171
  45. package/src/css-optimizer/transformers.ts +0 -301
  46. package/src/css-optimizer/types.ts +0 -128
  47. package/src/event-utils.ts +0 -227
  48. package/src/hydration/createHydrator.ts +0 -62
  49. package/src/hydration/delegate.ts +0 -62
  50. package/src/hydration/drag.ts +0 -214
  51. package/src/hydration/index.ts +0 -12
  52. package/src/hydration/keyboard.ts +0 -64
  53. package/src/hydration/toggle.ts +0 -101
  54. package/src/index.ts +0 -908
  55. package/src/jsx-dev-runtime.ts +0 -2
  56. package/src/jsx-runtime.ts +0 -398
  57. package/src/vite-plugin.ts +0 -718
  58. package/tests/__screenshots__/apg.test.ts/APG-Components---Accessibility-Tests-Button-Pattern-disabled-button-has-aria-disabled-1.png +0 -0
  59. package/tests/apg.test.ts +0 -466
  60. package/tests/context.test.ts +0 -118
  61. package/tests/css-optimizer-extractors.test.ts +0 -264
  62. package/tests/css-optimizer-integration.test.ts +0 -566
  63. package/tests/css-optimizer-transformers.test.ts +0 -301
  64. package/tests/css-optimizer.test.ts +0 -646
  65. package/tests/css-runtime.bench.ts +0 -442
  66. package/tests/css-runtime.test.ts +0 -342
  67. package/tests/debounced.test.ts +0 -165
  68. package/tests/dom.test.ts +0 -873
  69. package/tests/integration.test.ts +0 -405
  70. package/tests/issue-11-show-null-to-truthy.test.ts +0 -176
  71. package/tests/issue-5-for-infinite-loop.test.ts +0 -516
  72. package/tests/jsx-runtime.test.tsx +0 -393
  73. package/tests/lifecycle.test.ts +0 -833
  74. package/tests/move-before.bench.ts +0 -304
  75. package/tests/preact-signals-comparison.test.ts +0 -1608
  76. package/tests/resource.test.ts +0 -160
  77. package/tests/router.test.ts +0 -117
  78. package/tests/show-initial-mount-leak.test.tsx +0 -182
  79. package/tests/solidjs-api.test.ts +0 -660
  80. package/tests/static-perf.bench.ts +0 -64
  81. package/tests/store.test.ts +0 -263
  82. package/tests/tsx-syntax.test.tsx +0 -404
  83. /package/dist/{event-utils-Cd5f3Njd.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
- });