@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,405 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Integration tests for nested components, ForEach, and provide
|
|
3
|
-
* These tests document current Luna API behavior and highlight differences from SolidJS
|
|
4
|
-
*/
|
|
5
|
-
import { describe, test, expect, beforeEach } from "vitest";
|
|
6
|
-
import {
|
|
7
|
-
// Signal API (SolidJS-style)
|
|
8
|
-
createSignal,
|
|
9
|
-
createEffect,
|
|
10
|
-
createRoot,
|
|
11
|
-
// Context API
|
|
12
|
-
createContext,
|
|
13
|
-
provide,
|
|
14
|
-
useContext,
|
|
15
|
-
// DOM API
|
|
16
|
-
createElement,
|
|
17
|
-
text,
|
|
18
|
-
render,
|
|
19
|
-
mount,
|
|
20
|
-
forEach,
|
|
21
|
-
show,
|
|
22
|
-
} from "../src/index";
|
|
23
|
-
|
|
24
|
-
// MoonBit AttrValue constructors
|
|
25
|
-
const AttrValue = {
|
|
26
|
-
Static: (value: string) => ({ $tag: 0, _0: value }),
|
|
27
|
-
Dynamic: (getter: () => string) => ({ $tag: 1, _0: getter }),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
function attr(name: string, value: unknown) {
|
|
31
|
-
return { _0: name, _1: value };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe("Integration: Nested Components with Context", () => {
|
|
35
|
-
let container: HTMLElement;
|
|
36
|
-
|
|
37
|
-
beforeEach(() => {
|
|
38
|
-
container = document.createElement("div");
|
|
39
|
-
document.body.appendChild(container);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe("Context across component boundaries", () => {
|
|
43
|
-
test("context flows through nested function components", () => {
|
|
44
|
-
const themeCtx = createContext("light");
|
|
45
|
-
const values: string[] = [];
|
|
46
|
-
|
|
47
|
-
// Simulate nested component structure
|
|
48
|
-
const GrandChild = () => {
|
|
49
|
-
values.push(`grandchild: ${useContext(themeCtx)}`);
|
|
50
|
-
return text(useContext(themeCtx));
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const Child = () => {
|
|
54
|
-
values.push(`child: ${useContext(themeCtx)}`);
|
|
55
|
-
return createElement("span", [], [GrandChild()]);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const Parent = () => {
|
|
59
|
-
values.push(`parent: ${useContext(themeCtx)}`);
|
|
60
|
-
return createElement("div", [], [Child()]);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// Provide dark theme at root
|
|
64
|
-
provide(themeCtx, "dark", () => {
|
|
65
|
-
render(container, Parent());
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
expect(values).toEqual([
|
|
69
|
-
"parent: dark",
|
|
70
|
-
"child: dark",
|
|
71
|
-
"grandchild: dark",
|
|
72
|
-
]);
|
|
73
|
-
expect(container.textContent).toBe("dark");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("nested provide overrides parent context", () => {
|
|
77
|
-
const themeCtx = createContext("light");
|
|
78
|
-
const values: string[] = [];
|
|
79
|
-
|
|
80
|
-
const Inner = () => {
|
|
81
|
-
values.push(useContext(themeCtx));
|
|
82
|
-
return text(useContext(themeCtx));
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const Middle = () => {
|
|
86
|
-
// Override context for children
|
|
87
|
-
return provide(themeCtx, "blue", () => {
|
|
88
|
-
values.push(`middle sees: ${useContext(themeCtx)}`);
|
|
89
|
-
return createElement("div", [], [Inner()]);
|
|
90
|
-
});
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const Outer = () => {
|
|
94
|
-
values.push(`outer sees: ${useContext(themeCtx)}`);
|
|
95
|
-
return createElement("div", [], [Middle()]);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
provide(themeCtx, "dark", () => {
|
|
99
|
-
render(container, Outer());
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
expect(values).toEqual([
|
|
103
|
-
"outer sees: dark",
|
|
104
|
-
"middle sees: blue",
|
|
105
|
-
"blue",
|
|
106
|
-
]);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("multiple contexts work independently", () => {
|
|
110
|
-
const themeCtx = createContext("light");
|
|
111
|
-
const langCtx = createContext("en");
|
|
112
|
-
const sizeCtx = createContext("medium");
|
|
113
|
-
|
|
114
|
-
let capturedTheme: string;
|
|
115
|
-
let capturedLang: string;
|
|
116
|
-
let capturedSize: string;
|
|
117
|
-
|
|
118
|
-
const DeepComponent = () => {
|
|
119
|
-
capturedTheme = useContext(themeCtx);
|
|
120
|
-
capturedLang = useContext(langCtx);
|
|
121
|
-
capturedSize = useContext(sizeCtx);
|
|
122
|
-
return text("deep");
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
provide(themeCtx, "dark", () => {
|
|
126
|
-
provide(langCtx, "ja", () => {
|
|
127
|
-
// sizeCtx not provided, should use default
|
|
128
|
-
render(container, DeepComponent());
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
expect(capturedTheme!).toBe("dark");
|
|
133
|
-
expect(capturedLang!).toBe("ja");
|
|
134
|
-
expect(capturedSize!).toBe("medium"); // default
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe("ForEach with reactive updates", () => {
|
|
139
|
-
test("forEach renders initial list", () => {
|
|
140
|
-
const [items] = createSignal(["a", "b", "c"]);
|
|
141
|
-
|
|
142
|
-
const List = () =>
|
|
143
|
-
createElement(
|
|
144
|
-
"ul",
|
|
145
|
-
[],
|
|
146
|
-
[
|
|
147
|
-
forEach(items, (item, index) =>
|
|
148
|
-
createElement(
|
|
149
|
-
"li",
|
|
150
|
-
[attr("data-index", AttrValue.Static(String(index)))],
|
|
151
|
-
[text(item)]
|
|
152
|
-
)
|
|
153
|
-
),
|
|
154
|
-
]
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
render(container, List());
|
|
158
|
-
|
|
159
|
-
const lis = container.querySelectorAll("li");
|
|
160
|
-
expect(lis.length).toBe(3);
|
|
161
|
-
expect(lis[0].textContent).toBe("a");
|
|
162
|
-
expect(lis[1].textContent).toBe("b");
|
|
163
|
-
expect(lis[2].textContent).toBe("c");
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("forEach updates when signal changes", () => {
|
|
167
|
-
const [items, setItems] = createSignal(["x", "y"]);
|
|
168
|
-
|
|
169
|
-
createRoot((dispose) => {
|
|
170
|
-
const List = () =>
|
|
171
|
-
createElement("ul", [], [
|
|
172
|
-
forEach(items, (item) => createElement("li", [], [text(item)])),
|
|
173
|
-
]);
|
|
174
|
-
|
|
175
|
-
render(container, List());
|
|
176
|
-
|
|
177
|
-
expect(container.querySelectorAll("li").length).toBe(2);
|
|
178
|
-
|
|
179
|
-
// Update the list
|
|
180
|
-
setItems(["x", "y", "z"]);
|
|
181
|
-
|
|
182
|
-
expect(container.querySelectorAll("li").length).toBe(3);
|
|
183
|
-
expect(container.querySelectorAll("li")[2].textContent).toBe("z");
|
|
184
|
-
|
|
185
|
-
dispose();
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test("forEach with object items", () => {
|
|
190
|
-
interface User {
|
|
191
|
-
id: number;
|
|
192
|
-
name: string;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const [users] = createSignal<User[]>([
|
|
196
|
-
{ id: 1, name: "Alice" },
|
|
197
|
-
{ id: 2, name: "Bob" },
|
|
198
|
-
]);
|
|
199
|
-
|
|
200
|
-
createRoot((dispose) => {
|
|
201
|
-
const UserList = () =>
|
|
202
|
-
createElement("div", [], [
|
|
203
|
-
forEach(users, (user) =>
|
|
204
|
-
createElement(
|
|
205
|
-
"div",
|
|
206
|
-
[attr("data-id", AttrValue.Static(String(user.id)))],
|
|
207
|
-
[text(user.name)]
|
|
208
|
-
)
|
|
209
|
-
),
|
|
210
|
-
]);
|
|
211
|
-
|
|
212
|
-
render(container, UserList());
|
|
213
|
-
|
|
214
|
-
const divs = container.querySelectorAll("[data-id]");
|
|
215
|
-
expect(divs.length).toBe(2);
|
|
216
|
-
expect(divs[0].textContent).toBe("Alice");
|
|
217
|
-
expect(divs[1].textContent).toBe("Bob");
|
|
218
|
-
|
|
219
|
-
dispose();
|
|
220
|
-
});
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
describe("Context + ForEach integration", () => {
|
|
225
|
-
test("forEach items can access context", () => {
|
|
226
|
-
const prefixCtx = createContext("");
|
|
227
|
-
const [items] = createSignal(["one", "two", "three"]);
|
|
228
|
-
const renderedTexts: string[] = [];
|
|
229
|
-
|
|
230
|
-
const ItemComponent = (item: string) => {
|
|
231
|
-
const prefix = useContext(prefixCtx);
|
|
232
|
-
const fullText = `${prefix}${item}`;
|
|
233
|
-
renderedTexts.push(fullText);
|
|
234
|
-
return createElement("span", [], [text(fullText)]);
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
provide(prefixCtx, "item-", () => {
|
|
238
|
-
const List = () =>
|
|
239
|
-
createElement("div", [], [
|
|
240
|
-
forEach(items, (item) => ItemComponent(item)),
|
|
241
|
-
]);
|
|
242
|
-
|
|
243
|
-
render(container, List());
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
expect(renderedTexts).toEqual(["item-one", "item-two", "item-three"]);
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
describe("Show (conditional rendering)", () => {
|
|
251
|
-
// NOTE: show() has a known limitation - it doesn't render initially when true
|
|
252
|
-
// because the effect runs before the placeholder is in the DOM.
|
|
253
|
-
// This is a design difference from SolidJS's <Show> component.
|
|
254
|
-
|
|
255
|
-
test("show hides when condition is false", () => {
|
|
256
|
-
const [visible] = createSignal(false);
|
|
257
|
-
|
|
258
|
-
const node = show(visible, () =>
|
|
259
|
-
createElement("span", [], [text("visible")])
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
mount(container, node);
|
|
263
|
-
|
|
264
|
-
// When false, show renders a placeholder (comment node)
|
|
265
|
-
expect(container.querySelector("span")).toBeNull();
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
test("show toggles from false to true", () => {
|
|
269
|
-
// Start with false, then toggle to true
|
|
270
|
-
const [visible, setVisible] = createSignal(false);
|
|
271
|
-
|
|
272
|
-
const node = show(visible, () =>
|
|
273
|
-
createElement("span", [], [text("content")])
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
mount(container, node);
|
|
277
|
-
|
|
278
|
-
expect(container.querySelector("span")).toBeNull();
|
|
279
|
-
|
|
280
|
-
// Toggle to true - this should work because placeholder is now in DOM
|
|
281
|
-
setVisible(true);
|
|
282
|
-
expect(container.querySelector("span")).not.toBeNull();
|
|
283
|
-
|
|
284
|
-
// Toggle back to false
|
|
285
|
-
setVisible(false);
|
|
286
|
-
expect(container.querySelector("span")).toBeNull();
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
test("show renders when initial condition is true", () => {
|
|
290
|
-
const [visible] = createSignal(true);
|
|
291
|
-
|
|
292
|
-
const node = show(visible, () =>
|
|
293
|
-
createElement("span", [], [text("visible")])
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
mount(container, node);
|
|
297
|
-
|
|
298
|
-
expect(container.querySelector("span")).not.toBeNull();
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
describe("Complex nested scenario", () => {
|
|
303
|
-
/**
|
|
304
|
-
* Luna's provide() is now Owner-based (component-tree-scoped).
|
|
305
|
-
*
|
|
306
|
-
* Context values are associated with Owners (reactive scopes).
|
|
307
|
-
* When show() or forEach() render later, they inherit context from
|
|
308
|
-
* the Owner that was active when they were created.
|
|
309
|
-
*
|
|
310
|
-
* This matches SolidJS's <Provider> behavior.
|
|
311
|
-
*/
|
|
312
|
-
|
|
313
|
-
test("show and forEach inherit context from Owner (fixed)", () => {
|
|
314
|
-
const themeCtx = createContext("light"); // default is "light"
|
|
315
|
-
const [showList, setShowList] = createSignal(false);
|
|
316
|
-
const [items, setItems] = createSignal(["A", "B"]);
|
|
317
|
-
|
|
318
|
-
const ListItem = (item: string) => {
|
|
319
|
-
const theme = useContext(themeCtx);
|
|
320
|
-
return createElement(
|
|
321
|
-
"li",
|
|
322
|
-
[attr("class", AttrValue.Static(`item-${theme}`))],
|
|
323
|
-
[text(item)]
|
|
324
|
-
);
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
// Context is Owner-based: show/forEach must be created inside provide scope
|
|
328
|
-
// to capture the Owner with the context value
|
|
329
|
-
provide(themeCtx, "dark", () => {
|
|
330
|
-
const listNode = show(showList, () =>
|
|
331
|
-
createElement("ul", [], [forEach(items, (item) => ListItem(item))])
|
|
332
|
-
);
|
|
333
|
-
mount(container, listNode);
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
// Initially hidden
|
|
337
|
-
expect(container.querySelector("ul")).toBeNull();
|
|
338
|
-
|
|
339
|
-
// Toggle to visible - context should be "dark" (inherited from Owner)
|
|
340
|
-
setShowList(true);
|
|
341
|
-
let lis = container.querySelectorAll("li");
|
|
342
|
-
expect(lis.length).toBe(2);
|
|
343
|
-
// Context is "dark" because Owner-based context persists
|
|
344
|
-
expect(lis[0].className).toBe("item-dark");
|
|
345
|
-
|
|
346
|
-
// Toggle visibility
|
|
347
|
-
setShowList(false);
|
|
348
|
-
expect(container.querySelector("ul")).toBeNull();
|
|
349
|
-
|
|
350
|
-
setShowList(true);
|
|
351
|
-
lis = container.querySelectorAll("li");
|
|
352
|
-
expect(lis.length).toBe(2);
|
|
353
|
-
// Still "dark" after re-render
|
|
354
|
-
expect(lis[0].className).toBe("item-dark");
|
|
355
|
-
|
|
356
|
-
// Update items - new items should also get "dark" context
|
|
357
|
-
setItems(["A", "B", "C"]);
|
|
358
|
-
lis = container.querySelectorAll("li");
|
|
359
|
-
expect(lis.length).toBe(3);
|
|
360
|
-
expect(lis[2].className).toBe("item-dark");
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
test("context works with immediate rendering (non-deferred)", () => {
|
|
364
|
-
const themeCtx = createContext("light");
|
|
365
|
-
|
|
366
|
-
// Context works when rendering is synchronous
|
|
367
|
-
provide(themeCtx, "dark", () => {
|
|
368
|
-
const theme = useContext(themeCtx);
|
|
369
|
-
const node = createElement(
|
|
370
|
-
"div",
|
|
371
|
-
[attr("class", AttrValue.Static(`theme-${theme}`))],
|
|
372
|
-
[text(theme)]
|
|
373
|
-
);
|
|
374
|
-
mount(container, node);
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
const div = container.querySelector("div");
|
|
378
|
-
expect(div?.className).toBe("theme-dark");
|
|
379
|
-
expect(div?.textContent).toBe("dark");
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
test("forEach renders correctly without show (initial items)", () => {
|
|
383
|
-
// This test shows that forEach initial render works,
|
|
384
|
-
// but context is still function-scoped
|
|
385
|
-
const [items, setItems] = createSignal(["A", "B"]);
|
|
386
|
-
|
|
387
|
-
const listNode = createElement("ul", [], [
|
|
388
|
-
forEach(items, (item) => createElement("li", [], [text(item)])),
|
|
389
|
-
]);
|
|
390
|
-
|
|
391
|
-
mount(container, listNode);
|
|
392
|
-
|
|
393
|
-
let lis = container.querySelectorAll("li");
|
|
394
|
-
expect(lis.length).toBe(2);
|
|
395
|
-
expect(lis[0].textContent).toBe("A");
|
|
396
|
-
expect(lis[1].textContent).toBe("B");
|
|
397
|
-
|
|
398
|
-
// Update items
|
|
399
|
-
setItems(["X", "Y", "Z"]);
|
|
400
|
-
lis = container.querySelectorAll("li");
|
|
401
|
-
expect(lis.length).toBe(3);
|
|
402
|
-
expect(lis[2].textContent).toBe("Z");
|
|
403
|
-
});
|
|
404
|
-
});
|
|
405
|
-
});
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Issue #11: Show component does not re-render when signal changes from null to truthy value
|
|
3
|
-
*
|
|
4
|
-
* This test reproduces the exact scenario described in the issue.
|
|
5
|
-
* Now using SolidJS-compatible accessor pattern: children receives an accessor function.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
9
|
-
import {
|
|
10
|
-
createSignal,
|
|
11
|
-
createRoot,
|
|
12
|
-
mount,
|
|
13
|
-
render,
|
|
14
|
-
Show,
|
|
15
|
-
show,
|
|
16
|
-
createElement,
|
|
17
|
-
text,
|
|
18
|
-
} from "../src/index";
|
|
19
|
-
|
|
20
|
-
describe("Issue #11: Show with null to truthy transition", () => {
|
|
21
|
-
let container: HTMLElement;
|
|
22
|
-
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
container = document.createElement("div");
|
|
25
|
-
document.body.appendChild(container);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
container.remove();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("Show updates when signal changes from null to truthy (pattern 1: direct signal)", () => {
|
|
33
|
-
const [data, setData] = createSignal<string | null>(null);
|
|
34
|
-
|
|
35
|
-
const node = Show({
|
|
36
|
-
when: data,
|
|
37
|
-
// SolidJS-style: children receives accessor, call with ()
|
|
38
|
-
children: (value: () => string) => createElement("p", [], [text(`Data: ${value()}`)]),
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
mount(container, node);
|
|
42
|
-
|
|
43
|
-
// Initial state: data is null, nothing should be visible
|
|
44
|
-
expect(container.querySelector("p")).toBeNull();
|
|
45
|
-
|
|
46
|
-
// Update signal to truthy value
|
|
47
|
-
setData("loaded data");
|
|
48
|
-
|
|
49
|
-
// After update: content should be visible
|
|
50
|
-
const p = container.querySelector("p");
|
|
51
|
-
expect(p).not.toBeNull();
|
|
52
|
-
expect(p?.textContent).toBe("Data: loaded data");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("Show updates when signal changes from null to truthy (pattern 2: getter function)", () => {
|
|
56
|
-
const [data, setData] = createSignal<string | null>(null);
|
|
57
|
-
|
|
58
|
-
const node = Show({
|
|
59
|
-
when: () => data(),
|
|
60
|
-
// SolidJS-style: children receives accessor, call with ()
|
|
61
|
-
children: (value: () => string) => createElement("p", [], [text(`Data: ${value()}`)]),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
mount(container, node);
|
|
65
|
-
|
|
66
|
-
// Initial state: data is null, nothing should be visible
|
|
67
|
-
expect(container.querySelector("p")).toBeNull();
|
|
68
|
-
|
|
69
|
-
// Update signal to truthy value
|
|
70
|
-
setData("loaded data");
|
|
71
|
-
|
|
72
|
-
// After update: content should be visible
|
|
73
|
-
const p = container.querySelector("p");
|
|
74
|
-
expect(p).not.toBeNull();
|
|
75
|
-
expect(p?.textContent).toBe("Data: loaded data");
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("Show updates with async delay (simulating real use case)", async () => {
|
|
79
|
-
const [data, setData] = createSignal<string | null>(null);
|
|
80
|
-
|
|
81
|
-
const node = Show({
|
|
82
|
-
when: data,
|
|
83
|
-
// SolidJS-style: children receives accessor, call with ()
|
|
84
|
-
children: (value: () => string) => createElement("p", [], [text(`Data: ${value()}`)]),
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
mount(container, node);
|
|
88
|
-
|
|
89
|
-
// Initial state
|
|
90
|
-
expect(container.querySelector("p")).toBeNull();
|
|
91
|
-
|
|
92
|
-
// Simulate async data loading
|
|
93
|
-
await new Promise<void>((resolve) => {
|
|
94
|
-
setTimeout(() => {
|
|
95
|
-
setData("loaded data");
|
|
96
|
-
resolve();
|
|
97
|
-
}, 50);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// After async update
|
|
101
|
-
const p = container.querySelector("p");
|
|
102
|
-
expect(p).not.toBeNull();
|
|
103
|
-
expect(p?.textContent).toBe("Data: loaded data");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("Low-level show() works with null to truthy", () => {
|
|
107
|
-
const [data, setData] = createSignal<string | null>(null);
|
|
108
|
-
|
|
109
|
-
const node = show(
|
|
110
|
-
() => data() !== null,
|
|
111
|
-
() => createElement("p", [], [text(`Data: ${data()}`)]),
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
mount(container, node);
|
|
115
|
-
|
|
116
|
-
// Initial state
|
|
117
|
-
expect(container.querySelector("p")).toBeNull();
|
|
118
|
-
|
|
119
|
-
// Update
|
|
120
|
-
setData("loaded data");
|
|
121
|
-
|
|
122
|
-
// After update
|
|
123
|
-
const p = container.querySelector("p");
|
|
124
|
-
expect(p).not.toBeNull();
|
|
125
|
-
expect(p?.textContent).toBe("Data: loaded data");
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
test("Show inside createRoot works correctly", () => {
|
|
129
|
-
const [data, setData] = createSignal<string | null>(null);
|
|
130
|
-
let rootNode: unknown;
|
|
131
|
-
|
|
132
|
-
createRoot((dispose) => {
|
|
133
|
-
rootNode = Show({
|
|
134
|
-
when: data,
|
|
135
|
-
// SolidJS-style: children receives accessor, call with ()
|
|
136
|
-
children: (value: () => string) => createElement("p", [], [text(`Data: ${value()}`)]),
|
|
137
|
-
});
|
|
138
|
-
mount(container, rootNode as any);
|
|
139
|
-
return dispose;
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// Initial state
|
|
143
|
-
expect(container.querySelector("p")).toBeNull();
|
|
144
|
-
|
|
145
|
-
// Update
|
|
146
|
-
setData("loaded data");
|
|
147
|
-
|
|
148
|
-
// After update
|
|
149
|
-
const p = container.querySelector("p");
|
|
150
|
-
expect(p).not.toBeNull();
|
|
151
|
-
expect(p?.textContent).toBe("Data: loaded data");
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("Show with render (not mount) works correctly", () => {
|
|
155
|
-
const [data, setData] = createSignal<string | null>(null);
|
|
156
|
-
|
|
157
|
-
const node = Show({
|
|
158
|
-
when: data,
|
|
159
|
-
// SolidJS-style: children receives accessor, call with ()
|
|
160
|
-
children: (value: () => string) => createElement("p", [], [text(`Data: ${value()}`)]),
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
render(container, node);
|
|
164
|
-
|
|
165
|
-
// Initial state
|
|
166
|
-
expect(container.querySelector("p")).toBeNull();
|
|
167
|
-
|
|
168
|
-
// Update
|
|
169
|
-
setData("loaded data");
|
|
170
|
-
|
|
171
|
-
// After update
|
|
172
|
-
const p = container.querySelector("p");
|
|
173
|
-
expect(p).not.toBeNull();
|
|
174
|
-
expect(p?.textContent).toBe("Data: loaded data");
|
|
175
|
-
});
|
|
176
|
-
});
|