what-core 0.5.6 → 0.6.1

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