@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.
- 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-vO066aMd.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 +50 -0
- package/dist/signals.js +1 -0
- package/dist/vite-plugin.d.ts +7708 -2
- package/dist/vite-plugin.js +7 -6
- package/package.json +34 -11
- package/dist/event-utils-C_M2XBNj.js +0 -1
- package/dist/src-BbjOW18q.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/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/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 -160
- 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-Cd5f3Njd.d.ts → event-utils-BvAf0NwN.d.ts} +0 -0
|
@@ -1,660 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for SolidJS-compatible API utilities
|
|
3
|
-
*/
|
|
4
|
-
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
5
|
-
import {
|
|
6
|
-
createSignal,
|
|
7
|
-
createRenderEffect,
|
|
8
|
-
createMemo,
|
|
9
|
-
createRoot,
|
|
10
|
-
on,
|
|
11
|
-
mergeProps,
|
|
12
|
-
splitProps,
|
|
13
|
-
Provider,
|
|
14
|
-
Index,
|
|
15
|
-
Switch,
|
|
16
|
-
Match,
|
|
17
|
-
Portal,
|
|
18
|
-
portalToBody,
|
|
19
|
-
portalToSelector,
|
|
20
|
-
createContext,
|
|
21
|
-
useContext,
|
|
22
|
-
createElement,
|
|
23
|
-
text,
|
|
24
|
-
mount,
|
|
25
|
-
For,
|
|
26
|
-
} from "../src/index";
|
|
27
|
-
|
|
28
|
-
// MoonBit AttrValue constructors
|
|
29
|
-
const AttrValue = {
|
|
30
|
-
Static: (value: string) => ({ $tag: 0, _0: value }),
|
|
31
|
-
Dynamic: (getter: () => string) => ({ $tag: 1, _0: getter }),
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
function attr(name: string, value: unknown) {
|
|
35
|
-
return { _0: name, _1: value };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
describe("on() utility", () => {
|
|
39
|
-
test("on tracks single dependency", () => {
|
|
40
|
-
const results: [number, number | undefined][] = [];
|
|
41
|
-
|
|
42
|
-
createRoot((dispose) => {
|
|
43
|
-
const [count, setCount] = createSignal(0);
|
|
44
|
-
|
|
45
|
-
createRenderEffect(
|
|
46
|
-
on(count, (value, prev) => {
|
|
47
|
-
results.push([value, prev]);
|
|
48
|
-
})
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
setCount(1);
|
|
52
|
-
setCount(2);
|
|
53
|
-
dispose();
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
expect(results).toEqual([
|
|
57
|
-
[0, undefined],
|
|
58
|
-
[1, 0],
|
|
59
|
-
[2, 1],
|
|
60
|
-
]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("on tracks multiple dependencies", () => {
|
|
64
|
-
const results: [[number, string], [number, string] | undefined][] = [];
|
|
65
|
-
|
|
66
|
-
createRoot((dispose) => {
|
|
67
|
-
const [a, setA] = createSignal(1);
|
|
68
|
-
const [b, setB] = createSignal("x");
|
|
69
|
-
|
|
70
|
-
createRenderEffect(
|
|
71
|
-
on([a, b], (values, prev) => {
|
|
72
|
-
results.push([values as [number, string], prev as [number, string] | undefined]);
|
|
73
|
-
})
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
setA(2);
|
|
77
|
-
setB("y");
|
|
78
|
-
dispose();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
expect(results).toEqual([
|
|
82
|
-
[[1, "x"], undefined],
|
|
83
|
-
[[2, "x"], [1, "x"]],
|
|
84
|
-
[[2, "y"], [2, "x"]],
|
|
85
|
-
]);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("on with defer option skips initial run", () => {
|
|
89
|
-
const results: number[] = [];
|
|
90
|
-
|
|
91
|
-
createRoot((dispose) => {
|
|
92
|
-
const [count, setCount] = createSignal(0);
|
|
93
|
-
|
|
94
|
-
createRenderEffect(
|
|
95
|
-
on(
|
|
96
|
-
count,
|
|
97
|
-
(value) => {
|
|
98
|
-
results.push(value);
|
|
99
|
-
},
|
|
100
|
-
{ defer: true }
|
|
101
|
-
)
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
// Initial run should be skipped
|
|
105
|
-
expect(results).toEqual([]);
|
|
106
|
-
|
|
107
|
-
setCount(1);
|
|
108
|
-
expect(results).toEqual([1]);
|
|
109
|
-
|
|
110
|
-
setCount(2);
|
|
111
|
-
expect(results).toEqual([1, 2]);
|
|
112
|
-
|
|
113
|
-
dispose();
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe("mergeProps()", () => {
|
|
119
|
-
test("merges simple props", () => {
|
|
120
|
-
const a = { foo: 1, bar: 2 };
|
|
121
|
-
const b = { bar: 3, baz: 4 };
|
|
122
|
-
const result = mergeProps(a, b);
|
|
123
|
-
|
|
124
|
-
expect(result).toEqual({ foo: 1, bar: 3, baz: 4 });
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("merges event handlers", () => {
|
|
128
|
-
const calls: string[] = [];
|
|
129
|
-
const a = { onClick: () => calls.push("a") };
|
|
130
|
-
const b = { onClick: () => calls.push("b") };
|
|
131
|
-
const result = mergeProps(a, b) as { onClick: () => void };
|
|
132
|
-
|
|
133
|
-
result.onClick();
|
|
134
|
-
expect(calls).toEqual(["a", "b"]);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("merges ref callbacks", () => {
|
|
138
|
-
const refs: string[] = [];
|
|
139
|
-
const a = { ref: (el: string) => refs.push(`a:${el}`) };
|
|
140
|
-
const b = { ref: (el: string) => refs.push(`b:${el}`) };
|
|
141
|
-
const result = mergeProps(a, b) as { ref: (el: string) => void };
|
|
142
|
-
|
|
143
|
-
result.ref("element");
|
|
144
|
-
expect(refs).toEqual(["a:element", "b:element"]);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("merges class names", () => {
|
|
148
|
-
const a = { class: "foo" };
|
|
149
|
-
const b = { class: "bar" };
|
|
150
|
-
const result = mergeProps(a, b);
|
|
151
|
-
|
|
152
|
-
expect(result.class).toBe("foo bar");
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test("merges className", () => {
|
|
156
|
-
const a = { className: "foo" };
|
|
157
|
-
const b = { className: "bar" };
|
|
158
|
-
const result = mergeProps(a, b);
|
|
159
|
-
|
|
160
|
-
expect(result.className).toBe("foo bar");
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("merges style objects", () => {
|
|
164
|
-
const a = { style: { color: "red", margin: "10px" } } as const;
|
|
165
|
-
const b = { style: { color: "blue", padding: "5px" } } as const;
|
|
166
|
-
const result = mergeProps<{ style: Record<string, string> }>(a as any, b as any);
|
|
167
|
-
|
|
168
|
-
expect(result.style).toEqual({ color: "blue", margin: "10px", padding: "5px" });
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("handles undefined sources", () => {
|
|
172
|
-
const a = { foo: 1 };
|
|
173
|
-
const result = mergeProps<{ foo?: number; bar?: number }>(undefined, a, undefined, { bar: 2 });
|
|
174
|
-
|
|
175
|
-
expect(result).toEqual({ foo: 1, bar: 2 });
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
describe("splitProps()", () => {
|
|
180
|
-
test("splits props into specified groups", () => {
|
|
181
|
-
const props = { a: 1, b: 2, c: 3, d: 4 };
|
|
182
|
-
const [ab, cd] = splitProps(props, ["a", "b"]);
|
|
183
|
-
|
|
184
|
-
expect(ab).toEqual({ a: 1, b: 2 });
|
|
185
|
-
expect(cd).toEqual({ c: 3, d: 4 });
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("splits props into multiple groups", () => {
|
|
189
|
-
const props = { a: 1, b: 2, c: 3, d: 4, e: 5 };
|
|
190
|
-
const [group1, group2, rest] = splitProps(props, ["a", "b"], ["c"]);
|
|
191
|
-
|
|
192
|
-
expect(group1).toEqual({ a: 1, b: 2 });
|
|
193
|
-
expect(group2).toEqual({ c: 3 });
|
|
194
|
-
expect(rest).toEqual({ d: 4, e: 5 });
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("handles missing keys", () => {
|
|
198
|
-
const props = { a: 1, b: 2 };
|
|
199
|
-
const [group, rest] = splitProps(props, ["a", "c"] as (keyof typeof props)[]);
|
|
200
|
-
|
|
201
|
-
expect(group).toEqual({ a: 1 });
|
|
202
|
-
expect(rest).toEqual({ b: 2 });
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
describe("Provider component", () => {
|
|
207
|
-
let container: HTMLElement;
|
|
208
|
-
|
|
209
|
-
beforeEach(() => {
|
|
210
|
-
container = document.createElement("div");
|
|
211
|
-
document.body.appendChild(container);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test("Provider provides context value", () => {
|
|
215
|
-
const themeCtx = createContext("light");
|
|
216
|
-
let capturedTheme = "";
|
|
217
|
-
|
|
218
|
-
const Child = () => {
|
|
219
|
-
capturedTheme = useContext(themeCtx);
|
|
220
|
-
return text(capturedTheme);
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
Provider({
|
|
224
|
-
context: themeCtx,
|
|
225
|
-
value: "dark",
|
|
226
|
-
children: () => {
|
|
227
|
-
mount(container, Child());
|
|
228
|
-
return text("");
|
|
229
|
-
},
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
expect(capturedTheme).toBe("dark");
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
test("Provider works with function children", () => {
|
|
236
|
-
const countCtx = createContext(0);
|
|
237
|
-
let capturedCount = -1;
|
|
238
|
-
|
|
239
|
-
Provider({
|
|
240
|
-
context: countCtx,
|
|
241
|
-
value: 42,
|
|
242
|
-
children: () => {
|
|
243
|
-
capturedCount = useContext(countCtx);
|
|
244
|
-
return text(String(capturedCount));
|
|
245
|
-
},
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
expect(capturedCount).toBe(42);
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
describe("Index component", () => {
|
|
253
|
-
let container: HTMLElement;
|
|
254
|
-
|
|
255
|
-
beforeEach(() => {
|
|
256
|
-
container = document.createElement("div");
|
|
257
|
-
document.body.appendChild(container);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test("Index renders list with item getters", () => {
|
|
261
|
-
const [items] = createSignal(["a", "b", "c"]);
|
|
262
|
-
|
|
263
|
-
const node = Index({
|
|
264
|
-
each: items,
|
|
265
|
-
children: (itemGetter, index) =>
|
|
266
|
-
createElement(
|
|
267
|
-
"span",
|
|
268
|
-
[attr("data-index", AttrValue.Static(String(index)))],
|
|
269
|
-
[text(itemGetter())]
|
|
270
|
-
),
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
mount(container, node);
|
|
274
|
-
|
|
275
|
-
const spans = container.querySelectorAll("span");
|
|
276
|
-
expect(spans.length).toBe(3);
|
|
277
|
-
expect(spans[0].textContent).toBe("a");
|
|
278
|
-
expect(spans[1].textContent).toBe("b");
|
|
279
|
-
expect(spans[2].textContent).toBe("c");
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
test("Index provides working item getter", () => {
|
|
283
|
-
const values: string[] = [];
|
|
284
|
-
const [items] = createSignal(["x", "y"]);
|
|
285
|
-
|
|
286
|
-
Index({
|
|
287
|
-
each: items,
|
|
288
|
-
children: (itemGetter, _index) => {
|
|
289
|
-
values.push(itemGetter());
|
|
290
|
-
return text(itemGetter());
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
expect(values).toEqual(["x", "y"]);
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
describe("Switch/Match components", () => {
|
|
299
|
-
let container: HTMLElement;
|
|
300
|
-
|
|
301
|
-
beforeEach(() => {
|
|
302
|
-
container = document.createElement("div");
|
|
303
|
-
document.body.appendChild(container);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
afterEach(() => {
|
|
307
|
-
container.remove();
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
test("Switch renders first truthy Match", () => {
|
|
311
|
-
const [value, setValue] = createSignal("a");
|
|
312
|
-
|
|
313
|
-
let rendered = "";
|
|
314
|
-
createRoot((dispose) => {
|
|
315
|
-
const result = Switch({
|
|
316
|
-
fallback: text("none"),
|
|
317
|
-
children: [
|
|
318
|
-
Match({
|
|
319
|
-
when: () => value() === "a",
|
|
320
|
-
children: text("matched-a"),
|
|
321
|
-
}),
|
|
322
|
-
Match({
|
|
323
|
-
when: () => value() === "b",
|
|
324
|
-
children: text("matched-b"),
|
|
325
|
-
}),
|
|
326
|
-
],
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// result should be the matched content
|
|
330
|
-
rendered = result ? "has-result" : "no-result";
|
|
331
|
-
dispose();
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
expect(rendered).toBe("has-result");
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
test("Switch updates DOM when signal changes", () => {
|
|
338
|
-
const [value, setValue] = createSignal("a");
|
|
339
|
-
|
|
340
|
-
createRoot((dispose) => {
|
|
341
|
-
const node = Switch({
|
|
342
|
-
fallback: text("fallback"),
|
|
343
|
-
children: [
|
|
344
|
-
Match({
|
|
345
|
-
when: () => value() === "a",
|
|
346
|
-
children: text("A"),
|
|
347
|
-
}),
|
|
348
|
-
Match({
|
|
349
|
-
when: () => value() === "b",
|
|
350
|
-
children: text("B"),
|
|
351
|
-
}),
|
|
352
|
-
],
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
mount(container, node);
|
|
356
|
-
expect(container.textContent).toBe("A");
|
|
357
|
-
|
|
358
|
-
setValue("b");
|
|
359
|
-
expect(container.textContent).toBe("B");
|
|
360
|
-
|
|
361
|
-
setValue("c");
|
|
362
|
-
expect(container.textContent).toBe("fallback");
|
|
363
|
-
|
|
364
|
-
setValue("a");
|
|
365
|
-
expect(container.textContent).toBe("A");
|
|
366
|
-
|
|
367
|
-
dispose();
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
test("Switch returns fallback when no match", () => {
|
|
372
|
-
const [value] = createSignal("c");
|
|
373
|
-
|
|
374
|
-
const result = Switch({
|
|
375
|
-
fallback: text("fallback"),
|
|
376
|
-
children: [
|
|
377
|
-
Match({
|
|
378
|
-
when: () => value() === "a",
|
|
379
|
-
children: text("a"),
|
|
380
|
-
}),
|
|
381
|
-
Match({
|
|
382
|
-
when: () => value() === "b",
|
|
383
|
-
children: text("b"),
|
|
384
|
-
}),
|
|
385
|
-
],
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
// Result should be fallback text node
|
|
389
|
-
expect(result).toBeDefined();
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
test("Match with function children receives accessor (SolidJS-style)", () => {
|
|
393
|
-
const [user, setUser] = createSignal<{ name: string } | null>(null);
|
|
394
|
-
let receivedName = "";
|
|
395
|
-
|
|
396
|
-
createRoot((dispose) => {
|
|
397
|
-
const match = Match({
|
|
398
|
-
when: user,
|
|
399
|
-
// SolidJS-style: children receives accessor function, call with ()
|
|
400
|
-
children: (userAccessor: () => { name: string }) => {
|
|
401
|
-
receivedName = userAccessor().name;
|
|
402
|
-
return text(userAccessor().name);
|
|
403
|
-
},
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
// First, user is null, so match.when() should be false
|
|
407
|
-
expect(match.when()).toBe(false);
|
|
408
|
-
|
|
409
|
-
setUser({ name: "Alice" });
|
|
410
|
-
expect(match.when()).toBe(true);
|
|
411
|
-
|
|
412
|
-
// Call children with the value
|
|
413
|
-
if (match.when() && typeof match.children === "function") {
|
|
414
|
-
match.children();
|
|
415
|
-
expect(receivedName).toBe("Alice");
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
dispose();
|
|
419
|
-
});
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
describe("SolidJS API compatibility", () => {
|
|
424
|
-
test("createSignal returns tuple [getter, setter]", () => {
|
|
425
|
-
const [count, setCount] = createSignal(0);
|
|
426
|
-
|
|
427
|
-
expect(typeof count).toBe("function");
|
|
428
|
-
expect(typeof setCount).toBe("function");
|
|
429
|
-
expect(count()).toBe(0);
|
|
430
|
-
|
|
431
|
-
setCount(5);
|
|
432
|
-
expect(count()).toBe(5);
|
|
433
|
-
|
|
434
|
-
setCount((c) => c + 1);
|
|
435
|
-
expect(count()).toBe(6);
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
test("createMemo returns accessor", () => {
|
|
439
|
-
const [count, setCount] = createSignal(2);
|
|
440
|
-
const doubled = createMemo(() => count() * 2);
|
|
441
|
-
|
|
442
|
-
expect(typeof doubled).toBe("function");
|
|
443
|
-
expect(doubled()).toBe(4);
|
|
444
|
-
|
|
445
|
-
setCount(5);
|
|
446
|
-
expect(doubled()).toBe(10);
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
test("createRenderEffect tracks dependencies automatically", () => {
|
|
450
|
-
const values: number[] = [];
|
|
451
|
-
|
|
452
|
-
createRoot((dispose) => {
|
|
453
|
-
const [count, setCount] = createSignal(0);
|
|
454
|
-
|
|
455
|
-
createRenderEffect(() => {
|
|
456
|
-
values.push(count());
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
setCount(1);
|
|
460
|
-
setCount(2);
|
|
461
|
-
dispose();
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
expect(values).toEqual([0, 1, 2]);
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
describe("Portal component", () => {
|
|
469
|
-
let container: HTMLElement;
|
|
470
|
-
let portalTarget: HTMLElement;
|
|
471
|
-
|
|
472
|
-
beforeEach(() => {
|
|
473
|
-
container = document.createElement("div");
|
|
474
|
-
container.id = "test-container";
|
|
475
|
-
document.body.appendChild(container);
|
|
476
|
-
|
|
477
|
-
portalTarget = document.createElement("div");
|
|
478
|
-
portalTarget.id = "portal-target";
|
|
479
|
-
document.body.appendChild(portalTarget);
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
afterEach(() => {
|
|
483
|
-
container.remove();
|
|
484
|
-
portalTarget.remove();
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
test("Portal renders children to body by default", () => {
|
|
488
|
-
const placeholder = Portal({
|
|
489
|
-
children: () => createElement("div", [attr("id", AttrValue.Static("portal-content"))], [text("Portal content")]),
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
// Placeholder should be returned
|
|
493
|
-
expect(placeholder).toBeDefined();
|
|
494
|
-
|
|
495
|
-
// Content should be in body
|
|
496
|
-
const rendered = document.getElementById("portal-content");
|
|
497
|
-
expect(rendered).not.toBeNull();
|
|
498
|
-
expect(rendered?.textContent).toBe("Portal content");
|
|
499
|
-
|
|
500
|
-
// Clean up
|
|
501
|
-
rendered?.remove();
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
test("Portal renders to selector mount target", () => {
|
|
505
|
-
Portal({
|
|
506
|
-
mount: "#portal-target",
|
|
507
|
-
children: () => createElement("div", [attr("id", AttrValue.Static("selector-portal"))], [text("Selector portal")]),
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
// Content should be in the portal target
|
|
511
|
-
const rendered = portalTarget.querySelector("#selector-portal");
|
|
512
|
-
expect(rendered).not.toBeNull();
|
|
513
|
-
expect(rendered?.textContent).toBe("Selector portal");
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
test("portalToBody low-level API works", () => {
|
|
517
|
-
const content = createElement("div", [attr("id", AttrValue.Static("low-level-portal"))], [text("Low level")]);
|
|
518
|
-
|
|
519
|
-
portalToBody([content]);
|
|
520
|
-
|
|
521
|
-
const rendered = document.getElementById("low-level-portal");
|
|
522
|
-
expect(rendered).not.toBeNull();
|
|
523
|
-
expect(rendered?.textContent).toBe("Low level");
|
|
524
|
-
|
|
525
|
-
rendered?.remove();
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
test("portalToSelector low-level API works", () => {
|
|
529
|
-
const content = createElement("div", [attr("class", AttrValue.Static("selector-content"))], [text("Selector content")]);
|
|
530
|
-
|
|
531
|
-
portalToSelector("#portal-target", [content]);
|
|
532
|
-
|
|
533
|
-
const rendered = portalTarget.querySelector(".selector-content");
|
|
534
|
-
expect(rendered).not.toBeNull();
|
|
535
|
-
expect(rendered?.textContent).toBe("Selector content");
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
test("Portal accepts function children", () => {
|
|
539
|
-
const content = () => createElement("span", [attr("id", AttrValue.Static("func-portal"))], [text("Function child")]);
|
|
540
|
-
|
|
541
|
-
Portal({
|
|
542
|
-
children: content,
|
|
543
|
-
});
|
|
544
|
-
|
|
545
|
-
const rendered = document.getElementById("func-portal");
|
|
546
|
-
expect(rendered).not.toBeNull();
|
|
547
|
-
expect(rendered?.textContent).toBe("Function child");
|
|
548
|
-
|
|
549
|
-
rendered?.remove();
|
|
550
|
-
});
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
describe("Switch/Match component", () => {
|
|
554
|
-
let container: HTMLElement;
|
|
555
|
-
|
|
556
|
-
beforeEach(() => {
|
|
557
|
-
container = document.createElement("div");
|
|
558
|
-
document.body.appendChild(container);
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
afterEach(() => {
|
|
562
|
-
container.remove();
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
test("Switch with single Match renders correctly", () => {
|
|
566
|
-
createRoot((dispose) => {
|
|
567
|
-
const [value, setValue] = createSignal(true);
|
|
568
|
-
|
|
569
|
-
const node = Switch({
|
|
570
|
-
children: Match({
|
|
571
|
-
when: value,
|
|
572
|
-
children: createElement("div", [], [text("Match 1")]),
|
|
573
|
-
}),
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
mount(container, node);
|
|
577
|
-
expect(container.textContent).toContain("Match 1");
|
|
578
|
-
dispose();
|
|
579
|
-
});
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
test("Switch with single Match and fallback", () => {
|
|
583
|
-
createRoot((dispose) => {
|
|
584
|
-
const [value, setValue] = createSignal(false);
|
|
585
|
-
|
|
586
|
-
const node = Switch({
|
|
587
|
-
fallback: createElement("div", [], [text("Fallback")]),
|
|
588
|
-
children: Match({
|
|
589
|
-
when: value,
|
|
590
|
-
children: createElement("div", [], [text("Match 1")]),
|
|
591
|
-
}),
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
mount(container, node);
|
|
595
|
-
expect(container.textContent).toContain("Fallback");
|
|
596
|
-
|
|
597
|
-
// Trigger update
|
|
598
|
-
setValue(true);
|
|
599
|
-
expect(container.textContent).toContain("Match 1");
|
|
600
|
-
dispose();
|
|
601
|
-
});
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
test("Switch with multiple Match components", () => {
|
|
605
|
-
createRoot((dispose) => {
|
|
606
|
-
const [value, setValue] = createSignal(1);
|
|
607
|
-
|
|
608
|
-
const node = Switch({
|
|
609
|
-
fallback: createElement("div", [], [text("Fallback")]),
|
|
610
|
-
children: [
|
|
611
|
-
Match({
|
|
612
|
-
when: () => value() === 1,
|
|
613
|
-
children: createElement("div", [], [text("First")]),
|
|
614
|
-
}),
|
|
615
|
-
Match({
|
|
616
|
-
when: () => value() === 2,
|
|
617
|
-
children: createElement("div", [], [text("Second")]),
|
|
618
|
-
}),
|
|
619
|
-
],
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
mount(container, node);
|
|
623
|
-
expect(container.textContent).toContain("First");
|
|
624
|
-
|
|
625
|
-
setValue(2);
|
|
626
|
-
expect(container.textContent).toContain("Second");
|
|
627
|
-
|
|
628
|
-
setValue(3);
|
|
629
|
-
expect(container.textContent).toContain("Fallback");
|
|
630
|
-
dispose();
|
|
631
|
-
});
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
test("Switch with accessor condition in Match", () => {
|
|
635
|
-
createRoot((dispose) => {
|
|
636
|
-
const [count, setCount] = createSignal(0);
|
|
637
|
-
|
|
638
|
-
const node = Switch({
|
|
639
|
-
fallback: createElement("div", [], [text("Default")]),
|
|
640
|
-
children: Match({
|
|
641
|
-
when: () => count() % 3 === 0 && count() > 0,
|
|
642
|
-
children: createElement("div", [], [text("Bang!")]),
|
|
643
|
-
}),
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
mount(container, node);
|
|
647
|
-
expect(container.textContent).toContain("Default");
|
|
648
|
-
|
|
649
|
-
setCount(3);
|
|
650
|
-
expect(container.textContent).toContain("Bang!");
|
|
651
|
-
|
|
652
|
-
setCount(4);
|
|
653
|
-
expect(container.textContent).toContain("Default");
|
|
654
|
-
|
|
655
|
-
setCount(6);
|
|
656
|
-
expect(container.textContent).toContain("Bang!");
|
|
657
|
-
dispose();
|
|
658
|
-
});
|
|
659
|
-
});
|
|
660
|
-
});
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { bench, describe } from "vitest";
|
|
2
|
-
|
|
3
|
-
// Generate HTML for list items
|
|
4
|
-
function generateListHTML(count: number): string {
|
|
5
|
-
let html = "<ul>";
|
|
6
|
-
for (let i = 0; i < count; i++) {
|
|
7
|
-
html += `<li>Item ${i}</li>`;
|
|
8
|
-
}
|
|
9
|
-
html += "</ul>";
|
|
10
|
-
return html;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Generate DOM using createElement (like render_vnode_to_dom)
|
|
14
|
-
function createListDOM(count: number): Node {
|
|
15
|
-
const ul = document.createElement("ul");
|
|
16
|
-
for (let i = 0; i < count; i++) {
|
|
17
|
-
const li = document.createElement("li");
|
|
18
|
-
li.textContent = `Item ${i}`;
|
|
19
|
-
ul.appendChild(li);
|
|
20
|
-
}
|
|
21
|
-
return ul;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
describe("Static Content: innerHTML vs createElement (Real Browser)", () => {
|
|
25
|
-
const html100 = generateListHTML(100);
|
|
26
|
-
const html500 = generateListHTML(500);
|
|
27
|
-
const html1000 = generateListHTML(1000);
|
|
28
|
-
|
|
29
|
-
describe("100 items", () => {
|
|
30
|
-
bench("createElement (render_vnode_to_dom style)", () => {
|
|
31
|
-
const container = document.createElement("div");
|
|
32
|
-
container.appendChild(createListDOM(100));
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
bench("innerHTML (inject_static style)", () => {
|
|
36
|
-
const container = document.createElement("div");
|
|
37
|
-
container.innerHTML = html100;
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe("500 items", () => {
|
|
42
|
-
bench("createElement (render_vnode_to_dom style)", () => {
|
|
43
|
-
const container = document.createElement("div");
|
|
44
|
-
container.appendChild(createListDOM(500));
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
bench("innerHTML (inject_static style)", () => {
|
|
48
|
-
const container = document.createElement("div");
|
|
49
|
-
container.innerHTML = html500;
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe("1000 items", () => {
|
|
54
|
-
bench("createElement (render_vnode_to_dom style)", () => {
|
|
55
|
-
const container = document.createElement("div");
|
|
56
|
-
container.appendChild(createListDOM(1000));
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
bench("innerHTML (inject_static style)", () => {
|
|
60
|
-
const container = document.createElement("div");
|
|
61
|
-
container.innerHTML = html1000;
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
});
|