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