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/dist/testing.js CHANGED
@@ -1,46 +1,1028 @@
1
- // What Framework - Testing Utilities
2
- // Helpers for testing components, similar to @testing-library/react
3
- // Works with Node.js test runner or any test framework
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
- import { mount, h, signal, batch, effect } from './index.js';
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
- // Minimal DOM implementation for Node.js
8
- let container = null;
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
- // --- Setup and Cleanup ---
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
- export function setupDOM() {
13
- if (typeof document !== 'undefined') {
14
- // Browser environment
15
- container = document.createElement('div');
16
- container.id = 'test-root';
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('No DOM container available. Are you running in Node.js without jsdom?');
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
- // --- Query Helpers ---
63
-
64
- function queryByText(container, text) {
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
- container,
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
- container,
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('click', {
1196
+ const event = new MouseEvent("click", {
112
1197
  bubbles: true,
113
1198
  cancelable: true,
114
- view: typeof window !== 'undefined' ? window : undefined,
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('input', { bubbles: true });
1206
+ const event = new Event("input", { bubbles: true });
123
1207
  element.dispatchEvent(event);
124
- const changeEvent = new Event('change', { bubbles: true });
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('input', { bubbles: true });
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('submit', { bubbles: true, cancelable: true });
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('focus', { bubbles: true });
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('blur', { bubbles: true });
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('keydown', {
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('keyup', {
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('mouseenter', { bubbles: true });
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('mouseleave', { bubbles: true });
1261
+ const event = new MouseEvent("mouseleave", { bubbles: true });
186
1262
  element.dispatchEvent(event);
187
1263
  return event;
188
- },
1264
+ }
189
1265
  };
190
-
191
- // --- Wait Utilities ---
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
- export async function waitForElementToBeRemoved(callback, options = {}) {
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('Element not found');
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
- // Wait for microtasks to flush
236
- await new Promise(r => queueMicrotask(r));
237
- // Wait for any scheduled effects
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() { return s(); },
256
- set value(v) { s.set(v); },
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('div', { 'data-testid': `mock-${name}` },
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 = () => { calls.length = 0; };
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('Expected element to be in the document');
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('Element not found');
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('Element not found');
1357
+ throw new Error("Element not found");
308
1358
  }
309
1359
  const attrValue = element.getAttribute(attr);
310
- if (value !== undefined && attrValue !== 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 === undefined && attrValue === null) {
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('Element not found');
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('Element not found');
1377
+ throw new Error("Element not found");
330
1378
  }
331
1379
  const style = window.getComputedStyle(element);
332
- if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
333
- throw new Error('Expected element to be visible');
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('Element not found');
1386
+ throw new Error("Element not found");
340
1387
  }
341
1388
  if (!element.disabled) {
342
- throw new Error('Expected element to be disabled');
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('Element not found');
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