what-core 0.5.6 → 0.6.1
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/README.md +8 -6
- package/dist/components.js +1 -1
- package/dist/dom.js +127 -451
- package/dist/h.js +1 -1
- package/dist/hooks.js +4 -0
- package/dist/index.js +5983 -123
- package/dist/index.js.map +7 -0
- package/dist/index.min.js +123 -0
- package/dist/index.min.js.map +7 -0
- package/dist/jsx-dev-runtime.js +51 -0
- package/dist/jsx-dev-runtime.js.map +7 -0
- package/dist/jsx-dev-runtime.min.js +2 -0
- package/dist/jsx-dev-runtime.min.js.map +7 -0
- package/dist/jsx-runtime.js +49 -0
- package/dist/jsx-runtime.js.map +7 -0
- package/dist/jsx-runtime.min.js +2 -0
- package/dist/jsx-runtime.min.js.map +7 -0
- package/dist/reactive.js +175 -11
- package/dist/render.js +1549 -272
- package/dist/render.js.map +7 -0
- package/dist/render.min.js +2 -0
- package/dist/render.min.js.map +7 -0
- package/dist/testing.js +1257 -144
- package/dist/testing.js.map +7 -0
- package/dist/testing.min.js +2 -0
- package/dist/testing.min.js.map +7 -0
- package/dist/what.js +3 -2
- package/package.json +9 -4
- package/src/agent-context.js +126 -0
- package/src/components.js +10 -34
- package/src/dom.js +269 -749
- package/src/errors.js +253 -0
- package/src/guardrails.js +224 -0
- package/src/h.js +3 -3
- package/src/hooks.js +121 -52
- package/src/index.js +38 -4
- package/src/reactive.js +450 -64
- package/src/render.js +462 -14
- package/src/testing.js +169 -1
- package/src/warnings.js +110 -0
package/dist/testing.js
CHANGED
|
@@ -1,46 +1,1081 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// packages/core/src/reactive.js
|
|
2
|
+
var __DEV__ = typeof process !== "undefined" ? true : true;
|
|
3
|
+
var __devtools = null;
|
|
4
|
+
var currentEffect = null;
|
|
5
|
+
var currentRoot = null;
|
|
6
|
+
var currentOwner = null;
|
|
7
|
+
var insideComputed = false;
|
|
8
|
+
var batchDepth = 0;
|
|
9
|
+
var pendingEffects = [];
|
|
10
|
+
var pendingNeedSort = false;
|
|
11
|
+
var subSetOwner = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
var NEEDS_UPSTREAM = /* @__PURE__ */ Symbol("needs_upstream");
|
|
13
|
+
function signal(initial, debugName) {
|
|
14
|
+
let value = initial;
|
|
15
|
+
const subs = /* @__PURE__ */ new Set();
|
|
16
|
+
function sig(...args) {
|
|
17
|
+
if (args.length === 0) {
|
|
18
|
+
if (currentEffect) {
|
|
19
|
+
subs.add(currentEffect);
|
|
20
|
+
currentEffect.deps.push(subs);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
if (__DEV__ && insideComputed) {
|
|
25
|
+
console.warn(
|
|
26
|
+
"[what] Signal.set() called inside a computed function. This may cause infinite loops. Use effect() instead." + (debugName ? ` (signal: ${debugName})` : "")
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const nextVal = typeof args[0] === "function" ? args[0](value) : args[0];
|
|
30
|
+
if (Object.is(value, nextVal)) return;
|
|
31
|
+
value = nextVal;
|
|
32
|
+
if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);
|
|
33
|
+
if (subs.size > 0) notify(subs);
|
|
34
|
+
}
|
|
35
|
+
sig.set = (next) => {
|
|
36
|
+
if (__DEV__ && insideComputed) {
|
|
37
|
+
console.warn(
|
|
38
|
+
"[what] Signal.set() called inside a computed function. This may cause infinite loops. Use effect() instead." + (debugName ? ` (signal: ${debugName})` : "")
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const nextVal = typeof next === "function" ? next(value) : next;
|
|
42
|
+
if (Object.is(value, nextVal)) return;
|
|
43
|
+
value = nextVal;
|
|
44
|
+
if (__DEV__ && __devtools) __devtools.onSignalUpdate(sig);
|
|
45
|
+
if (subs.size > 0) notify(subs);
|
|
46
|
+
};
|
|
47
|
+
sig.peek = () => value;
|
|
48
|
+
sig.subscribe = (fn) => {
|
|
49
|
+
return effect(() => fn(sig()));
|
|
50
|
+
};
|
|
51
|
+
sig._signal = true;
|
|
52
|
+
if (__DEV__) {
|
|
53
|
+
sig._subs = subs;
|
|
54
|
+
if (debugName) sig._debugName = debugName;
|
|
55
|
+
}
|
|
56
|
+
if (__DEV__ && __devtools) __devtools.onSignalCreate(sig);
|
|
57
|
+
return sig;
|
|
58
|
+
}
|
|
59
|
+
function _updateLevel(e) {
|
|
60
|
+
let maxDepLevel = 0;
|
|
61
|
+
const deps = e.deps;
|
|
62
|
+
for (let i = 0; i < deps.length; i++) {
|
|
63
|
+
const owner = subSetOwner.get(deps[i]);
|
|
64
|
+
if (owner) {
|
|
65
|
+
const depLevel = owner._level;
|
|
66
|
+
if (depLevel > maxDepLevel) maxDepLevel = depLevel;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
e._level = maxDepLevel + 1;
|
|
70
|
+
}
|
|
71
|
+
function effect(fn, opts) {
|
|
72
|
+
const e = _createEffect(fn);
|
|
73
|
+
e._level = 1;
|
|
74
|
+
const prev = currentEffect;
|
|
75
|
+
currentEffect = e;
|
|
76
|
+
try {
|
|
77
|
+
const result = e.fn();
|
|
78
|
+
if (typeof result === "function") e._cleanup = result;
|
|
79
|
+
} finally {
|
|
80
|
+
currentEffect = prev;
|
|
81
|
+
}
|
|
82
|
+
_updateLevel(e);
|
|
83
|
+
if (opts?.stable) e._stable = true;
|
|
84
|
+
const dispose = () => _disposeEffect(e);
|
|
85
|
+
if (currentRoot) {
|
|
86
|
+
currentRoot.disposals.push(dispose);
|
|
87
|
+
}
|
|
88
|
+
return dispose;
|
|
89
|
+
}
|
|
90
|
+
function _createEffect(fn, lazy) {
|
|
91
|
+
const e = {
|
|
92
|
+
fn,
|
|
93
|
+
deps: [],
|
|
94
|
+
// array of subscriber sets (cheaper than Set for typical 1-3 deps)
|
|
95
|
+
lazy: lazy || false,
|
|
96
|
+
_onNotify: null,
|
|
97
|
+
disposed: false,
|
|
98
|
+
_pending: false,
|
|
99
|
+
_stable: false,
|
|
100
|
+
// stable effects skip cleanup/re-subscribe on re-run
|
|
101
|
+
_level: 0,
|
|
102
|
+
// topological depth: signals=0, computed/effects=max(deps)+1
|
|
103
|
+
_computed: false,
|
|
104
|
+
// true for computed inner effects
|
|
105
|
+
_computedSubs: null,
|
|
106
|
+
// reference to the computed's subscriber set
|
|
107
|
+
_isDirty: null,
|
|
108
|
+
// function to check if computed is dirty (set by computed())
|
|
109
|
+
_markDirty: null
|
|
110
|
+
// function to mark computed dirty (set by computed())
|
|
111
|
+
};
|
|
112
|
+
if (__DEV__ && __devtools) __devtools.onEffectCreate(e);
|
|
113
|
+
return e;
|
|
114
|
+
}
|
|
115
|
+
function _runEffect(e) {
|
|
116
|
+
if (e.disposed) return;
|
|
117
|
+
if (e._stable) {
|
|
118
|
+
if (e._cleanup) {
|
|
119
|
+
try {
|
|
120
|
+
e._cleanup();
|
|
121
|
+
} catch (err) {
|
|
122
|
+
if (__DEV__) console.warn("[what] Error in effect cleanup:", err);
|
|
123
|
+
}
|
|
124
|
+
e._cleanup = null;
|
|
125
|
+
}
|
|
126
|
+
const prev2 = currentEffect;
|
|
127
|
+
currentEffect = null;
|
|
128
|
+
try {
|
|
129
|
+
const result = e.fn();
|
|
130
|
+
if (typeof result === "function") e._cleanup = result;
|
|
131
|
+
} catch (err) {
|
|
132
|
+
if (__devtools?.onError) __devtools.onError(err, { type: "effect", effect: e });
|
|
133
|
+
if (__DEV__) console.warn("[what] Error in stable effect:", err);
|
|
134
|
+
} finally {
|
|
135
|
+
currentEffect = prev2;
|
|
136
|
+
}
|
|
137
|
+
if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
cleanup(e);
|
|
141
|
+
if (e._cleanup) {
|
|
142
|
+
try {
|
|
143
|
+
e._cleanup();
|
|
144
|
+
} catch (err) {
|
|
145
|
+
if (__devtools?.onError) __devtools.onError(err, { type: "effect-cleanup", effect: e });
|
|
146
|
+
if (__DEV__) console.warn("[what] Error in effect cleanup:", err);
|
|
147
|
+
}
|
|
148
|
+
e._cleanup = null;
|
|
149
|
+
}
|
|
150
|
+
const prev = currentEffect;
|
|
151
|
+
currentEffect = e;
|
|
152
|
+
try {
|
|
153
|
+
const result = e.fn();
|
|
154
|
+
if (typeof result === "function") {
|
|
155
|
+
e._cleanup = result;
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (err === NEEDS_UPSTREAM) throw err;
|
|
159
|
+
if (__devtools?.onError) __devtools.onError(err, { type: "effect", effect: e });
|
|
160
|
+
throw err;
|
|
161
|
+
} finally {
|
|
162
|
+
currentEffect = prev;
|
|
163
|
+
}
|
|
164
|
+
if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
|
|
165
|
+
}
|
|
166
|
+
function _disposeEffect(e) {
|
|
167
|
+
e.disposed = true;
|
|
168
|
+
if (__DEV__ && __devtools) __devtools.onEffectDispose(e);
|
|
169
|
+
cleanup(e);
|
|
170
|
+
if (e._cleanup) {
|
|
171
|
+
try {
|
|
172
|
+
e._cleanup();
|
|
173
|
+
} catch (err) {
|
|
174
|
+
if (__DEV__) console.warn("[what] Error in effect cleanup on dispose:", err);
|
|
175
|
+
}
|
|
176
|
+
e._cleanup = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function cleanup(e) {
|
|
180
|
+
const deps = e.deps;
|
|
181
|
+
for (let i = 0; i < deps.length; i++) deps[i].delete(e);
|
|
182
|
+
deps.length = 0;
|
|
183
|
+
}
|
|
184
|
+
var notifyDepth = 0;
|
|
185
|
+
var notifyQueue = null;
|
|
186
|
+
var notifyQueueLen = 0;
|
|
187
|
+
function notify(subs) {
|
|
188
|
+
if (notifyDepth === 0) {
|
|
189
|
+
notifyDepth = 1;
|
|
190
|
+
try {
|
|
191
|
+
for (const e of subs) {
|
|
192
|
+
if (e.disposed) continue;
|
|
193
|
+
if (e._onNotify) {
|
|
194
|
+
e._onNotify();
|
|
195
|
+
} else if (batchDepth === 0 && e._stable) {
|
|
196
|
+
const prev = currentEffect;
|
|
197
|
+
currentEffect = null;
|
|
198
|
+
try {
|
|
199
|
+
const result = e.fn();
|
|
200
|
+
if (typeof result === "function") {
|
|
201
|
+
if (e._cleanup) try {
|
|
202
|
+
e._cleanup();
|
|
203
|
+
} catch (err) {
|
|
204
|
+
}
|
|
205
|
+
e._cleanup = result;
|
|
206
|
+
}
|
|
207
|
+
} catch (err) {
|
|
208
|
+
if (__devtools?.onError) __devtools.onError(err, { type: "effect", effect: e });
|
|
209
|
+
if (__DEV__) console.warn("[what] Error in stable effect:", err);
|
|
210
|
+
} finally {
|
|
211
|
+
currentEffect = prev;
|
|
212
|
+
}
|
|
213
|
+
} else if (!e._pending) {
|
|
214
|
+
e._pending = true;
|
|
215
|
+
const level = e._level;
|
|
216
|
+
const len = pendingEffects.length;
|
|
217
|
+
if (len > 0 && pendingEffects[len - 1]._level > level) {
|
|
218
|
+
pendingNeedSort = true;
|
|
219
|
+
}
|
|
220
|
+
pendingEffects.push(e);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (notifyQueueLen > 0) {
|
|
224
|
+
let qi = 0;
|
|
225
|
+
while (qi < notifyQueueLen) {
|
|
226
|
+
const queuedSubs = notifyQueue[qi];
|
|
227
|
+
notifyQueue[qi] = null;
|
|
228
|
+
qi++;
|
|
229
|
+
for (const e of queuedSubs) {
|
|
230
|
+
if (e.disposed) continue;
|
|
231
|
+
if (e._onNotify) {
|
|
232
|
+
e._onNotify();
|
|
233
|
+
} else if (batchDepth === 0 && e._stable) {
|
|
234
|
+
const prev = currentEffect;
|
|
235
|
+
currentEffect = null;
|
|
236
|
+
try {
|
|
237
|
+
const result = e.fn();
|
|
238
|
+
if (typeof result === "function") {
|
|
239
|
+
if (e._cleanup) try {
|
|
240
|
+
e._cleanup();
|
|
241
|
+
} catch (err) {
|
|
242
|
+
}
|
|
243
|
+
e._cleanup = result;
|
|
244
|
+
}
|
|
245
|
+
} catch (err) {
|
|
246
|
+
if (__devtools?.onError) __devtools.onError(err, { type: "effect", effect: e });
|
|
247
|
+
if (__DEV__) console.warn("[what] Error in stable effect:", err);
|
|
248
|
+
} finally {
|
|
249
|
+
currentEffect = prev;
|
|
250
|
+
}
|
|
251
|
+
} else if (!e._pending) {
|
|
252
|
+
e._pending = true;
|
|
253
|
+
const level = e._level;
|
|
254
|
+
const len = pendingEffects.length;
|
|
255
|
+
if (len > 0 && pendingEffects[len - 1]._level > level) {
|
|
256
|
+
pendingNeedSort = true;
|
|
257
|
+
}
|
|
258
|
+
pendingEffects.push(e);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
notifyQueueLen = 0;
|
|
263
|
+
}
|
|
264
|
+
} finally {
|
|
265
|
+
notifyDepth = 0;
|
|
266
|
+
}
|
|
267
|
+
if (batchDepth === 0 && pendingEffects.length > 0) scheduleMicrotask();
|
|
268
|
+
} else {
|
|
269
|
+
if (notifyQueue === null) notifyQueue = [];
|
|
270
|
+
if (notifyQueueLen >= notifyQueue.length) {
|
|
271
|
+
notifyQueue.push(subs);
|
|
272
|
+
} else {
|
|
273
|
+
notifyQueue[notifyQueueLen] = subs;
|
|
274
|
+
}
|
|
275
|
+
notifyQueueLen++;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
var microtaskScheduled = false;
|
|
279
|
+
function scheduleMicrotask() {
|
|
280
|
+
if (!microtaskScheduled) {
|
|
281
|
+
microtaskScheduled = true;
|
|
282
|
+
queueMicrotask(() => {
|
|
283
|
+
microtaskScheduled = false;
|
|
284
|
+
flush();
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
var isFlushing = false;
|
|
289
|
+
function flush() {
|
|
290
|
+
if (isFlushing) return;
|
|
291
|
+
isFlushing = true;
|
|
292
|
+
try {
|
|
293
|
+
let iterations = 0;
|
|
294
|
+
while (pendingEffects.length > 0 && iterations < 25) {
|
|
295
|
+
const batch2 = pendingEffects;
|
|
296
|
+
pendingEffects = [];
|
|
297
|
+
if (batch2.length > 1 && pendingNeedSort) {
|
|
298
|
+
batch2.sort((a, b) => a._level - b._level);
|
|
299
|
+
}
|
|
300
|
+
pendingNeedSort = false;
|
|
301
|
+
for (let i = 0; i < batch2.length; i++) {
|
|
302
|
+
const e = batch2[i];
|
|
303
|
+
e._pending = false;
|
|
304
|
+
if (!e.disposed && !e._onNotify) {
|
|
305
|
+
const prevDepsLen = e.deps.length;
|
|
306
|
+
_runEffect(e);
|
|
307
|
+
if (!e._computed && e.deps.length !== prevDepsLen) {
|
|
308
|
+
_updateLevel(e);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
iterations++;
|
|
313
|
+
}
|
|
314
|
+
if (iterations >= 25) {
|
|
315
|
+
for (let i = 0; i < pendingEffects.length; i++) pendingEffects[i]._pending = false;
|
|
316
|
+
pendingEffects.length = 0;
|
|
317
|
+
if (__DEV__) {
|
|
318
|
+
const remaining = pendingEffects.slice(0, 3);
|
|
319
|
+
const effectNames = remaining.map((e) => e.fn?.name || e.fn?.toString().slice(0, 60) || "(anonymous)");
|
|
320
|
+
console.warn(
|
|
321
|
+
`[what] Possible infinite effect loop detected (25 iterations). Likely cause: an effect writes to a signal it also reads, creating a cycle. Use untrack() to read signals without subscribing. Looping effects: ${effectNames.join(", ")}`
|
|
322
|
+
);
|
|
323
|
+
} else {
|
|
324
|
+
console.warn("[what] Possible infinite effect loop detected");
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} finally {
|
|
328
|
+
isFlushing = false;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function flushSync() {
|
|
332
|
+
if (isFlushing) {
|
|
333
|
+
if (__DEV__) {
|
|
334
|
+
console.warn(
|
|
335
|
+
"[what] flushSync() called during an active flush (e.g., inside a component render or effect). This is a no-op to prevent infinite loops. Move flushSync() to an event handler or onMount callback."
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (currentEffect) {
|
|
341
|
+
if (__DEV__) {
|
|
342
|
+
console.warn(
|
|
343
|
+
"[what] flushSync() called during effect execution. This is a no-op to prevent infinite loops. Move flushSync() to an event handler or onMount callback."
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
microtaskScheduled = false;
|
|
349
|
+
flush();
|
|
350
|
+
}
|
|
351
|
+
function untrack(fn) {
|
|
352
|
+
const prev = currentEffect;
|
|
353
|
+
currentEffect = null;
|
|
354
|
+
try {
|
|
355
|
+
return fn();
|
|
356
|
+
} finally {
|
|
357
|
+
currentEffect = prev;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function createRoot(fn) {
|
|
361
|
+
const prevRoot = currentRoot;
|
|
362
|
+
const prevOwner = currentOwner;
|
|
363
|
+
const root = {
|
|
364
|
+
disposals: [],
|
|
365
|
+
owner: currentOwner,
|
|
366
|
+
// parent owner for ownership tree
|
|
367
|
+
children: [],
|
|
368
|
+
// child roots (ownership tree)
|
|
369
|
+
_disposed: false
|
|
370
|
+
};
|
|
371
|
+
if (currentOwner) {
|
|
372
|
+
currentOwner.children.push(root);
|
|
373
|
+
}
|
|
374
|
+
currentRoot = root;
|
|
375
|
+
currentOwner = root;
|
|
376
|
+
try {
|
|
377
|
+
const dispose = () => {
|
|
378
|
+
if (root._disposed) return;
|
|
379
|
+
root._disposed = true;
|
|
380
|
+
for (let i = root.children.length - 1; i >= 0; i--) {
|
|
381
|
+
_disposeRoot(root.children[i]);
|
|
382
|
+
}
|
|
383
|
+
root.children.length = 0;
|
|
384
|
+
for (let i = root.disposals.length - 1; i >= 0; i--) {
|
|
385
|
+
root.disposals[i]();
|
|
386
|
+
}
|
|
387
|
+
root.disposals.length = 0;
|
|
388
|
+
if (root.owner) {
|
|
389
|
+
const idx = root.owner.children.indexOf(root);
|
|
390
|
+
if (idx >= 0) root.owner.children.splice(idx, 1);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
return fn(dispose);
|
|
394
|
+
} finally {
|
|
395
|
+
currentRoot = prevRoot;
|
|
396
|
+
currentOwner = prevOwner;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function _disposeRoot(root) {
|
|
400
|
+
if (root._disposed) return;
|
|
401
|
+
root._disposed = true;
|
|
402
|
+
for (let i = root.children.length - 1; i >= 0; i--) {
|
|
403
|
+
_disposeRoot(root.children[i]);
|
|
404
|
+
}
|
|
405
|
+
root.children.length = 0;
|
|
406
|
+
for (let i = root.disposals.length - 1; i >= 0; i--) {
|
|
407
|
+
root.disposals[i]();
|
|
408
|
+
}
|
|
409
|
+
root.disposals.length = 0;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// packages/core/src/h.js
|
|
413
|
+
var EMPTY_OBJ = /* @__PURE__ */ Object.create(null);
|
|
414
|
+
function h(tag, props, ...children) {
|
|
415
|
+
props = props || EMPTY_OBJ;
|
|
416
|
+
const flat = flattenChildren(children);
|
|
417
|
+
const key = props.key ?? null;
|
|
418
|
+
if (props.key !== void 0) {
|
|
419
|
+
props = { ...props };
|
|
420
|
+
delete props.key;
|
|
421
|
+
}
|
|
422
|
+
return { tag, props, children: flat, key, _vnode: true };
|
|
423
|
+
}
|
|
424
|
+
function flattenChildren(children) {
|
|
425
|
+
const out = [];
|
|
426
|
+
for (let i = 0; i < children.length; i++) {
|
|
427
|
+
const child = children[i];
|
|
428
|
+
if (child == null || child === false || child === true) continue;
|
|
429
|
+
if (Array.isArray(child)) {
|
|
430
|
+
out.push(...flattenChildren(child));
|
|
431
|
+
} else if (typeof child === "object" && child._vnode) {
|
|
432
|
+
out.push(child);
|
|
433
|
+
} else if (typeof child === "function") {
|
|
434
|
+
out.push(child);
|
|
435
|
+
} else {
|
|
436
|
+
out.push(String(child));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
4
441
|
|
|
5
|
-
|
|
442
|
+
// packages/core/src/components.js
|
|
443
|
+
var _getCurrentComponent = null;
|
|
444
|
+
function _injectGetCurrentComponent(fn) {
|
|
445
|
+
_getCurrentComponent = fn;
|
|
446
|
+
}
|
|
447
|
+
function reportError(error, startCtx) {
|
|
448
|
+
let ctx = startCtx || _getCurrentComponent?.();
|
|
449
|
+
while (ctx) {
|
|
450
|
+
if (ctx._errorBoundary) {
|
|
451
|
+
ctx._errorBoundary(error);
|
|
452
|
+
return true;
|
|
453
|
+
}
|
|
454
|
+
ctx = ctx._parentCtx;
|
|
455
|
+
}
|
|
456
|
+
return false;
|
|
457
|
+
}
|
|
6
458
|
|
|
7
|
-
//
|
|
8
|
-
|
|
459
|
+
// packages/core/src/helpers.js
|
|
460
|
+
var _getCurrentComponentRef = null;
|
|
461
|
+
function _setComponentRef(fn) {
|
|
462
|
+
_getCurrentComponentRef = fn;
|
|
463
|
+
}
|
|
9
464
|
|
|
10
|
-
//
|
|
465
|
+
// packages/core/src/dom.js
|
|
466
|
+
var SVG_ELEMENTS = /* @__PURE__ */ new Set([
|
|
467
|
+
"svg",
|
|
468
|
+
"path",
|
|
469
|
+
"circle",
|
|
470
|
+
"rect",
|
|
471
|
+
"line",
|
|
472
|
+
"polyline",
|
|
473
|
+
"polygon",
|
|
474
|
+
"ellipse",
|
|
475
|
+
"g",
|
|
476
|
+
"defs",
|
|
477
|
+
"use",
|
|
478
|
+
"symbol",
|
|
479
|
+
"clipPath",
|
|
480
|
+
"mask",
|
|
481
|
+
"pattern",
|
|
482
|
+
"image",
|
|
483
|
+
"text",
|
|
484
|
+
"tspan",
|
|
485
|
+
"textPath",
|
|
486
|
+
"foreignObject",
|
|
487
|
+
"linearGradient",
|
|
488
|
+
"radialGradient",
|
|
489
|
+
"stop",
|
|
490
|
+
"marker",
|
|
491
|
+
"animate",
|
|
492
|
+
"animateTransform",
|
|
493
|
+
"animateMotion",
|
|
494
|
+
"set",
|
|
495
|
+
"filter",
|
|
496
|
+
"feBlend",
|
|
497
|
+
"feColorMatrix",
|
|
498
|
+
"feComponentTransfer",
|
|
499
|
+
"feComposite",
|
|
500
|
+
"feConvolveMatrix",
|
|
501
|
+
"feDiffuseLighting",
|
|
502
|
+
"feDisplacementMap",
|
|
503
|
+
"feFlood",
|
|
504
|
+
"feGaussianBlur",
|
|
505
|
+
"feImage",
|
|
506
|
+
"feMerge",
|
|
507
|
+
"feMergeNode",
|
|
508
|
+
"feMorphology",
|
|
509
|
+
"feOffset",
|
|
510
|
+
"feSpecularLighting",
|
|
511
|
+
"feTile",
|
|
512
|
+
"feTurbulence"
|
|
513
|
+
]);
|
|
514
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
515
|
+
var mountedComponents = /* @__PURE__ */ new Set();
|
|
516
|
+
var _commentCtxMap = /* @__PURE__ */ new WeakMap();
|
|
517
|
+
function isDomNode(value) {
|
|
518
|
+
if (!value || typeof value !== "object") return false;
|
|
519
|
+
if (typeof Node !== "undefined" && value instanceof Node) return true;
|
|
520
|
+
return typeof value.nodeType === "number" && typeof value.nodeName === "string";
|
|
521
|
+
}
|
|
522
|
+
function isVNode(value) {
|
|
523
|
+
return !!value && typeof value === "object" && (value._vnode === true || "tag" in value);
|
|
524
|
+
}
|
|
525
|
+
function disposeComponent(ctx) {
|
|
526
|
+
if (ctx.disposed) return;
|
|
527
|
+
ctx.disposed = true;
|
|
528
|
+
if (ctx.cleanups) {
|
|
529
|
+
for (const cleanup3 of ctx.cleanups) {
|
|
530
|
+
try {
|
|
531
|
+
cleanup3();
|
|
532
|
+
} catch (e) {
|
|
533
|
+
console.error("[what] cleanup error:", e);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (ctx.effects) {
|
|
538
|
+
for (const dispose of ctx.effects) {
|
|
539
|
+
try {
|
|
540
|
+
dispose();
|
|
541
|
+
} catch (e) {
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
if (ctx.hooks) {
|
|
546
|
+
for (const hook of ctx.hooks) {
|
|
547
|
+
if (hook && typeof hook.cleanup === "function") {
|
|
548
|
+
try {
|
|
549
|
+
hook.cleanup();
|
|
550
|
+
} catch (e) {
|
|
551
|
+
console.error("[what] hook cleanup error:", e);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (ctx._cleanupCallbacks) {
|
|
557
|
+
for (const fn of ctx._cleanupCallbacks) {
|
|
558
|
+
try {
|
|
559
|
+
fn();
|
|
560
|
+
} catch (e) {
|
|
561
|
+
console.error("[what] onCleanup error:", e);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (__DEV__ && __devtools?.onComponentUnmount) __devtools.onComponentUnmount(ctx);
|
|
566
|
+
mountedComponents.delete(ctx);
|
|
567
|
+
}
|
|
568
|
+
function disposeTree(node) {
|
|
569
|
+
if (!node) return;
|
|
570
|
+
if (node._componentCtx) {
|
|
571
|
+
disposeComponent(node._componentCtx);
|
|
572
|
+
}
|
|
573
|
+
const commentCtx = _commentCtxMap.get(node);
|
|
574
|
+
if (commentCtx) {
|
|
575
|
+
disposeComponent(commentCtx);
|
|
576
|
+
}
|
|
577
|
+
if (node._dispose) {
|
|
578
|
+
try {
|
|
579
|
+
node._dispose();
|
|
580
|
+
} catch (e) {
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (node._propEffects) {
|
|
584
|
+
for (const key in node._propEffects) {
|
|
585
|
+
try {
|
|
586
|
+
node._propEffects[key]();
|
|
587
|
+
} catch (e) {
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (node.childNodes) {
|
|
592
|
+
for (const child of node.childNodes) {
|
|
593
|
+
disposeTree(child);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function mount(vnode, container2) {
|
|
598
|
+
if (typeof container2 === "string") {
|
|
599
|
+
container2 = document.querySelector(container2);
|
|
600
|
+
}
|
|
601
|
+
disposeTree(container2);
|
|
602
|
+
container2.textContent = "";
|
|
603
|
+
const node = createDOM(vnode, container2);
|
|
604
|
+
if (node) container2.appendChild(node);
|
|
605
|
+
return () => {
|
|
606
|
+
disposeTree(container2);
|
|
607
|
+
container2.textContent = "";
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
function createDOM(vnode, parent, isSvg) {
|
|
611
|
+
if (vnode == null || vnode === false || vnode === true) {
|
|
612
|
+
return document.createComment("");
|
|
613
|
+
}
|
|
614
|
+
if (typeof vnode === "string" || typeof vnode === "number") {
|
|
615
|
+
return document.createTextNode(String(vnode));
|
|
616
|
+
}
|
|
617
|
+
if (isDomNode(vnode)) {
|
|
618
|
+
return vnode;
|
|
619
|
+
}
|
|
620
|
+
if (typeof vnode === "function") {
|
|
621
|
+
const startMarker = document.createComment("fn");
|
|
622
|
+
const endMarker = document.createComment("/fn");
|
|
623
|
+
let currentNodes = [];
|
|
624
|
+
const frag = document.createDocumentFragment();
|
|
625
|
+
frag.appendChild(startMarker);
|
|
626
|
+
frag.appendChild(endMarker);
|
|
627
|
+
const dispose = effect(() => {
|
|
628
|
+
const val = vnode();
|
|
629
|
+
const vnodes = val == null || val === false || val === true ? [] : Array.isArray(val) ? val : [val];
|
|
630
|
+
const realParent = endMarker.parentNode;
|
|
631
|
+
if (!realParent) return;
|
|
632
|
+
for (const old of currentNodes) {
|
|
633
|
+
disposeTree(old);
|
|
634
|
+
if (old.parentNode === realParent) realParent.removeChild(old);
|
|
635
|
+
}
|
|
636
|
+
currentNodes = [];
|
|
637
|
+
for (const v of vnodes) {
|
|
638
|
+
const node = createDOM(v, realParent, parent?._isSvg);
|
|
639
|
+
if (node) {
|
|
640
|
+
if (node.nodeType === 11) {
|
|
641
|
+
const children = Array.from(node.childNodes);
|
|
642
|
+
realParent.insertBefore(node, endMarker);
|
|
643
|
+
for (const child of children) currentNodes.push(child);
|
|
644
|
+
} else {
|
|
645
|
+
realParent.insertBefore(node, endMarker);
|
|
646
|
+
currentNodes.push(node);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
startMarker._dispose = dispose;
|
|
652
|
+
endMarker._dispose = dispose;
|
|
653
|
+
return frag;
|
|
654
|
+
}
|
|
655
|
+
if (Array.isArray(vnode)) {
|
|
656
|
+
const frag = document.createDocumentFragment();
|
|
657
|
+
for (const child of vnode) {
|
|
658
|
+
const node = createDOM(child, parent, isSvg);
|
|
659
|
+
if (node) frag.appendChild(node);
|
|
660
|
+
}
|
|
661
|
+
return frag;
|
|
662
|
+
}
|
|
663
|
+
if (isVNode(vnode) && typeof vnode.tag === "function") {
|
|
664
|
+
return createComponent(vnode, parent, isSvg);
|
|
665
|
+
}
|
|
666
|
+
if (isVNode(vnode)) {
|
|
667
|
+
return createElementFromVNode(vnode, parent, isSvg);
|
|
668
|
+
}
|
|
669
|
+
return document.createTextNode(String(vnode));
|
|
670
|
+
}
|
|
671
|
+
var componentStack = [];
|
|
672
|
+
function getCurrentComponent() {
|
|
673
|
+
return componentStack[componentStack.length - 1];
|
|
674
|
+
}
|
|
675
|
+
_injectGetCurrentComponent(getCurrentComponent);
|
|
676
|
+
_setComponentRef(getCurrentComponent);
|
|
677
|
+
function createComponent(vnode, parent, isSvg) {
|
|
678
|
+
let { tag: Component, props, children } = vnode;
|
|
679
|
+
if (typeof Component === "function" && (Component.prototype?.isReactComponent || Component.prototype?.render)) {
|
|
680
|
+
const ClassComp = Component;
|
|
681
|
+
Component = function ClassComponentBridge(props2) {
|
|
682
|
+
const instance = new ClassComp(props2);
|
|
683
|
+
return instance.render();
|
|
684
|
+
};
|
|
685
|
+
Component.displayName = ClassComp.displayName || ClassComp.name || "ClassComponent";
|
|
686
|
+
}
|
|
687
|
+
if (Component === "__errorBoundary" || vnode.tag === "__errorBoundary") {
|
|
688
|
+
return createErrorBoundary(vnode, parent);
|
|
689
|
+
}
|
|
690
|
+
if (Component === "__suspense" || vnode.tag === "__suspense") {
|
|
691
|
+
return createSuspenseBoundary(vnode, parent);
|
|
692
|
+
}
|
|
693
|
+
if (Component === "__portal" || vnode.tag === "__portal") {
|
|
694
|
+
return createPortalDOM(vnode, parent);
|
|
695
|
+
}
|
|
696
|
+
const ctx = {
|
|
697
|
+
hooks: [],
|
|
698
|
+
hookIndex: 0,
|
|
699
|
+
effects: [],
|
|
700
|
+
cleanups: [],
|
|
701
|
+
mounted: false,
|
|
702
|
+
disposed: false,
|
|
703
|
+
Component,
|
|
704
|
+
_parentCtx: componentStack[componentStack.length - 1] || null,
|
|
705
|
+
_errorBoundary: (() => {
|
|
706
|
+
let p = componentStack[componentStack.length - 1];
|
|
707
|
+
while (p) {
|
|
708
|
+
if (p._errorBoundary) return p._errorBoundary;
|
|
709
|
+
p = p._parentCtx;
|
|
710
|
+
}
|
|
711
|
+
return null;
|
|
712
|
+
})()
|
|
713
|
+
};
|
|
714
|
+
const startComment = document.createComment("c:start");
|
|
715
|
+
const endComment = document.createComment("c:end");
|
|
716
|
+
_commentCtxMap.set(startComment, ctx);
|
|
717
|
+
ctx._startComment = startComment;
|
|
718
|
+
ctx._endComment = endComment;
|
|
719
|
+
const container2 = document.createDocumentFragment();
|
|
720
|
+
container2._componentCtx = ctx;
|
|
721
|
+
ctx._wrapper = startComment;
|
|
722
|
+
mountedComponents.add(ctx);
|
|
723
|
+
if (__DEV__ && __devtools?.onComponentMount) __devtools.onComponentMount(ctx);
|
|
724
|
+
const propsChildren = children.length === 0 ? void 0 : children.length === 1 ? children[0] : children;
|
|
725
|
+
const propsSignal = signal({ ...props, children: propsChildren });
|
|
726
|
+
ctx._propsSignal = propsSignal;
|
|
727
|
+
const reactiveProps = new Proxy({}, {
|
|
728
|
+
get(_, key) {
|
|
729
|
+
const current = propsSignal();
|
|
730
|
+
return current[key];
|
|
731
|
+
},
|
|
732
|
+
has(_, key) {
|
|
733
|
+
const current = propsSignal();
|
|
734
|
+
return key in current;
|
|
735
|
+
},
|
|
736
|
+
ownKeys() {
|
|
737
|
+
const current = propsSignal();
|
|
738
|
+
return Reflect.ownKeys(current);
|
|
739
|
+
},
|
|
740
|
+
getOwnPropertyDescriptor(_, key) {
|
|
741
|
+
const current = propsSignal();
|
|
742
|
+
if (key in current) {
|
|
743
|
+
return { value: current[key], writable: false, enumerable: true, configurable: true };
|
|
744
|
+
}
|
|
745
|
+
return void 0;
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
componentStack.push(ctx);
|
|
749
|
+
let result;
|
|
750
|
+
try {
|
|
751
|
+
result = Component(reactiveProps);
|
|
752
|
+
} catch (error) {
|
|
753
|
+
componentStack.pop();
|
|
754
|
+
if (!reportError(error, ctx)) {
|
|
755
|
+
console.error("[what] Uncaught error in component:", Component.name || "Anonymous", error);
|
|
756
|
+
throw error;
|
|
757
|
+
}
|
|
758
|
+
container2.appendChild(startComment);
|
|
759
|
+
container2.appendChild(endComment);
|
|
760
|
+
return container2;
|
|
761
|
+
}
|
|
762
|
+
componentStack.pop();
|
|
763
|
+
ctx.mounted = true;
|
|
764
|
+
if (ctx._mountCallbacks) {
|
|
765
|
+
queueMicrotask(() => {
|
|
766
|
+
if (ctx.disposed) return;
|
|
767
|
+
for (const fn of ctx._mountCallbacks) {
|
|
768
|
+
try {
|
|
769
|
+
fn();
|
|
770
|
+
} catch (e) {
|
|
771
|
+
console.error("[what] onMount error:", e);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
container2.appendChild(startComment);
|
|
777
|
+
const vnodes = Array.isArray(result) ? result : [result];
|
|
778
|
+
for (const v of vnodes) {
|
|
779
|
+
const node = createDOM(v, container2, isSvg);
|
|
780
|
+
if (node) container2.appendChild(node);
|
|
781
|
+
}
|
|
782
|
+
container2.appendChild(endComment);
|
|
783
|
+
return container2;
|
|
784
|
+
}
|
|
785
|
+
function createErrorBoundary(vnode, parent) {
|
|
786
|
+
const { errorState, handleError, fallback, reset } = vnode.props;
|
|
787
|
+
const children = vnode.children;
|
|
788
|
+
const startComment = document.createComment("eb:start");
|
|
789
|
+
const endComment = document.createComment("eb:end");
|
|
790
|
+
const boundaryCtx = {
|
|
791
|
+
hooks: [],
|
|
792
|
+
hookIndex: 0,
|
|
793
|
+
effects: [],
|
|
794
|
+
cleanups: [],
|
|
795
|
+
mounted: false,
|
|
796
|
+
disposed: false,
|
|
797
|
+
_parentCtx: componentStack[componentStack.length - 1] || null,
|
|
798
|
+
_errorBoundary: handleError,
|
|
799
|
+
_startComment: startComment,
|
|
800
|
+
_endComment: endComment
|
|
801
|
+
};
|
|
802
|
+
_commentCtxMap.set(startComment, boundaryCtx);
|
|
803
|
+
const container2 = document.createDocumentFragment();
|
|
804
|
+
container2._componentCtx = boundaryCtx;
|
|
805
|
+
container2.appendChild(startComment);
|
|
806
|
+
container2.appendChild(endComment);
|
|
807
|
+
const dispose = effect(() => {
|
|
808
|
+
const error = errorState();
|
|
809
|
+
componentStack.push(boundaryCtx);
|
|
810
|
+
if (startComment.parentNode) {
|
|
811
|
+
while (startComment.nextSibling && startComment.nextSibling !== endComment) {
|
|
812
|
+
const old = startComment.nextSibling;
|
|
813
|
+
disposeTree(old);
|
|
814
|
+
old.parentNode.removeChild(old);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
let vnodes;
|
|
818
|
+
if (error) {
|
|
819
|
+
vnodes = typeof fallback === "function" ? [fallback({ error, reset })] : [fallback];
|
|
820
|
+
} else {
|
|
821
|
+
vnodes = children;
|
|
822
|
+
}
|
|
823
|
+
vnodes = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
824
|
+
for (const v of vnodes) {
|
|
825
|
+
const node = createDOM(v, parent);
|
|
826
|
+
if (node) {
|
|
827
|
+
if (endComment.parentNode) {
|
|
828
|
+
endComment.parentNode.insertBefore(node, endComment);
|
|
829
|
+
} else {
|
|
830
|
+
container2.insertBefore(node, endComment);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
componentStack.pop();
|
|
835
|
+
});
|
|
836
|
+
boundaryCtx.effects.push(dispose);
|
|
837
|
+
return container2;
|
|
838
|
+
}
|
|
839
|
+
function createSuspenseBoundary(vnode, parent) {
|
|
840
|
+
const { boundary, fallback, loading } = vnode.props;
|
|
841
|
+
const children = vnode.children;
|
|
842
|
+
const startComment = document.createComment("sb:start");
|
|
843
|
+
const endComment = document.createComment("sb:end");
|
|
844
|
+
const boundaryCtx = {
|
|
845
|
+
hooks: [],
|
|
846
|
+
hookIndex: 0,
|
|
847
|
+
effects: [],
|
|
848
|
+
cleanups: [],
|
|
849
|
+
mounted: false,
|
|
850
|
+
disposed: false,
|
|
851
|
+
_parentCtx: componentStack[componentStack.length - 1] || null,
|
|
852
|
+
_startComment: startComment,
|
|
853
|
+
_endComment: endComment
|
|
854
|
+
};
|
|
855
|
+
_commentCtxMap.set(startComment, boundaryCtx);
|
|
856
|
+
const container2 = document.createDocumentFragment();
|
|
857
|
+
container2._componentCtx = boundaryCtx;
|
|
858
|
+
container2.appendChild(startComment);
|
|
859
|
+
container2.appendChild(endComment);
|
|
860
|
+
const dispose = effect(() => {
|
|
861
|
+
const isLoading = loading();
|
|
862
|
+
const vnodes = isLoading ? [fallback] : children;
|
|
863
|
+
const normalized = Array.isArray(vnodes) ? vnodes : [vnodes];
|
|
864
|
+
componentStack.push(boundaryCtx);
|
|
865
|
+
if (startComment.parentNode) {
|
|
866
|
+
while (startComment.nextSibling && startComment.nextSibling !== endComment) {
|
|
867
|
+
const old = startComment.nextSibling;
|
|
868
|
+
disposeTree(old);
|
|
869
|
+
old.parentNode.removeChild(old);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
for (const v of normalized) {
|
|
873
|
+
const node = createDOM(v, parent);
|
|
874
|
+
if (node) {
|
|
875
|
+
if (endComment.parentNode) {
|
|
876
|
+
endComment.parentNode.insertBefore(node, endComment);
|
|
877
|
+
} else {
|
|
878
|
+
container2.insertBefore(node, endComment);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
componentStack.pop();
|
|
883
|
+
});
|
|
884
|
+
boundaryCtx.effects.push(dispose);
|
|
885
|
+
return container2;
|
|
886
|
+
}
|
|
887
|
+
function createPortalDOM(vnode, parent) {
|
|
888
|
+
const { container: container2 } = vnode.props;
|
|
889
|
+
const children = vnode.children;
|
|
890
|
+
if (!container2) {
|
|
891
|
+
console.warn("[what] Portal: target container not found");
|
|
892
|
+
return document.createComment("portal:empty");
|
|
893
|
+
}
|
|
894
|
+
const portalCtx = {
|
|
895
|
+
hooks: [],
|
|
896
|
+
hookIndex: 0,
|
|
897
|
+
effects: [],
|
|
898
|
+
cleanups: [],
|
|
899
|
+
mounted: false,
|
|
900
|
+
disposed: false,
|
|
901
|
+
_parentCtx: componentStack[componentStack.length - 1] || null
|
|
902
|
+
};
|
|
903
|
+
const placeholder = document.createComment("portal");
|
|
904
|
+
placeholder._componentCtx = portalCtx;
|
|
905
|
+
const portalNodes = [];
|
|
906
|
+
for (const child of children) {
|
|
907
|
+
const node = createDOM(child, container2);
|
|
908
|
+
if (node) {
|
|
909
|
+
container2.appendChild(node);
|
|
910
|
+
portalNodes.push(node);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
portalCtx._cleanupCallbacks = [() => {
|
|
914
|
+
for (const node of portalNodes) {
|
|
915
|
+
disposeTree(node);
|
|
916
|
+
if (node.parentNode) node.parentNode.removeChild(node);
|
|
917
|
+
}
|
|
918
|
+
}];
|
|
919
|
+
return placeholder;
|
|
920
|
+
}
|
|
921
|
+
function createElementFromVNode(vnode, parent, isSvg) {
|
|
922
|
+
const { tag, props, children } = vnode;
|
|
923
|
+
const svgContext = isSvg || SVG_ELEMENTS.has(tag);
|
|
924
|
+
const el = svgContext ? document.createElementNS(SVG_NS, tag) : document.createElement(tag);
|
|
925
|
+
if (props) {
|
|
926
|
+
applyProps(el, props, {}, svgContext);
|
|
927
|
+
}
|
|
928
|
+
for (const child of children) {
|
|
929
|
+
const node = createDOM(child, el, svgContext && tag !== "foreignObject");
|
|
930
|
+
if (node) el.appendChild(node);
|
|
931
|
+
}
|
|
932
|
+
el._vnode = vnode;
|
|
933
|
+
return el;
|
|
934
|
+
}
|
|
935
|
+
function applyProps(el, newProps, oldProps, isSvg) {
|
|
936
|
+
newProps = newProps || {};
|
|
937
|
+
oldProps = oldProps || {};
|
|
938
|
+
for (const key in newProps) {
|
|
939
|
+
if (key === "key" || key === "children") continue;
|
|
940
|
+
if (key === "ref") {
|
|
941
|
+
if (typeof newProps.ref === "function") newProps.ref(el);
|
|
942
|
+
else if (newProps.ref) newProps.ref.current = el;
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
setProp(el, key, newProps[key], isSvg);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function setProp(el, key, value, isSvg) {
|
|
949
|
+
if (typeof value === "function" && !(key.startsWith("on") && key.length > 2) && key !== "ref") {
|
|
950
|
+
if (!el._propEffects) el._propEffects = {};
|
|
951
|
+
if (el._propEffects[key]) {
|
|
952
|
+
try {
|
|
953
|
+
el._propEffects[key]();
|
|
954
|
+
} catch (e) {
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
el._propEffects[key] = effect(() => {
|
|
958
|
+
const resolved = value();
|
|
959
|
+
setProp(el, key, resolved, isSvg);
|
|
960
|
+
});
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
if (key.startsWith("on") && key.length > 2) {
|
|
964
|
+
let eventName = key.slice(2);
|
|
965
|
+
let useCapture = false;
|
|
966
|
+
if (eventName.endsWith("Capture")) {
|
|
967
|
+
eventName = eventName.slice(0, -7);
|
|
968
|
+
useCapture = true;
|
|
969
|
+
}
|
|
970
|
+
const event = eventName.toLowerCase();
|
|
971
|
+
const storageKey = useCapture ? event + "_capture" : event;
|
|
972
|
+
const old = el._events?.[storageKey];
|
|
973
|
+
if (old && old._original === value) return;
|
|
974
|
+
if (old) el.removeEventListener(event, old, useCapture);
|
|
975
|
+
if (value == null) return;
|
|
976
|
+
if (!el._events) el._events = {};
|
|
977
|
+
const wrappedHandler = (e) => {
|
|
978
|
+
if (!e.nativeEvent) e.nativeEvent = e;
|
|
979
|
+
return untrack(() => value(e));
|
|
980
|
+
};
|
|
981
|
+
wrappedHandler._original = value;
|
|
982
|
+
el._events[storageKey] = wrappedHandler;
|
|
983
|
+
const eventOpts = value._eventOpts;
|
|
984
|
+
el.addEventListener(event, wrappedHandler, eventOpts || useCapture || void 0);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (key === "className" || key === "class") {
|
|
988
|
+
if (isSvg) {
|
|
989
|
+
el.setAttribute("class", value || "");
|
|
990
|
+
} else {
|
|
991
|
+
el.className = value || "";
|
|
992
|
+
}
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
if (key === "style") {
|
|
996
|
+
if (typeof value === "string") {
|
|
997
|
+
el.style.cssText = value;
|
|
998
|
+
el._prevStyle = null;
|
|
999
|
+
} else if (typeof value === "object") {
|
|
1000
|
+
const oldStyle = el._prevStyle || {};
|
|
1001
|
+
for (const prop in oldStyle) {
|
|
1002
|
+
if (!(prop in value)) el.style[prop] = "";
|
|
1003
|
+
}
|
|
1004
|
+
for (const prop in value) {
|
|
1005
|
+
el.style[prop] = value[prop] ?? "";
|
|
1006
|
+
}
|
|
1007
|
+
el._prevStyle = { ...value };
|
|
1008
|
+
}
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
if (key === "dangerouslySetInnerHTML") {
|
|
1012
|
+
el.innerHTML = value?.__html ?? "";
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
if (key === "innerHTML") {
|
|
1016
|
+
if (value == null) return;
|
|
1017
|
+
if (value && typeof value === "object" && "__html" in value) {
|
|
1018
|
+
el.innerHTML = value.__html ?? "";
|
|
1019
|
+
} else {
|
|
1020
|
+
if (__DEV__) {
|
|
1021
|
+
console.warn(
|
|
1022
|
+
"[what] innerHTML received a raw string. This is a security risk (XSS). Use innerHTML={{ __html: trustedString }} or dangerouslySetInnerHTML={{ __html: trustedString }} instead."
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (typeof value === "boolean") {
|
|
1030
|
+
if (value) el.setAttribute(key, "");
|
|
1031
|
+
else el.removeAttribute(key);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if (key.startsWith("data-") || key.startsWith("aria-")) {
|
|
1035
|
+
el.setAttribute(key, value);
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
if (isSvg) {
|
|
1039
|
+
if (value === false || value == null) {
|
|
1040
|
+
el.removeAttribute(key);
|
|
1041
|
+
} else {
|
|
1042
|
+
el.setAttribute(key, value === true ? "" : String(value));
|
|
1043
|
+
}
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (key in el) {
|
|
1047
|
+
el[key] = value;
|
|
1048
|
+
} else {
|
|
1049
|
+
el.setAttribute(key, value);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
11
1052
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
container
|
|
1053
|
+
// packages/core/src/testing.js
|
|
1054
|
+
var container = null;
|
|
1055
|
+
function setupDOM() {
|
|
1056
|
+
if (typeof document !== "undefined") {
|
|
1057
|
+
container = document.createElement("div");
|
|
1058
|
+
container.id = "test-root";
|
|
17
1059
|
document.body.appendChild(container);
|
|
18
1060
|
}
|
|
19
1061
|
return container;
|
|
20
1062
|
}
|
|
21
|
-
|
|
22
|
-
export function cleanup() {
|
|
1063
|
+
function cleanup2() {
|
|
23
1064
|
if (container) {
|
|
24
|
-
container.innerHTML =
|
|
1065
|
+
container.innerHTML = "";
|
|
25
1066
|
if (container.parentNode) {
|
|
26
1067
|
container.parentNode.removeChild(container);
|
|
27
1068
|
}
|
|
28
1069
|
container = null;
|
|
29
1070
|
}
|
|
30
1071
|
}
|
|
31
|
-
|
|
32
|
-
// --- Render ---
|
|
33
|
-
|
|
34
|
-
export function render(vnode, options = {}) {
|
|
1072
|
+
function render(vnode, options = {}) {
|
|
35
1073
|
const { container: customContainer } = options;
|
|
36
1074
|
const target = customContainer || setupDOM();
|
|
37
|
-
|
|
38
1075
|
if (!target) {
|
|
39
|
-
throw new Error(
|
|
1076
|
+
throw new Error("No DOM container available. Are you running in Node.js without jsdom?");
|
|
40
1077
|
}
|
|
41
|
-
|
|
42
1078
|
const unmount = mount(vnode, target);
|
|
43
|
-
|
|
44
1079
|
return {
|
|
45
1080
|
container: target,
|
|
46
1081
|
unmount,
|
|
@@ -55,245 +1090,314 @@ export function render(vnode, options = {}) {
|
|
|
55
1090
|
debug: () => console.log(target.innerHTML),
|
|
56
1091
|
// Async utilities
|
|
57
1092
|
findByText: (text, timeout) => waitFor(() => queryByText(target, text), { timeout }),
|
|
58
|
-
findByTestId: (id, timeout) => waitFor(() => target.querySelector(`[data-testid="${id}"]`), { timeout })
|
|
1093
|
+
findByTestId: (id, timeout) => waitFor(() => target.querySelector(`[data-testid="${id}"]`), { timeout })
|
|
59
1094
|
};
|
|
60
1095
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
1096
|
+
function renderTest(Component, props) {
|
|
1097
|
+
const target = setupDOM();
|
|
1098
|
+
if (!target) {
|
|
1099
|
+
throw new Error("No DOM container available. Are you running in Node.js without jsdom?");
|
|
1100
|
+
}
|
|
1101
|
+
const signalRegistry = {};
|
|
1102
|
+
let rootDispose = null;
|
|
1103
|
+
let unmountFn;
|
|
1104
|
+
createRoot((dispose) => {
|
|
1105
|
+
rootDispose = dispose;
|
|
1106
|
+
const vnode = h(Component, props || {});
|
|
1107
|
+
unmountFn = mount(vnode, target);
|
|
1108
|
+
});
|
|
1109
|
+
return {
|
|
1110
|
+
container: target,
|
|
1111
|
+
// Proxy to access component signals by name
|
|
1112
|
+
signals: new Proxy(signalRegistry, {
|
|
1113
|
+
get(obj, prop) {
|
|
1114
|
+
if (prop in obj) return obj[prop];
|
|
1115
|
+
return void 0;
|
|
1116
|
+
},
|
|
1117
|
+
set(obj, prop, value) {
|
|
1118
|
+
obj[prop] = value;
|
|
1119
|
+
return true;
|
|
1120
|
+
}
|
|
1121
|
+
}),
|
|
1122
|
+
// Synchronous flush: run all pending effects immediately
|
|
1123
|
+
update() {
|
|
1124
|
+
flushSync();
|
|
1125
|
+
},
|
|
1126
|
+
unmount() {
|
|
1127
|
+
if (unmountFn) unmountFn();
|
|
1128
|
+
if (rootDispose) rootDispose();
|
|
1129
|
+
cleanup2();
|
|
1130
|
+
},
|
|
1131
|
+
// Query helpers
|
|
1132
|
+
getByText: (text) => queryByText(target, text),
|
|
1133
|
+
getByTestId: (id) => target.querySelector(`[data-testid="${id}"]`),
|
|
1134
|
+
queryByText: (text) => queryByText(target, text),
|
|
1135
|
+
debug: () => console.log(target.innerHTML)
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
function flushEffects() {
|
|
1139
|
+
flushSync();
|
|
1140
|
+
}
|
|
1141
|
+
function trackSignals(fn) {
|
|
1142
|
+
const accessed = [];
|
|
1143
|
+
const written = [];
|
|
1144
|
+
const _origSignal = signal;
|
|
1145
|
+
const trackedSignals = /* @__PURE__ */ new Map();
|
|
1146
|
+
const trackRead = (name) => {
|
|
1147
|
+
if (!accessed.includes(name)) accessed.push(name);
|
|
1148
|
+
};
|
|
1149
|
+
const trackWrite = (name) => {
|
|
1150
|
+
if (!written.includes(name)) written.push(name);
|
|
1151
|
+
};
|
|
1152
|
+
let dispose;
|
|
1153
|
+
createRoot((d) => {
|
|
1154
|
+
dispose = d;
|
|
1155
|
+
const e = effect(() => {
|
|
1156
|
+
fn();
|
|
1157
|
+
});
|
|
1158
|
+
});
|
|
1159
|
+
if (dispose) dispose();
|
|
1160
|
+
return { accessed, written };
|
|
1161
|
+
}
|
|
1162
|
+
function mockSignal(name, initialValue) {
|
|
1163
|
+
const history = [initialValue];
|
|
1164
|
+
let setCount = 0;
|
|
1165
|
+
const s = signal(initialValue, name);
|
|
1166
|
+
const origSet = s.set;
|
|
1167
|
+
s.set = function(next) {
|
|
1168
|
+
const nextVal = typeof next === "function" ? next(s.peek()) : next;
|
|
1169
|
+
if (!Object.is(s.peek(), nextVal)) {
|
|
1170
|
+
setCount++;
|
|
1171
|
+
history.push(nextVal);
|
|
1172
|
+
}
|
|
1173
|
+
return origSet(nextVal);
|
|
1174
|
+
};
|
|
1175
|
+
const origFn = s;
|
|
1176
|
+
const mock = function(...args) {
|
|
1177
|
+
if (args.length === 0) {
|
|
1178
|
+
return origFn();
|
|
1179
|
+
}
|
|
1180
|
+
const nextVal = typeof args[0] === "function" ? args[0](origFn.peek()) : args[0];
|
|
1181
|
+
if (!Object.is(origFn.peek(), nextVal)) {
|
|
1182
|
+
setCount++;
|
|
1183
|
+
history.push(nextVal);
|
|
1184
|
+
}
|
|
1185
|
+
return origFn(nextVal);
|
|
1186
|
+
};
|
|
1187
|
+
mock._signal = true;
|
|
1188
|
+
mock.peek = s.peek;
|
|
1189
|
+
mock.set = s.set;
|
|
1190
|
+
mock.subscribe = s.subscribe;
|
|
1191
|
+
if (s._debugName) mock._debugName = s._debugName;
|
|
1192
|
+
if (s._subs) mock._subs = s._subs;
|
|
1193
|
+
Object.defineProperty(mock, "history", {
|
|
1194
|
+
get() {
|
|
1195
|
+
return history;
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
Object.defineProperty(mock, "setCount", {
|
|
1199
|
+
get() {
|
|
1200
|
+
return setCount;
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
mock.reset = function(value) {
|
|
1204
|
+
const resetVal = value !== void 0 ? value : initialValue;
|
|
1205
|
+
history.length = 0;
|
|
1206
|
+
history.push(resetVal);
|
|
1207
|
+
setCount = 0;
|
|
1208
|
+
origFn(resetVal);
|
|
1209
|
+
};
|
|
1210
|
+
return mock;
|
|
1211
|
+
}
|
|
1212
|
+
function queryByText(container2, text) {
|
|
65
1213
|
const regex = text instanceof RegExp ? text : null;
|
|
66
1214
|
const walker = document.createTreeWalker(
|
|
67
|
-
|
|
1215
|
+
container2,
|
|
68
1216
|
NodeFilter.SHOW_TEXT,
|
|
69
1217
|
null,
|
|
70
1218
|
false
|
|
71
1219
|
);
|
|
72
|
-
|
|
73
1220
|
while (walker.nextNode()) {
|
|
74
1221
|
const node = walker.currentNode;
|
|
75
|
-
const matches = regex
|
|
76
|
-
? regex.test(node.textContent)
|
|
77
|
-
: node.textContent.includes(text);
|
|
1222
|
+
const matches = regex ? regex.test(node.textContent) : node.textContent.includes(text);
|
|
78
1223
|
if (matches) {
|
|
79
1224
|
return node.parentElement;
|
|
80
1225
|
}
|
|
81
1226
|
}
|
|
82
1227
|
return null;
|
|
83
1228
|
}
|
|
84
|
-
|
|
85
|
-
function queryAllByText(container, text) {
|
|
1229
|
+
function queryAllByText(container2, text) {
|
|
86
1230
|
const results = [];
|
|
87
1231
|
const regex = text instanceof RegExp ? text : null;
|
|
88
1232
|
const walker = document.createTreeWalker(
|
|
89
|
-
|
|
1233
|
+
container2,
|
|
90
1234
|
NodeFilter.SHOW_TEXT,
|
|
91
1235
|
null,
|
|
92
1236
|
false
|
|
93
1237
|
);
|
|
94
|
-
|
|
95
1238
|
while (walker.nextNode()) {
|
|
96
1239
|
const node = walker.currentNode;
|
|
97
|
-
const matches = regex
|
|
98
|
-
? regex.test(node.textContent)
|
|
99
|
-
: node.textContent.includes(text);
|
|
1240
|
+
const matches = regex ? regex.test(node.textContent) : node.textContent.includes(text);
|
|
100
1241
|
if (matches) {
|
|
101
1242
|
results.push(node.parentElement);
|
|
102
1243
|
}
|
|
103
1244
|
}
|
|
104
1245
|
return results;
|
|
105
1246
|
}
|
|
106
|
-
|
|
107
|
-
// --- Fire Events ---
|
|
108
|
-
|
|
109
|
-
export const fireEvent = {
|
|
1247
|
+
var fireEvent = {
|
|
110
1248
|
click(element) {
|
|
111
|
-
const event = new MouseEvent(
|
|
1249
|
+
const event = new MouseEvent("click", {
|
|
112
1250
|
bubbles: true,
|
|
113
1251
|
cancelable: true,
|
|
114
|
-
view: typeof window !==
|
|
1252
|
+
view: typeof window !== "undefined" ? window : void 0
|
|
115
1253
|
});
|
|
116
1254
|
element.dispatchEvent(event);
|
|
117
1255
|
return event;
|
|
118
1256
|
},
|
|
119
|
-
|
|
120
1257
|
change(element, value) {
|
|
121
1258
|
element.value = value;
|
|
122
|
-
const event = new Event(
|
|
1259
|
+
const event = new Event("input", { bubbles: true });
|
|
123
1260
|
element.dispatchEvent(event);
|
|
124
|
-
const changeEvent = new Event(
|
|
1261
|
+
const changeEvent = new Event("change", { bubbles: true });
|
|
125
1262
|
element.dispatchEvent(changeEvent);
|
|
126
1263
|
return changeEvent;
|
|
127
1264
|
},
|
|
128
|
-
|
|
129
1265
|
input(element, value) {
|
|
130
1266
|
element.value = value;
|
|
131
|
-
const event = new Event(
|
|
1267
|
+
const event = new Event("input", { bubbles: true });
|
|
132
1268
|
element.dispatchEvent(event);
|
|
133
1269
|
return event;
|
|
134
1270
|
},
|
|
135
|
-
|
|
136
1271
|
submit(element) {
|
|
137
|
-
const event = new Event(
|
|
1272
|
+
const event = new Event("submit", { bubbles: true, cancelable: true });
|
|
138
1273
|
element.dispatchEvent(event);
|
|
139
1274
|
return event;
|
|
140
1275
|
},
|
|
141
|
-
|
|
142
1276
|
focus(element) {
|
|
143
1277
|
element.focus();
|
|
144
|
-
const event = new FocusEvent(
|
|
1278
|
+
const event = new FocusEvent("focus", { bubbles: true });
|
|
145
1279
|
element.dispatchEvent(event);
|
|
146
1280
|
return event;
|
|
147
1281
|
},
|
|
148
|
-
|
|
149
1282
|
blur(element) {
|
|
150
1283
|
element.blur();
|
|
151
|
-
const event = new FocusEvent(
|
|
1284
|
+
const event = new FocusEvent("blur", { bubbles: true });
|
|
152
1285
|
element.dispatchEvent(event);
|
|
153
1286
|
return event;
|
|
154
1287
|
},
|
|
155
|
-
|
|
156
1288
|
keyDown(element, key, options = {}) {
|
|
157
|
-
const event = new KeyboardEvent(
|
|
1289
|
+
const event = new KeyboardEvent("keydown", {
|
|
158
1290
|
bubbles: true,
|
|
159
1291
|
cancelable: true,
|
|
160
1292
|
key,
|
|
161
|
-
...options
|
|
1293
|
+
...options
|
|
162
1294
|
});
|
|
163
1295
|
element.dispatchEvent(event);
|
|
164
1296
|
return event;
|
|
165
1297
|
},
|
|
166
|
-
|
|
167
1298
|
keyUp(element, key, options = {}) {
|
|
168
|
-
const event = new KeyboardEvent(
|
|
1299
|
+
const event = new KeyboardEvent("keyup", {
|
|
169
1300
|
bubbles: true,
|
|
170
1301
|
cancelable: true,
|
|
171
1302
|
key,
|
|
172
|
-
...options
|
|
1303
|
+
...options
|
|
173
1304
|
});
|
|
174
1305
|
element.dispatchEvent(event);
|
|
175
1306
|
return event;
|
|
176
1307
|
},
|
|
177
|
-
|
|
178
1308
|
mouseEnter(element) {
|
|
179
|
-
const event = new MouseEvent(
|
|
1309
|
+
const event = new MouseEvent("mouseenter", { bubbles: true });
|
|
180
1310
|
element.dispatchEvent(event);
|
|
181
1311
|
return event;
|
|
182
1312
|
},
|
|
183
|
-
|
|
184
1313
|
mouseLeave(element) {
|
|
185
|
-
const event = new MouseEvent(
|
|
1314
|
+
const event = new MouseEvent("mouseleave", { bubbles: true });
|
|
186
1315
|
element.dispatchEvent(event);
|
|
187
1316
|
return event;
|
|
188
|
-
}
|
|
1317
|
+
}
|
|
189
1318
|
};
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
export async function waitFor(callback, options = {}) {
|
|
194
|
-
const { timeout = 1000, interval = 50 } = options;
|
|
1319
|
+
async function waitFor(callback, options = {}) {
|
|
1320
|
+
const { timeout = 1e3, interval = 50 } = options;
|
|
195
1321
|
const startTime = Date.now();
|
|
196
|
-
|
|
197
1322
|
while (Date.now() - startTime < timeout) {
|
|
198
1323
|
try {
|
|
199
1324
|
const result = callback();
|
|
200
1325
|
if (result) return result;
|
|
201
1326
|
} catch (e) {
|
|
202
|
-
// Keep waiting
|
|
203
1327
|
}
|
|
204
|
-
await new Promise(r => setTimeout(r, interval));
|
|
1328
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
205
1329
|
}
|
|
206
|
-
|
|
207
1330
|
throw new Error(`waitFor timed out after ${timeout}ms`);
|
|
208
1331
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const { timeout = 1000, interval = 50 } = options;
|
|
1332
|
+
async function waitForElementToBeRemoved(callback, options = {}) {
|
|
1333
|
+
const { timeout = 1e3, interval = 50 } = options;
|
|
212
1334
|
const startTime = Date.now();
|
|
213
|
-
|
|
214
|
-
// First, element should exist
|
|
215
1335
|
let element = callback();
|
|
216
1336
|
if (!element) {
|
|
217
|
-
throw new Error(
|
|
1337
|
+
throw new Error("Element not found");
|
|
218
1338
|
}
|
|
219
|
-
|
|
220
|
-
// Then wait for it to be removed
|
|
221
1339
|
while (Date.now() - startTime < timeout) {
|
|
222
1340
|
element = callback();
|
|
223
1341
|
if (!element) return;
|
|
224
|
-
await new Promise(r => setTimeout(r, interval));
|
|
1342
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
225
1343
|
}
|
|
226
|
-
|
|
227
1344
|
throw new Error(`Element still present after ${timeout}ms`);
|
|
228
1345
|
}
|
|
229
|
-
|
|
230
|
-
// --- Act ---
|
|
231
|
-
// Ensure all effects and updates are flushed
|
|
232
|
-
|
|
233
|
-
export async function act(callback) {
|
|
1346
|
+
async function act(callback) {
|
|
234
1347
|
const result = await callback();
|
|
235
|
-
|
|
236
|
-
await new Promise(r => queueMicrotask(r));
|
|
237
|
-
|
|
238
|
-
await new Promise(r => setTimeout(r, 0));
|
|
1348
|
+
flushSync();
|
|
1349
|
+
await new Promise((r) => queueMicrotask(r));
|
|
1350
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
239
1351
|
return result;
|
|
240
1352
|
}
|
|
241
|
-
|
|
242
|
-
// --- Signal Testing Helpers ---
|
|
243
|
-
|
|
244
|
-
export function createTestSignal(initial) {
|
|
1353
|
+
function createTestSignal(initial) {
|
|
245
1354
|
const s = signal(initial);
|
|
246
1355
|
const history = [initial];
|
|
247
|
-
|
|
248
|
-
// Track all changes
|
|
249
1356
|
effect(() => {
|
|
250
1357
|
history.push(s());
|
|
251
1358
|
});
|
|
252
|
-
|
|
253
1359
|
return {
|
|
254
1360
|
signal: s,
|
|
255
|
-
get value() {
|
|
256
|
-
|
|
1361
|
+
get value() {
|
|
1362
|
+
return s();
|
|
1363
|
+
},
|
|
1364
|
+
set value(v) {
|
|
1365
|
+
s.set(v);
|
|
1366
|
+
},
|
|
257
1367
|
history,
|
|
258
1368
|
reset() {
|
|
259
1369
|
history.length = 0;
|
|
260
1370
|
history.push(s());
|
|
261
|
-
}
|
|
1371
|
+
}
|
|
262
1372
|
};
|
|
263
1373
|
}
|
|
264
|
-
|
|
265
|
-
// --- Mocking ---
|
|
266
|
-
|
|
267
|
-
export function mockComponent(name = 'MockComponent') {
|
|
1374
|
+
function mockComponent(name = "MockComponent") {
|
|
268
1375
|
const calls = [];
|
|
269
|
-
|
|
270
1376
|
function Mock(props) {
|
|
271
1377
|
calls.push({ props, timestamp: Date.now() });
|
|
272
|
-
return h(
|
|
1378
|
+
return h(
|
|
1379
|
+
"div",
|
|
1380
|
+
{ "data-testid": `mock-${name}` },
|
|
273
1381
|
JSON.stringify(props, null, 2)
|
|
274
1382
|
);
|
|
275
1383
|
}
|
|
276
|
-
|
|
277
1384
|
Mock.displayName = name;
|
|
278
1385
|
Mock.calls = calls;
|
|
279
1386
|
Mock.lastCall = () => calls[calls.length - 1];
|
|
280
|
-
Mock.reset = () => {
|
|
281
|
-
|
|
1387
|
+
Mock.reset = () => {
|
|
1388
|
+
calls.length = 0;
|
|
1389
|
+
};
|
|
282
1390
|
return Mock;
|
|
283
1391
|
}
|
|
284
|
-
|
|
285
|
-
// --- Assertions ---
|
|
286
|
-
|
|
287
|
-
export const expect = {
|
|
1392
|
+
var expect = {
|
|
288
1393
|
toBeInTheDocument(element) {
|
|
289
1394
|
if (!element || !element.parentNode) {
|
|
290
|
-
throw new Error(
|
|
1395
|
+
throw new Error("Expected element to be in the document");
|
|
291
1396
|
}
|
|
292
1397
|
},
|
|
293
|
-
|
|
294
1398
|
toHaveTextContent(element, text) {
|
|
295
1399
|
if (!element) {
|
|
296
|
-
throw new Error(
|
|
1400
|
+
throw new Error("Element not found");
|
|
297
1401
|
}
|
|
298
1402
|
const content = element.textContent;
|
|
299
1403
|
const matches = text instanceof RegExp ? text.test(content) : content.includes(text);
|
|
@@ -301,67 +1405,76 @@ export const expect = {
|
|
|
301
1405
|
throw new Error(`Expected "${content}" to contain "${text}"`);
|
|
302
1406
|
}
|
|
303
1407
|
},
|
|
304
|
-
|
|
305
1408
|
toHaveAttribute(element, attr, value) {
|
|
306
1409
|
if (!element) {
|
|
307
|
-
throw new Error(
|
|
1410
|
+
throw new Error("Element not found");
|
|
308
1411
|
}
|
|
309
1412
|
const attrValue = element.getAttribute(attr);
|
|
310
|
-
if (value !==
|
|
1413
|
+
if (value !== void 0 && attrValue !== value) {
|
|
311
1414
|
throw new Error(`Expected attribute "${attr}" to be "${value}", got "${attrValue}"`);
|
|
312
1415
|
}
|
|
313
|
-
if (value ===
|
|
1416
|
+
if (value === void 0 && attrValue === null) {
|
|
314
1417
|
throw new Error(`Expected element to have attribute "${attr}"`);
|
|
315
1418
|
}
|
|
316
1419
|
},
|
|
317
|
-
|
|
318
1420
|
toHaveClass(element, className) {
|
|
319
1421
|
if (!element) {
|
|
320
|
-
throw new Error(
|
|
1422
|
+
throw new Error("Element not found");
|
|
321
1423
|
}
|
|
322
1424
|
if (!element.classList.contains(className)) {
|
|
323
1425
|
throw new Error(`Expected element to have class "${className}"`);
|
|
324
1426
|
}
|
|
325
1427
|
},
|
|
326
|
-
|
|
327
1428
|
toBeVisible(element) {
|
|
328
1429
|
if (!element) {
|
|
329
|
-
throw new Error(
|
|
1430
|
+
throw new Error("Element not found");
|
|
330
1431
|
}
|
|
331
1432
|
const style = window.getComputedStyle(element);
|
|
332
|
-
if (style.display ===
|
|
333
|
-
throw new Error(
|
|
1433
|
+
if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") {
|
|
1434
|
+
throw new Error("Expected element to be visible");
|
|
334
1435
|
}
|
|
335
1436
|
},
|
|
336
|
-
|
|
337
1437
|
toBeDisabled(element) {
|
|
338
1438
|
if (!element) {
|
|
339
|
-
throw new Error(
|
|
1439
|
+
throw new Error("Element not found");
|
|
340
1440
|
}
|
|
341
1441
|
if (!element.disabled) {
|
|
342
|
-
throw new Error(
|
|
1442
|
+
throw new Error("Expected element to be disabled");
|
|
343
1443
|
}
|
|
344
1444
|
},
|
|
345
|
-
|
|
346
1445
|
toHaveValue(element, value) {
|
|
347
1446
|
if (!element) {
|
|
348
|
-
throw new Error(
|
|
1447
|
+
throw new Error("Element not found");
|
|
349
1448
|
}
|
|
350
1449
|
if (element.value !== value) {
|
|
351
1450
|
throw new Error(`Expected value to be "${value}", got "${element.value}"`);
|
|
352
1451
|
}
|
|
353
|
-
}
|
|
1452
|
+
}
|
|
354
1453
|
};
|
|
355
|
-
|
|
356
|
-
// --- Screen ---
|
|
357
|
-
// Global query object for convenience
|
|
358
|
-
|
|
359
|
-
export const screen = {
|
|
1454
|
+
var screen = {
|
|
360
1455
|
getByText: (text) => queryByText(document.body, text),
|
|
361
1456
|
getByTestId: (id) => document.querySelector(`[data-testid="${id}"]`),
|
|
362
1457
|
getByRole: (role) => document.querySelector(`[role="${role}"]`),
|
|
363
1458
|
getAllByText: (text) => queryAllByText(document.body, text),
|
|
364
1459
|
queryByText: (text) => queryByText(document.body, text),
|
|
365
1460
|
queryByTestId: (id) => document.querySelector(`[data-testid="${id}"]`),
|
|
366
|
-
debug: () => console.log(document.body.innerHTML)
|
|
1461
|
+
debug: () => console.log(document.body.innerHTML)
|
|
1462
|
+
};
|
|
1463
|
+
export {
|
|
1464
|
+
act,
|
|
1465
|
+
cleanup2 as cleanup,
|
|
1466
|
+
createTestSignal,
|
|
1467
|
+
expect,
|
|
1468
|
+
fireEvent,
|
|
1469
|
+
flushEffects,
|
|
1470
|
+
mockComponent,
|
|
1471
|
+
mockSignal,
|
|
1472
|
+
render,
|
|
1473
|
+
renderTest,
|
|
1474
|
+
screen,
|
|
1475
|
+
setupDOM,
|
|
1476
|
+
trackSignals,
|
|
1477
|
+
waitFor,
|
|
1478
|
+
waitForElementToBeRemoved
|
|
367
1479
|
};
|
|
1480
|
+
//# sourceMappingURL=testing.js.map
|