@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/store.test.ts
DELETED
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
createStore,
|
|
4
|
-
createRenderEffect,
|
|
5
|
-
createRoot,
|
|
6
|
-
produce,
|
|
7
|
-
reconcile,
|
|
8
|
-
} from "../src/index";
|
|
9
|
-
|
|
10
|
-
describe("createStore", () => {
|
|
11
|
-
describe("Basic functionality", () => {
|
|
12
|
-
test("creates a store with initial value", () => {
|
|
13
|
-
const [state] = createStore({ count: 0 });
|
|
14
|
-
expect(state.count).toBe(0);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("nested property access works", () => {
|
|
18
|
-
const [state] = createStore({
|
|
19
|
-
user: { name: "John", age: 30 },
|
|
20
|
-
});
|
|
21
|
-
expect(state.user.name).toBe("John");
|
|
22
|
-
expect(state.user.age).toBe(30);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test("deeply nested access works", () => {
|
|
26
|
-
const [state] = createStore({
|
|
27
|
-
a: { b: { c: { d: 42 } } },
|
|
28
|
-
});
|
|
29
|
-
expect(state.a.b.c.d).toBe(42);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe("setState with path", () => {
|
|
34
|
-
test("updates single property", () => {
|
|
35
|
-
const [state, setState] = createStore({ count: 0 });
|
|
36
|
-
setState("count", 1);
|
|
37
|
-
expect(state.count).toBe(1);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("updates nested property", () => {
|
|
41
|
-
const [state, setState] = createStore({
|
|
42
|
-
user: { name: "John" },
|
|
43
|
-
});
|
|
44
|
-
setState("user", "name", "Jane");
|
|
45
|
-
expect(state.user.name).toBe("Jane");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("functional update works", () => {
|
|
49
|
-
const [state, setState] = createStore({ count: 5 });
|
|
50
|
-
setState("count", (c: number) => c + 1);
|
|
51
|
-
expect(state.count).toBe(6);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("merges objects at path", () => {
|
|
55
|
-
const [state, setState] = createStore({
|
|
56
|
-
user: { name: "John", age: 30 },
|
|
57
|
-
});
|
|
58
|
-
setState("user", { age: 31 });
|
|
59
|
-
expect(state.user.name).toBe("John");
|
|
60
|
-
expect(state.user.age).toBe(31);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("merges at root level", () => {
|
|
64
|
-
const [state, setState] = createStore({
|
|
65
|
-
count: 0,
|
|
66
|
-
name: "test",
|
|
67
|
-
});
|
|
68
|
-
setState({ count: 10 });
|
|
69
|
-
expect(state.count).toBe(10);
|
|
70
|
-
expect(state.name).toBe("test");
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe("Reactivity", () => {
|
|
75
|
-
test("tracks property access in effects", () => {
|
|
76
|
-
createRoot(() => {
|
|
77
|
-
const [state, setState] = createStore({ count: 0 });
|
|
78
|
-
const values: number[] = [];
|
|
79
|
-
|
|
80
|
-
createRenderEffect(() => {
|
|
81
|
-
values.push(state.count);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
expect(values).toEqual([0]);
|
|
85
|
-
|
|
86
|
-
setState("count", 1);
|
|
87
|
-
expect(values).toEqual([0, 1]);
|
|
88
|
-
|
|
89
|
-
setState("count", 2);
|
|
90
|
-
expect(values).toEqual([0, 1, 2]);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("tracks nested property access", () => {
|
|
95
|
-
createRoot(() => {
|
|
96
|
-
const [state, setState] = createStore({
|
|
97
|
-
user: { name: "John" },
|
|
98
|
-
});
|
|
99
|
-
const names: string[] = [];
|
|
100
|
-
|
|
101
|
-
createRenderEffect(() => {
|
|
102
|
-
names.push(state.user.name);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
expect(names).toEqual(["John"]);
|
|
106
|
-
|
|
107
|
-
setState("user", "name", "Jane");
|
|
108
|
-
expect(names).toEqual(["John", "Jane"]);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("only triggers when accessed property changes", () => {
|
|
113
|
-
createRoot(() => {
|
|
114
|
-
const [state, setState] = createStore({
|
|
115
|
-
a: 1,
|
|
116
|
-
b: 2,
|
|
117
|
-
});
|
|
118
|
-
const aValues: number[] = [];
|
|
119
|
-
|
|
120
|
-
createRenderEffect(() => {
|
|
121
|
-
aValues.push(state.a);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
expect(aValues).toEqual([1]);
|
|
125
|
-
|
|
126
|
-
// Changing b should not trigger effect that only reads a
|
|
127
|
-
setState("b", 3);
|
|
128
|
-
expect(aValues).toEqual([1]);
|
|
129
|
-
|
|
130
|
-
// Changing a should trigger
|
|
131
|
-
setState("a", 10);
|
|
132
|
-
expect(aValues).toEqual([1, 10]);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
test("parent path change notifies child accessors", () => {
|
|
137
|
-
createRoot(() => {
|
|
138
|
-
const [state, setState] = createStore({
|
|
139
|
-
user: { name: "John", age: 30 },
|
|
140
|
-
});
|
|
141
|
-
const names: string[] = [];
|
|
142
|
-
|
|
143
|
-
createRenderEffect(() => {
|
|
144
|
-
names.push(state.user.name);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
expect(names).toEqual(["John"]);
|
|
148
|
-
|
|
149
|
-
// Replacing the entire user object should trigger
|
|
150
|
-
setState("user", { name: "Jane", age: 25 });
|
|
151
|
-
expect(names).toEqual(["John", "Jane"]);
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
describe("Arrays", () => {
|
|
157
|
-
test("array access works", () => {
|
|
158
|
-
const [state] = createStore({ items: [1, 2, 3] });
|
|
159
|
-
expect(state.items[0]).toBe(1);
|
|
160
|
-
expect(state.items.length).toBe(3);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("array updates work", () => {
|
|
164
|
-
const [state, setState] = createStore({ items: ["a", "b", "c"] });
|
|
165
|
-
setState("items", [...state.items, "d"]);
|
|
166
|
-
expect(state.items).toEqual(["a", "b", "c", "d"]);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("array with objects", () => {
|
|
170
|
-
const [state, setState] = createStore({
|
|
171
|
-
todos: [
|
|
172
|
-
{ id: 1, text: "First", done: false },
|
|
173
|
-
{ id: 2, text: "Second", done: true },
|
|
174
|
-
],
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
expect(state.todos[0].text).toBe("First");
|
|
178
|
-
expect(state.todos[1].done).toBe(true);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
describe("produce helper", () => {
|
|
183
|
-
test("produce creates a mutation function", () => {
|
|
184
|
-
const [state, setState] = createStore({
|
|
185
|
-
user: { name: "John", age: 30 },
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
setState(
|
|
189
|
-
"user",
|
|
190
|
-
produce((user: { name: string; age: number }) => {
|
|
191
|
-
user.name = "Jane";
|
|
192
|
-
user.age = 31;
|
|
193
|
-
})
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
expect(state.user.name).toBe("Jane");
|
|
197
|
-
expect(state.user.age).toBe(31);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test("produce works with arrays", () => {
|
|
201
|
-
const [state, setState] = createStore({
|
|
202
|
-
items: [1, 2, 3],
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
setState(
|
|
206
|
-
"items",
|
|
207
|
-
produce((items: number[]) => {
|
|
208
|
-
items.push(4);
|
|
209
|
-
})
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
expect(state.items).toEqual([1, 2, 3, 4]);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe("reconcile helper", () => {
|
|
217
|
-
test("reconcile replaces entire value", () => {
|
|
218
|
-
const [state, setState] = createStore({
|
|
219
|
-
items: [1, 2, 3],
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
setState("items", reconcile([4, 5, 6]));
|
|
223
|
-
expect(state.items).toEqual([4, 5, 6]);
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
describe("Edge cases", () => {
|
|
228
|
-
test("handles undefined values", () => {
|
|
229
|
-
const [state, setState] = createStore<{
|
|
230
|
-
value: number | undefined;
|
|
231
|
-
}>({ value: undefined });
|
|
232
|
-
expect(state.value).toBeUndefined();
|
|
233
|
-
|
|
234
|
-
setState("value", 42);
|
|
235
|
-
expect(state.value).toBe(42);
|
|
236
|
-
|
|
237
|
-
setState("value", undefined);
|
|
238
|
-
expect(state.value).toBeUndefined();
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
test("handles null values", () => {
|
|
242
|
-
const [state, setState] = createStore<{
|
|
243
|
-
value: string | null;
|
|
244
|
-
}>({ value: null });
|
|
245
|
-
expect(state.value).toBeNull();
|
|
246
|
-
|
|
247
|
-
setState("value", "hello");
|
|
248
|
-
expect(state.value).toBe("hello");
|
|
249
|
-
|
|
250
|
-
setState("value", null);
|
|
251
|
-
expect(state.value).toBeNull();
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test("creates nested paths that don't exist", () => {
|
|
255
|
-
const [state, setState] = createStore<{
|
|
256
|
-
deep?: { nested?: { value?: number } };
|
|
257
|
-
}>({});
|
|
258
|
-
|
|
259
|
-
setState("deep", "nested", "value", 42);
|
|
260
|
-
expect(state.deep?.nested?.value).toBe(42);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
});
|
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
// Test actual TSX syntax with jsxImportSource
|
|
2
|
-
import { describe, test, expect } from "vitest";
|
|
3
|
-
import "global-jsdom/register";
|
|
4
|
-
import { createSignal, createRoot, render, Fragment, Show, Switch, Match } from "../src/index";
|
|
5
|
-
import type { JSX } from "../src/jsx-runtime";
|
|
6
|
-
|
|
7
|
-
describe("TSX Syntax with jsxImportSource", () => {
|
|
8
|
-
test("basic element", () => {
|
|
9
|
-
const node = <div className="test">Hello</div>;
|
|
10
|
-
expect(node).toBeDefined();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
test("nested elements", () => {
|
|
14
|
-
const node = (
|
|
15
|
-
<div className="container">
|
|
16
|
-
<h1>Title</h1>
|
|
17
|
-
<p>Paragraph</p>
|
|
18
|
-
</div>
|
|
19
|
-
);
|
|
20
|
-
expect(node).toBeDefined();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
test("element with multiple attributes", () => {
|
|
24
|
-
const node = (
|
|
25
|
-
<input
|
|
26
|
-
type="text"
|
|
27
|
-
placeholder="Enter name"
|
|
28
|
-
value="initial"
|
|
29
|
-
/>
|
|
30
|
-
);
|
|
31
|
-
expect(node).toBeDefined();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("event handler", () => {
|
|
35
|
-
let clicked = false;
|
|
36
|
-
const node = (
|
|
37
|
-
<button onClick={() => { clicked = true; }}>
|
|
38
|
-
Click me
|
|
39
|
-
</button>
|
|
40
|
-
);
|
|
41
|
-
expect(node).toBeDefined();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("function component", () => {
|
|
45
|
-
function Greeting(props: { name: string }): JSX.Element {
|
|
46
|
-
return <span>Hello, {props.name}!</span>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const node = <Greeting name="World" />;
|
|
50
|
-
expect(node).toBeDefined();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("component with children", () => {
|
|
54
|
-
function Card(props: { title: string; children?: JSX.Element }): JSX.Element {
|
|
55
|
-
return (
|
|
56
|
-
<div className="card">
|
|
57
|
-
<h2>{props.title}</h2>
|
|
58
|
-
<div className="content">{props.children}</div>
|
|
59
|
-
</div>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const node = (
|
|
64
|
-
<Card title="My Card">
|
|
65
|
-
<p>Card content here</p>
|
|
66
|
-
</Card>
|
|
67
|
-
);
|
|
68
|
-
expect(node).toBeDefined();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("fragment with shorthand syntax", () => {
|
|
72
|
-
const node = (
|
|
73
|
-
<>
|
|
74
|
-
<div>First</div>
|
|
75
|
-
<div>Second</div>
|
|
76
|
-
</>
|
|
77
|
-
);
|
|
78
|
-
// Fragment should return a DomNode (not an array)
|
|
79
|
-
expect(node).toBeDefined();
|
|
80
|
-
expect(Array.isArray(node)).toBe(false);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("Fragment component with explicit JSX tag", () => {
|
|
84
|
-
// This is the bug fix test: <Fragment> should work like <>
|
|
85
|
-
const node = (
|
|
86
|
-
<Fragment>
|
|
87
|
-
<div>First</div>
|
|
88
|
-
<div>Second</div>
|
|
89
|
-
</Fragment>
|
|
90
|
-
);
|
|
91
|
-
expect(node).toBeDefined();
|
|
92
|
-
expect(Array.isArray(node)).toBe(false);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("Fragment renders correctly to DOM", () => {
|
|
96
|
-
const container = document.createElement("div");
|
|
97
|
-
|
|
98
|
-
const node = (
|
|
99
|
-
<div id="wrapper">
|
|
100
|
-
<Fragment>
|
|
101
|
-
<span>A</span>
|
|
102
|
-
<span>B</span>
|
|
103
|
-
</Fragment>
|
|
104
|
-
</div>
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
render(container, node);
|
|
108
|
-
|
|
109
|
-
expect(container.innerHTML).toContain("A");
|
|
110
|
-
expect(container.innerHTML).toContain("B");
|
|
111
|
-
// Both spans should be direct children of wrapper
|
|
112
|
-
const wrapper = container.querySelector("#wrapper");
|
|
113
|
-
expect(wrapper?.querySelectorAll("span").length).toBe(2);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("component returning Fragment works", () => {
|
|
117
|
-
function MultipleElements(): JSX.Element {
|
|
118
|
-
return (
|
|
119
|
-
<Fragment>
|
|
120
|
-
<div>One</div>
|
|
121
|
-
<div>Two</div>
|
|
122
|
-
<div>Three</div>
|
|
123
|
-
</Fragment>
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const container = document.createElement("div");
|
|
128
|
-
render(container, <MultipleElements />);
|
|
129
|
-
|
|
130
|
-
expect(container.textContent).toContain("One");
|
|
131
|
-
expect(container.textContent).toContain("Two");
|
|
132
|
-
expect(container.textContent).toContain("Three");
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("render TSX to DOM", () => {
|
|
136
|
-
const container = document.createElement("div");
|
|
137
|
-
|
|
138
|
-
const node = (
|
|
139
|
-
<div id="app">
|
|
140
|
-
<h1 className="title">Hello TSX</h1>
|
|
141
|
-
<p>This is a paragraph.</p>
|
|
142
|
-
</div>
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
render(container, node);
|
|
146
|
-
|
|
147
|
-
expect(container.innerHTML).toContain("app");
|
|
148
|
-
expect(container.innerHTML).toContain("title");
|
|
149
|
-
expect(container.innerHTML).toContain("Hello TSX");
|
|
150
|
-
expect(container.innerHTML).toContain("paragraph");
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
test("reactive value with signal", () => {
|
|
154
|
-
const [count] = createSignal(0);
|
|
155
|
-
|
|
156
|
-
const node = (
|
|
157
|
-
<div className={() => `count-${count()}`}>
|
|
158
|
-
Count value
|
|
159
|
-
</div>
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
expect(node).toBeDefined();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("list rendering", () => {
|
|
166
|
-
const items = ["Apple", "Banana", "Cherry"];
|
|
167
|
-
|
|
168
|
-
const node = (
|
|
169
|
-
<ul>
|
|
170
|
-
{items.map((item) => (
|
|
171
|
-
<li>{item}</li>
|
|
172
|
-
))}
|
|
173
|
-
</ul>
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
expect(node).toBeDefined();
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test("conditional rendering in component", () => {
|
|
180
|
-
function Toggle(props: { show: boolean }): JSX.Element {
|
|
181
|
-
return props.show ? <div>Visible</div> : <div>Hidden</div>;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const visible = <Toggle show={true} />;
|
|
185
|
-
const hidden = <Toggle show={false} />;
|
|
186
|
-
|
|
187
|
-
expect(visible).toBeDefined();
|
|
188
|
-
expect(hidden).toBeDefined();
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test("Show with function children returning array", () => {
|
|
192
|
-
const container = document.createElement("div");
|
|
193
|
-
|
|
194
|
-
createRoot((dispose) => {
|
|
195
|
-
const [show, setShow] = createSignal(true);
|
|
196
|
-
|
|
197
|
-
// Function children that returns multiple elements (array)
|
|
198
|
-
const node = (
|
|
199
|
-
<div id="show-test">
|
|
200
|
-
<Show when={show}>
|
|
201
|
-
{(value) => [
|
|
202
|
-
<span>Shown: {String(value)}</span>,
|
|
203
|
-
<span>Also shown</span>
|
|
204
|
-
]}
|
|
205
|
-
</Show>
|
|
206
|
-
</div>
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
render(container, node);
|
|
210
|
-
|
|
211
|
-
expect(container.textContent).toContain("Shown");
|
|
212
|
-
expect(container.textContent).toContain("Also shown");
|
|
213
|
-
|
|
214
|
-
dispose();
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
test("Switch/Match with function children", () => {
|
|
219
|
-
const container = document.createElement("div");
|
|
220
|
-
|
|
221
|
-
createRoot((dispose) => {
|
|
222
|
-
const [value, setValue] = createSignal("a");
|
|
223
|
-
|
|
224
|
-
const node = (
|
|
225
|
-
<div id="switch-test">
|
|
226
|
-
<Switch fallback={<span>No match</span>}>
|
|
227
|
-
<Match when={() => value() === "a"}>
|
|
228
|
-
{() => <span>Match A</span>}
|
|
229
|
-
</Match>
|
|
230
|
-
<Match when={() => value() === "b"}>
|
|
231
|
-
{() => [
|
|
232
|
-
<span>Match B1</span>,
|
|
233
|
-
<span>Match B2</span>
|
|
234
|
-
]}
|
|
235
|
-
</Match>
|
|
236
|
-
</Switch>
|
|
237
|
-
</div>
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
render(container, node);
|
|
241
|
-
expect(container.textContent).toContain("Match A");
|
|
242
|
-
|
|
243
|
-
setValue("b");
|
|
244
|
-
expect(container.textContent).toContain("Match B1");
|
|
245
|
-
expect(container.textContent).toContain("Match B2");
|
|
246
|
-
|
|
247
|
-
setValue("c");
|
|
248
|
-
expect(container.textContent).toContain("No match");
|
|
249
|
-
|
|
250
|
-
dispose();
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
test("Switch/Match with direct multiple children (no function wrapper)", () => {
|
|
255
|
-
const container = document.createElement("div");
|
|
256
|
-
|
|
257
|
-
createRoot((dispose) => {
|
|
258
|
-
const [isEven, setIsEven] = createSignal(true);
|
|
259
|
-
|
|
260
|
-
const node = (
|
|
261
|
-
<div id="direct-children-test">
|
|
262
|
-
<Switch fallback={<span>Odd</span>}>
|
|
263
|
-
<Match when={() => isEven()}>
|
|
264
|
-
<div>A</div>
|
|
265
|
-
<div>B</div>
|
|
266
|
-
</Match>
|
|
267
|
-
</Switch>
|
|
268
|
-
</div>
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
render(container, node);
|
|
272
|
-
expect(container.textContent).toContain("A");
|
|
273
|
-
expect(container.textContent).toContain("B");
|
|
274
|
-
|
|
275
|
-
setIsEven(false);
|
|
276
|
-
expect(container.textContent).toContain("Odd");
|
|
277
|
-
expect(container.textContent).not.toContain("A");
|
|
278
|
-
expect(container.textContent).not.toContain("B");
|
|
279
|
-
|
|
280
|
-
dispose();
|
|
281
|
-
});
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// Issue #7: Reactive props (class, innerHTML)
|
|
285
|
-
describe("Reactive props (Issue #7)", () => {
|
|
286
|
-
test("reactive class with accessor function", () => {
|
|
287
|
-
const container = document.createElement("div");
|
|
288
|
-
|
|
289
|
-
createRoot((dispose) => {
|
|
290
|
-
const [active, setActive] = createSignal(false);
|
|
291
|
-
|
|
292
|
-
// Pass accessor function directly (not called): class={className}
|
|
293
|
-
const className = () => `btn ${active() ? "active" : "inactive"}`;
|
|
294
|
-
|
|
295
|
-
const node = (
|
|
296
|
-
<button class={className}>
|
|
297
|
-
Toggle
|
|
298
|
-
</button>
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
render(container, node);
|
|
302
|
-
const button = container.querySelector("button")!;
|
|
303
|
-
|
|
304
|
-
// Initial state
|
|
305
|
-
expect(button.className).toBe("btn inactive");
|
|
306
|
-
|
|
307
|
-
// After update
|
|
308
|
-
setActive(true);
|
|
309
|
-
expect(button.className).toBe("btn active");
|
|
310
|
-
|
|
311
|
-
// Toggle back
|
|
312
|
-
setActive(false);
|
|
313
|
-
expect(button.className).toBe("btn inactive");
|
|
314
|
-
|
|
315
|
-
dispose();
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
test("reactive className with accessor function", () => {
|
|
320
|
-
const container = document.createElement("div");
|
|
321
|
-
|
|
322
|
-
createRoot((dispose) => {
|
|
323
|
-
const [color, setColor] = createSignal("red");
|
|
324
|
-
|
|
325
|
-
const node = (
|
|
326
|
-
<div className={() => `text-${color()}`}>
|
|
327
|
-
Colored text
|
|
328
|
-
</div>
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
render(container, node);
|
|
332
|
-
const div = container.querySelector("div")!;
|
|
333
|
-
|
|
334
|
-
expect(div.className).toBe("text-red");
|
|
335
|
-
|
|
336
|
-
setColor("blue");
|
|
337
|
-
expect(div.className).toBe("text-blue");
|
|
338
|
-
|
|
339
|
-
dispose();
|
|
340
|
-
});
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
test("dangerouslySetInnerHTML static", () => {
|
|
344
|
-
const container = document.createElement("div");
|
|
345
|
-
|
|
346
|
-
const node = (
|
|
347
|
-
<span dangerouslySetInnerHTML={{ __html: "<b>bold</b>" }} />
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
render(container, node);
|
|
351
|
-
const span = container.querySelector("span")!;
|
|
352
|
-
|
|
353
|
-
expect(span.innerHTML).toBe("<b>bold</b>");
|
|
354
|
-
expect(span.querySelector("b")?.textContent).toBe("bold");
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
test("dangerouslySetInnerHTML dynamic", () => {
|
|
358
|
-
const container = document.createElement("div");
|
|
359
|
-
|
|
360
|
-
createRoot((dispose) => {
|
|
361
|
-
const [html, setHtml] = createSignal("<i>italic</i>");
|
|
362
|
-
|
|
363
|
-
const node = (
|
|
364
|
-
<span dangerouslySetInnerHTML={() => ({ __html: html() })} />
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
render(container, node);
|
|
368
|
-
const span = container.querySelector("span")!;
|
|
369
|
-
|
|
370
|
-
expect(span.innerHTML).toBe("<i>italic</i>");
|
|
371
|
-
|
|
372
|
-
setHtml("<strong>strong</strong>");
|
|
373
|
-
expect(span.innerHTML).toBe("<strong>strong</strong>");
|
|
374
|
-
|
|
375
|
-
dispose();
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
test("reactive style object", () => {
|
|
380
|
-
const container = document.createElement("div");
|
|
381
|
-
|
|
382
|
-
createRoot((dispose) => {
|
|
383
|
-
const [color, setColor] = createSignal("red");
|
|
384
|
-
|
|
385
|
-
const node = (
|
|
386
|
-
<div style={() => ({ color: color(), fontWeight: "bold" })}>
|
|
387
|
-
Styled text
|
|
388
|
-
</div>
|
|
389
|
-
);
|
|
390
|
-
|
|
391
|
-
render(container, node);
|
|
392
|
-
const div = container.querySelector("div")!;
|
|
393
|
-
|
|
394
|
-
expect(div.getAttribute("style")).toContain("color: red");
|
|
395
|
-
expect(div.getAttribute("style")).toContain("font-weight: bold");
|
|
396
|
-
|
|
397
|
-
setColor("blue");
|
|
398
|
-
expect(div.getAttribute("style")).toContain("color: blue");
|
|
399
|
-
|
|
400
|
-
dispose();
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
});
|
|
404
|
-
});
|
|
File without changes
|