sibujs 1.2.0 → 1.3.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.
Files changed (89) hide show
  1. package/README.md +29 -25
  2. package/dist/browser.cjs +804 -2
  3. package/dist/browser.d.cts +591 -1
  4. package/dist/browser.d.ts +591 -1
  5. package/dist/browser.js +50 -8
  6. package/dist/build.cjs +654 -144
  7. package/dist/build.js +14 -12
  8. package/dist/cdn.global.js +188 -7
  9. package/dist/chunk-2BYQDGN3.js +742 -0
  10. package/dist/chunk-32DY64NT.js +282 -0
  11. package/dist/chunk-3AIRKM3B.js +1263 -0
  12. package/dist/chunk-3X2YG6YM.js +505 -0
  13. package/dist/chunk-5X6PP2UK.js +28 -0
  14. package/dist/chunk-77L6NL3X.js +1097 -0
  15. package/dist/chunk-BGN5ZMP4.js +26 -0
  16. package/dist/chunk-BTU3TJDS.js +365 -0
  17. package/dist/chunk-CHF5OHIA.js +61 -0
  18. package/dist/chunk-CMBFNA7L.js +27 -0
  19. package/dist/chunk-DAHRH4ON.js +331 -0
  20. package/dist/chunk-EBGIRKQY.js +616 -0
  21. package/dist/chunk-EUZND3CB.js +27 -0
  22. package/dist/chunk-F3FA4F32.js +292 -0
  23. package/dist/chunk-JAKHTMQU.js +1000 -0
  24. package/dist/chunk-JCI5M6U6.js +956 -0
  25. package/dist/chunk-KQPDEVVS.js +398 -0
  26. package/dist/chunk-NEKUBFPT.js +60 -0
  27. package/dist/chunk-NYVAC6P5.js +37 -0
  28. package/dist/chunk-PTQJDMRT.js +146 -0
  29. package/dist/chunk-QWZG56ET.js +2744 -0
  30. package/dist/chunk-TSOKIX5Z.js +654 -0
  31. package/dist/chunk-VRW3FULF.js +725 -0
  32. package/dist/chunk-WZSPOOER.js +84 -0
  33. package/dist/chunk-YT6HQ6AM.js +14 -0
  34. package/dist/chunk-ZD6OAMTH.js +277 -0
  35. package/dist/contracts-DDrwxvJ-.d.cts +245 -0
  36. package/dist/contracts-DDrwxvJ-.d.ts +245 -0
  37. package/dist/data.cjs +35 -2
  38. package/dist/data.d.cts +7 -0
  39. package/dist/data.d.ts +7 -0
  40. package/dist/data.js +9 -8
  41. package/dist/devtools.cjs +122 -0
  42. package/dist/devtools.d.cts +69 -461
  43. package/dist/devtools.d.ts +69 -461
  44. package/dist/devtools.js +127 -6
  45. package/dist/ecosystem.cjs +23 -6
  46. package/dist/ecosystem.d.cts +1 -1
  47. package/dist/ecosystem.d.ts +1 -1
  48. package/dist/ecosystem.js +10 -9
  49. package/dist/extras.cjs +1207 -65
  50. package/dist/extras.d.cts +5 -5
  51. package/dist/extras.d.ts +5 -5
  52. package/dist/extras.js +69 -24
  53. package/dist/index.cjs +663 -144
  54. package/dist/index.d.cts +397 -17
  55. package/dist/index.d.ts +397 -17
  56. package/dist/index.js +39 -17
  57. package/dist/introspect-BumjnBKr.d.cts +477 -0
  58. package/dist/introspect-CZrlcaYy.d.ts +477 -0
  59. package/dist/introspect-Cb0zgpi2.d.cts +477 -0
  60. package/dist/introspect-Y2xNXGSf.d.ts +477 -0
  61. package/dist/motion.js +4 -4
  62. package/dist/patterns.cjs +51 -2
  63. package/dist/patterns.d.cts +18 -8
  64. package/dist/patterns.d.ts +18 -8
  65. package/dist/patterns.js +7 -7
  66. package/dist/performance.js +4 -4
  67. package/dist/plugins.cjs +428 -81
  68. package/dist/plugins.d.cts +27 -4
  69. package/dist/plugins.d.ts +27 -4
  70. package/dist/plugins.js +156 -37
  71. package/dist/ssr-4PBXAOO3.js +40 -0
  72. package/dist/ssr-Do_SiVoL.d.cts +201 -0
  73. package/dist/ssr-Do_SiVoL.d.ts +201 -0
  74. package/dist/ssr.cjs +312 -60
  75. package/dist/ssr.d.cts +10 -1
  76. package/dist/ssr.d.ts +10 -1
  77. package/dist/ssr.js +13 -10
  78. package/dist/tagFactory-DaJ0YWX6.d.cts +47 -0
  79. package/dist/tagFactory-DaJ0YWX6.d.ts +47 -0
  80. package/dist/testing.cjs +233 -2
  81. package/dist/testing.d.cts +42 -1
  82. package/dist/testing.d.ts +42 -1
  83. package/dist/testing.js +129 -2
  84. package/dist/ui.cjs +374 -3
  85. package/dist/ui.d.cts +252 -2
  86. package/dist/ui.d.ts +252 -2
  87. package/dist/ui.js +328 -8
  88. package/dist/widgets.js +7 -7
  89. package/package.json +1 -1
@@ -0,0 +1,47 @@
1
+ type NodeChild = Node | Element | Text | Comment | string | number | boolean | (() => NodeChild) | null | undefined;
2
+ type NodeChildren = NodeChild | NodeChild[] | NodeChild[][] | (() => NodeChild | NodeChild[]);
3
+
4
+ declare const SVG_NS = "http://www.w3.org/2000/svg";
5
+ interface TagProps {
6
+ id?: string;
7
+ class?: string | (() => string) | Record<string, boolean | (() => boolean)>;
8
+ style?: Record<string, string | number | (() => string | number)> | string | (() => string);
9
+ ref?: {
10
+ current: Element | null;
11
+ };
12
+ nodes?: NodeChildren;
13
+ on?: Record<string, (ev: Event) => void>;
14
+ /** Called with the element after creation — useful for imperative bindings */
15
+ onElement?: (el: HTMLElement) => void;
16
+ [attr: string]: unknown;
17
+ }
18
+ /**
19
+ * Factory for creating HTML or SVG elements with reactive props and nodes.
20
+ *
21
+ * Calling conventions:
22
+ *
23
+ * tag() empty element
24
+ * tag("text") element with text content
25
+ * tag(42) element with numeric text content
26
+ * tag([childA, childB]) element with children (array)
27
+ * tag(node) element wrapping a single existing node
28
+ * tag(getter) element with a reactive child
29
+ * tag("className", children) positional: class + children
30
+ * tag({ ...props }) full props object (children via props.nodes)
31
+ * tag({ ...props }, children) props + children (no need for `nodes:` key!)
32
+ *
33
+ * The last form is the "deeply-nested shorthand" the codebase favours:
34
+ *
35
+ * div({ class: "card" }, [
36
+ * h1({ class: "title" }, "Hello"),
37
+ * p({ class: "body" }, "World"),
38
+ * div({ class: "row" }, [
39
+ * span({ id: "x" }, "child"),
40
+ * ]),
41
+ * ])
42
+ *
43
+ * `children` overrides `props.nodes` when both are present.
44
+ */
45
+ declare const tagFactory: (tag: string, ns?: string) => (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
46
+
47
+ export { type NodeChildren as N, SVG_NS as S, type TagProps as T, type NodeChild as a, tagFactory as t };
@@ -0,0 +1,47 @@
1
+ type NodeChild = Node | Element | Text | Comment | string | number | boolean | (() => NodeChild) | null | undefined;
2
+ type NodeChildren = NodeChild | NodeChild[] | NodeChild[][] | (() => NodeChild | NodeChild[]);
3
+
4
+ declare const SVG_NS = "http://www.w3.org/2000/svg";
5
+ interface TagProps {
6
+ id?: string;
7
+ class?: string | (() => string) | Record<string, boolean | (() => boolean)>;
8
+ style?: Record<string, string | number | (() => string | number)> | string | (() => string);
9
+ ref?: {
10
+ current: Element | null;
11
+ };
12
+ nodes?: NodeChildren;
13
+ on?: Record<string, (ev: Event) => void>;
14
+ /** Called with the element after creation — useful for imperative bindings */
15
+ onElement?: (el: HTMLElement) => void;
16
+ [attr: string]: unknown;
17
+ }
18
+ /**
19
+ * Factory for creating HTML or SVG elements with reactive props and nodes.
20
+ *
21
+ * Calling conventions:
22
+ *
23
+ * tag() empty element
24
+ * tag("text") element with text content
25
+ * tag(42) element with numeric text content
26
+ * tag([childA, childB]) element with children (array)
27
+ * tag(node) element wrapping a single existing node
28
+ * tag(getter) element with a reactive child
29
+ * tag("className", children) positional: class + children
30
+ * tag({ ...props }) full props object (children via props.nodes)
31
+ * tag({ ...props }, children) props + children (no need for `nodes:` key!)
32
+ *
33
+ * The last form is the "deeply-nested shorthand" the codebase favours:
34
+ *
35
+ * div({ class: "card" }, [
36
+ * h1({ class: "title" }, "Hello"),
37
+ * p({ class: "body" }, "World"),
38
+ * div({ class: "row" }, [
39
+ * span({ id: "x" }, "child"),
40
+ * ]),
41
+ * ])
42
+ *
43
+ * `children` overrides `props.nodes` when both are present.
44
+ */
45
+ declare const tagFactory: (tag: string, ns?: string) => (first?: TagProps | NodeChildren, second?: NodeChildren) => Element;
46
+
47
+ export { type NodeChildren as N, SVG_NS as S, type TagProps as T, type NodeChild as a, tagFactory as t };
package/dist/testing.cjs CHANGED
@@ -45,14 +45,23 @@ __export(testing_exports, {
45
45
  createTimerMock: () => createTimerMock,
46
46
  createUniversalAdapter: () => createUniversalAdapter,
47
47
  createVisualSuite: () => createVisualSuite,
48
+ findByRole: () => findByRole,
49
+ findByTestId: () => findByTestId,
50
+ findByText: () => findByText,
48
51
  fireEvent: () => fireEvent,
49
52
  matchSnapshot: () => matchSnapshot,
50
53
  mockRouter: () => mockRouter,
51
54
  mockStore: () => mockStore,
55
+ queryByLabel: () => queryByLabel,
56
+ queryByRole: () => queryByRole,
57
+ queryByTestId: () => queryByTestId,
58
+ queryByText: () => queryByText,
52
59
  render: () => render,
53
60
  snapshotComponent: () => snapshotComponent,
54
61
  testComponent: () => testComponent,
55
- waitFor: () => waitFor
62
+ type: () => type,
63
+ waitFor: () => waitFor,
64
+ waitForSignal: () => waitForSignal
56
65
  });
57
66
  module.exports = __toCommonJS(testing_exports);
58
67
 
@@ -1445,6 +1454,219 @@ function testComponent(component, options = {}) {
1445
1454
  };
1446
1455
  }
1447
1456
 
1457
+ // src/core/dev.ts
1458
+ function isDev() {
1459
+ return typeof globalThis.__SIBU_DEV__ !== "undefined" ? !!globalThis.__SIBU_DEV__ : typeof __SIBU_DEV__ !== "undefined" ? __SIBU_DEV__ : typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
1460
+ }
1461
+ var _isDev = isDev();
1462
+ function devAssert(condition, message) {
1463
+ if (_isDev && !condition) {
1464
+ throw new Error(`[Sibu] ${message}`);
1465
+ }
1466
+ }
1467
+
1468
+ // src/reactivity/track.ts
1469
+ var _isDev2 = isDev();
1470
+ var subscriberStack = new Array(32);
1471
+ var stackCapacity = 32;
1472
+ var stackTop = -1;
1473
+ var currentSubscriber = null;
1474
+ var SUBS = "__s";
1475
+ function track(effectFn, subscriber) {
1476
+ if (!subscriber) subscriber = effectFn;
1477
+ cleanup(subscriber);
1478
+ ++stackTop;
1479
+ if (stackTop >= stackCapacity) {
1480
+ stackCapacity *= 2;
1481
+ subscriberStack.length = stackCapacity;
1482
+ }
1483
+ subscriberStack[stackTop] = subscriber;
1484
+ currentSubscriber = subscriber;
1485
+ try {
1486
+ effectFn();
1487
+ } finally {
1488
+ stackTop--;
1489
+ currentSubscriber = stackTop >= 0 ? subscriberStack[stackTop] : null;
1490
+ }
1491
+ return () => cleanup(subscriber);
1492
+ }
1493
+ function cleanup(subscriber) {
1494
+ const sub = subscriber;
1495
+ const singleDep = sub._dep;
1496
+ if (singleDep !== void 0) {
1497
+ const subs = singleDep[SUBS];
1498
+ if (subs) {
1499
+ subs.delete(subscriber);
1500
+ if (singleDep.__f === subscriber) {
1501
+ singleDep.__f = void 0;
1502
+ }
1503
+ }
1504
+ sub._dep = void 0;
1505
+ return;
1506
+ }
1507
+ const deps = sub._deps;
1508
+ if (!deps || deps.size === 0) return;
1509
+ for (const signal of deps) {
1510
+ const subs = signal[SUBS];
1511
+ if (subs) {
1512
+ subs.delete(subscriber);
1513
+ if (signal.__f === subscriber) {
1514
+ signal.__f = void 0;
1515
+ }
1516
+ }
1517
+ }
1518
+ deps.clear();
1519
+ }
1520
+
1521
+ // src/core/ssr-context.ts
1522
+ var ssrMode = false;
1523
+ function isSSR() {
1524
+ return ssrMode;
1525
+ }
1526
+
1527
+ // src/core/signals/effect.ts
1528
+ var _g = globalThis;
1529
+ function effect(effectFn, options) {
1530
+ devAssert(typeof effectFn === "function", "effect: argument must be a function.");
1531
+ if (isSSR()) return () => {
1532
+ };
1533
+ const onError = options?.onError;
1534
+ const wrappedFn = onError ? () => {
1535
+ try {
1536
+ effectFn();
1537
+ } catch (err) {
1538
+ onError(err);
1539
+ }
1540
+ } : effectFn;
1541
+ let cleanupHandle = () => {
1542
+ };
1543
+ const subscriber = () => {
1544
+ cleanupHandle();
1545
+ cleanupHandle = track(wrappedFn, subscriber);
1546
+ };
1547
+ cleanupHandle = track(wrappedFn, subscriber);
1548
+ const hook = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1549
+ if (hook) hook.emit("effect:create", { effectFn });
1550
+ return () => {
1551
+ const h = _g.__SIBU_DEVTOOLS_GLOBAL_HOOK__;
1552
+ if (h) h.emit("effect:destroy", { effectFn });
1553
+ cleanupHandle();
1554
+ };
1555
+ }
1556
+
1557
+ // src/testing/queries.ts
1558
+ function queryByText(container, text) {
1559
+ const walk = (node) => {
1560
+ if (node.childNodes.length === 1 && node.childNodes[0].nodeType === 3) {
1561
+ if (node.textContent?.includes(text)) return node;
1562
+ }
1563
+ for (const child of Array.from(node.children)) {
1564
+ const found = walk(child);
1565
+ if (found) return found;
1566
+ }
1567
+ return null;
1568
+ };
1569
+ return walk(container);
1570
+ }
1571
+ function queryByTestId(container, testId) {
1572
+ return container.querySelector(`[data-testid="${testId}"]`);
1573
+ }
1574
+ function queryByRole(container, role) {
1575
+ return container.querySelector(`[role="${role}"]`);
1576
+ }
1577
+ function cssEscape(value) {
1578
+ const g = globalThis;
1579
+ if (g.CSS && typeof g.CSS.escape === "function") return g.CSS.escape(value);
1580
+ return value.replace(/[^\w-]/g, (m) => `\\${m.charCodeAt(0).toString(16)} `);
1581
+ }
1582
+ function queryByLabel(container, labelText) {
1583
+ const labels = Array.from(container.querySelectorAll("label"));
1584
+ for (const label of labels) {
1585
+ if (label.textContent?.trim() === labelText) {
1586
+ const forId = label.getAttribute("for");
1587
+ if (forId) {
1588
+ const target = container.querySelector(`#${cssEscape(forId)}`);
1589
+ if (target) return target;
1590
+ }
1591
+ const child = label.querySelector("input, select, textarea, button");
1592
+ if (child) return child;
1593
+ }
1594
+ }
1595
+ return container.querySelector(`[aria-label="${labelText}"]`);
1596
+ }
1597
+ async function pollUntil(fn, timeout, interval, errorIfTimeout) {
1598
+ const start = Date.now();
1599
+ return new Promise((resolve, reject) => {
1600
+ const check = () => {
1601
+ const result = fn();
1602
+ if (result !== null) {
1603
+ resolve(result);
1604
+ return;
1605
+ }
1606
+ if (Date.now() - start >= timeout) {
1607
+ reject(new Error(errorIfTimeout));
1608
+ return;
1609
+ }
1610
+ setTimeout(check, interval);
1611
+ };
1612
+ check();
1613
+ });
1614
+ }
1615
+ function findByText(container, text, options = {}) {
1616
+ return pollUntil(
1617
+ () => queryByText(container, text),
1618
+ options.timeout ?? 1e3,
1619
+ options.interval ?? 50,
1620
+ `findByText: no element with text "${text}" after ${options.timeout ?? 1e3}ms`
1621
+ );
1622
+ }
1623
+ function findByTestId(container, testId, options = {}) {
1624
+ return pollUntil(
1625
+ () => queryByTestId(container, testId),
1626
+ options.timeout ?? 1e3,
1627
+ options.interval ?? 50,
1628
+ `findByTestId: no element with data-testid="${testId}" after ${options.timeout ?? 1e3}ms`
1629
+ );
1630
+ }
1631
+ function findByRole(container, role, options = {}) {
1632
+ return pollUntil(
1633
+ () => queryByRole(container, role),
1634
+ options.timeout ?? 1e3,
1635
+ options.interval ?? 50,
1636
+ `findByRole: no element with role="${role}" after ${options.timeout ?? 1e3}ms`
1637
+ );
1638
+ }
1639
+ function waitForSignal(getter, predicate, options = {}) {
1640
+ const timeoutMs = options.timeout ?? 1e3;
1641
+ return new Promise((resolve, reject) => {
1642
+ let resolved = false;
1643
+ const timer = setTimeout(() => {
1644
+ if (!resolved) {
1645
+ resolved = true;
1646
+ teardown();
1647
+ reject(new Error(`waitForSignal: predicate did not match within ${timeoutMs}ms`));
1648
+ }
1649
+ }, timeoutMs);
1650
+ const teardown = effect(() => {
1651
+ if (resolved) return;
1652
+ const value = getter();
1653
+ if (predicate(value)) {
1654
+ resolved = true;
1655
+ clearTimeout(timer);
1656
+ queueMicrotask(() => teardown());
1657
+ resolve(value);
1658
+ }
1659
+ });
1660
+ });
1661
+ }
1662
+ function type(element, text) {
1663
+ for (const char of text) {
1664
+ element.value += char;
1665
+ element.dispatchEvent(new InputEvent("input", { bubbles: true, data: char }));
1666
+ }
1667
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1668
+ }
1669
+
1448
1670
  // src/testing/snapshot.ts
1449
1671
  function serializeElement2(el, indent) {
1450
1672
  const pad = " ".repeat(indent);
@@ -1908,12 +2130,21 @@ function mockStore(initialState) {
1908
2130
  createTimerMock,
1909
2131
  createUniversalAdapter,
1910
2132
  createVisualSuite,
2133
+ findByRole,
2134
+ findByTestId,
2135
+ findByText,
1911
2136
  fireEvent,
1912
2137
  matchSnapshot,
1913
2138
  mockRouter,
1914
2139
  mockStore,
2140
+ queryByLabel,
2141
+ queryByRole,
2142
+ queryByTestId,
2143
+ queryByText,
1915
2144
  render,
1916
2145
  snapshotComponent,
1917
2146
  testComponent,
1918
- waitFor
2147
+ type,
2148
+ waitFor,
2149
+ waitForSignal
1919
2150
  });
@@ -315,6 +315,47 @@ declare function testComponent(component: (() => HTMLElement) | HTMLElement, opt
315
315
  destroy: () => void;
316
316
  };
317
317
 
318
+ /**
319
+ * Find an element by its exact or substring text content. Returns
320
+ * `null` if no match is found — unlike `getByText`, does not throw.
321
+ */
322
+ declare function queryByText(container: HTMLElement, text: string): HTMLElement | null;
323
+ declare function queryByTestId(container: HTMLElement, testId: string): HTMLElement | null;
324
+ declare function queryByRole(container: HTMLElement, role: string): HTMLElement | null;
325
+ declare function queryByLabel(container: HTMLElement, labelText: string): HTMLElement | null;
326
+ interface FindOptions {
327
+ timeout?: number;
328
+ interval?: number;
329
+ }
330
+ /**
331
+ * Resolve with the first element whose text matches, polling until
332
+ * `timeout` ms elapse. Useful for async content (data fetching,
333
+ * transitions, etc.) that appears after the initial render.
334
+ */
335
+ declare function findByText(container: HTMLElement, text: string, options?: FindOptions): Promise<HTMLElement>;
336
+ declare function findByTestId(container: HTMLElement, testId: string, options?: FindOptions): Promise<HTMLElement>;
337
+ declare function findByRole(container: HTMLElement, role: string, options?: FindOptions): Promise<HTMLElement>;
338
+ /**
339
+ * Wait until a reactive getter satisfies a predicate. Unlike `waitFor`,
340
+ * this subscribes to the getter so it reacts immediately on signal
341
+ * updates rather than polling. Falls back to a `timeout` rejection.
342
+ *
343
+ * @example
344
+ * ```ts
345
+ * await waitForSignal(() => loading(), (v) => v === false);
346
+ * ```
347
+ */
348
+ declare function waitForSignal<T>(getter: () => T, predicate: (value: T) => boolean, options?: {
349
+ timeout?: number;
350
+ }): Promise<T>;
351
+ /**
352
+ * Type a full string into an input, dispatching an input event after
353
+ * each character. This is closer to real user input than a single
354
+ * `fireEvent.input(el, value)` call and catches handlers that only
355
+ * run on specific event shapes.
356
+ */
357
+ declare function type(element: HTMLInputElement | HTMLTextAreaElement, text: string): void;
358
+
318
359
  /**
319
360
  * Snapshot testing utilities for SibuJS components.
320
361
  * Capture and compare component output over time.
@@ -488,4 +529,4 @@ declare function mockStore<T extends Record<string, unknown>>(initialState: T):
488
529
  reset: () => void;
489
530
  };
490
531
 
491
- export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, fireEvent, matchSnapshot, mockRouter, mockStore, render, snapshotComponent, testComponent, waitFor };
532
+ export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, findByRole, findByTestId, findByText, fireEvent, matchSnapshot, mockRouter, mockStore, queryByLabel, queryByRole, queryByTestId, queryByText, render, snapshotComponent, testComponent, type, waitFor, waitForSignal };
package/dist/testing.d.ts CHANGED
@@ -315,6 +315,47 @@ declare function testComponent(component: (() => HTMLElement) | HTMLElement, opt
315
315
  destroy: () => void;
316
316
  };
317
317
 
318
+ /**
319
+ * Find an element by its exact or substring text content. Returns
320
+ * `null` if no match is found — unlike `getByText`, does not throw.
321
+ */
322
+ declare function queryByText(container: HTMLElement, text: string): HTMLElement | null;
323
+ declare function queryByTestId(container: HTMLElement, testId: string): HTMLElement | null;
324
+ declare function queryByRole(container: HTMLElement, role: string): HTMLElement | null;
325
+ declare function queryByLabel(container: HTMLElement, labelText: string): HTMLElement | null;
326
+ interface FindOptions {
327
+ timeout?: number;
328
+ interval?: number;
329
+ }
330
+ /**
331
+ * Resolve with the first element whose text matches, polling until
332
+ * `timeout` ms elapse. Useful for async content (data fetching,
333
+ * transitions, etc.) that appears after the initial render.
334
+ */
335
+ declare function findByText(container: HTMLElement, text: string, options?: FindOptions): Promise<HTMLElement>;
336
+ declare function findByTestId(container: HTMLElement, testId: string, options?: FindOptions): Promise<HTMLElement>;
337
+ declare function findByRole(container: HTMLElement, role: string, options?: FindOptions): Promise<HTMLElement>;
338
+ /**
339
+ * Wait until a reactive getter satisfies a predicate. Unlike `waitFor`,
340
+ * this subscribes to the getter so it reacts immediately on signal
341
+ * updates rather than polling. Falls back to a `timeout` rejection.
342
+ *
343
+ * @example
344
+ * ```ts
345
+ * await waitForSignal(() => loading(), (v) => v === false);
346
+ * ```
347
+ */
348
+ declare function waitForSignal<T>(getter: () => T, predicate: (value: T) => boolean, options?: {
349
+ timeout?: number;
350
+ }): Promise<T>;
351
+ /**
352
+ * Type a full string into an input, dispatching an input event after
353
+ * each character. This is closer to real user input than a single
354
+ * `fireEvent.input(el, value)` call and catches handlers that only
355
+ * run on specific event shapes.
356
+ */
357
+ declare function type(element: HTMLInputElement | HTMLTextAreaElement, text: string): void;
358
+
318
359
  /**
319
360
  * Snapshot testing utilities for SibuJS components.
320
361
  * Capture and compare component output over time.
@@ -488,4 +529,4 @@ declare function mockStore<T extends Record<string, unknown>>(initialState: T):
488
529
  reset: () => void;
489
530
  };
490
531
 
491
- export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, fireEvent, matchSnapshot, mockRouter, mockStore, render, snapshotComponent, testComponent, waitFor };
532
+ export { type A11yCheckResult, type A11yViolation, type A11yViolationLevel, type FindOptions, type FingerprintChange, type MockResponse, type MockRoute, type VisualFingerprint, assertA11y, assertDOMEquals, captureFingerprint, checkA11y, checkAriaAttributes, checkColorContrast, checkFormLabels, checkHeadingHierarchy, checkImageAlt, checkKeyboardAccess, checkLandmarks, checkLinksAndButtons, checkListSemantics, checkTabOrder, compareFingerprints, createCypressAdapter, createDOMSnapshot, createHttpMock, createJestAdapter, createPlaywrightAdapter, createSnapshotMatcher, createSnapshotStore, createTimerMock, createUniversalAdapter, createVisualSuite, findByRole, findByTestId, findByText, fireEvent, matchSnapshot, mockRouter, mockStore, queryByLabel, queryByRole, queryByTestId, queryByText, render, snapshotComponent, testComponent, type, waitFor, waitForSignal };
package/dist/testing.js CHANGED
@@ -1,4 +1,9 @@
1
- import "./chunk-MLKGABMK.js";
1
+ import {
2
+ effect
3
+ } from "./chunk-CHF5OHIA.js";
4
+ import "./chunk-EUZND3CB.js";
5
+ import "./chunk-ZD6OAMTH.js";
6
+ import "./chunk-5X6PP2UK.js";
2
7
 
3
8
  // src/testing/a11y.ts
4
9
  var VALID_ARIA_ROLES = /* @__PURE__ */ new Set([
@@ -1389,6 +1394,119 @@ function testComponent(component, options = {}) {
1389
1394
  };
1390
1395
  }
1391
1396
 
1397
+ // src/testing/queries.ts
1398
+ function queryByText(container, text) {
1399
+ const walk = (node) => {
1400
+ if (node.childNodes.length === 1 && node.childNodes[0].nodeType === 3) {
1401
+ if (node.textContent?.includes(text)) return node;
1402
+ }
1403
+ for (const child of Array.from(node.children)) {
1404
+ const found = walk(child);
1405
+ if (found) return found;
1406
+ }
1407
+ return null;
1408
+ };
1409
+ return walk(container);
1410
+ }
1411
+ function queryByTestId(container, testId) {
1412
+ return container.querySelector(`[data-testid="${testId}"]`);
1413
+ }
1414
+ function queryByRole(container, role) {
1415
+ return container.querySelector(`[role="${role}"]`);
1416
+ }
1417
+ function cssEscape(value) {
1418
+ const g = globalThis;
1419
+ if (g.CSS && typeof g.CSS.escape === "function") return g.CSS.escape(value);
1420
+ return value.replace(/[^\w-]/g, (m) => `\\${m.charCodeAt(0).toString(16)} `);
1421
+ }
1422
+ function queryByLabel(container, labelText) {
1423
+ const labels = Array.from(container.querySelectorAll("label"));
1424
+ for (const label of labels) {
1425
+ if (label.textContent?.trim() === labelText) {
1426
+ const forId = label.getAttribute("for");
1427
+ if (forId) {
1428
+ const target = container.querySelector(`#${cssEscape(forId)}`);
1429
+ if (target) return target;
1430
+ }
1431
+ const child = label.querySelector("input, select, textarea, button");
1432
+ if (child) return child;
1433
+ }
1434
+ }
1435
+ return container.querySelector(`[aria-label="${labelText}"]`);
1436
+ }
1437
+ async function pollUntil(fn, timeout, interval, errorIfTimeout) {
1438
+ const start = Date.now();
1439
+ return new Promise((resolve, reject) => {
1440
+ const check = () => {
1441
+ const result = fn();
1442
+ if (result !== null) {
1443
+ resolve(result);
1444
+ return;
1445
+ }
1446
+ if (Date.now() - start >= timeout) {
1447
+ reject(new Error(errorIfTimeout));
1448
+ return;
1449
+ }
1450
+ setTimeout(check, interval);
1451
+ };
1452
+ check();
1453
+ });
1454
+ }
1455
+ function findByText(container, text, options = {}) {
1456
+ return pollUntil(
1457
+ () => queryByText(container, text),
1458
+ options.timeout ?? 1e3,
1459
+ options.interval ?? 50,
1460
+ `findByText: no element with text "${text}" after ${options.timeout ?? 1e3}ms`
1461
+ );
1462
+ }
1463
+ function findByTestId(container, testId, options = {}) {
1464
+ return pollUntil(
1465
+ () => queryByTestId(container, testId),
1466
+ options.timeout ?? 1e3,
1467
+ options.interval ?? 50,
1468
+ `findByTestId: no element with data-testid="${testId}" after ${options.timeout ?? 1e3}ms`
1469
+ );
1470
+ }
1471
+ function findByRole(container, role, options = {}) {
1472
+ return pollUntil(
1473
+ () => queryByRole(container, role),
1474
+ options.timeout ?? 1e3,
1475
+ options.interval ?? 50,
1476
+ `findByRole: no element with role="${role}" after ${options.timeout ?? 1e3}ms`
1477
+ );
1478
+ }
1479
+ function waitForSignal(getter, predicate, options = {}) {
1480
+ const timeoutMs = options.timeout ?? 1e3;
1481
+ return new Promise((resolve, reject) => {
1482
+ let resolved = false;
1483
+ const timer = setTimeout(() => {
1484
+ if (!resolved) {
1485
+ resolved = true;
1486
+ teardown();
1487
+ reject(new Error(`waitForSignal: predicate did not match within ${timeoutMs}ms`));
1488
+ }
1489
+ }, timeoutMs);
1490
+ const teardown = effect(() => {
1491
+ if (resolved) return;
1492
+ const value = getter();
1493
+ if (predicate(value)) {
1494
+ resolved = true;
1495
+ clearTimeout(timer);
1496
+ queueMicrotask(() => teardown());
1497
+ resolve(value);
1498
+ }
1499
+ });
1500
+ });
1501
+ }
1502
+ function type(element, text) {
1503
+ for (const char of text) {
1504
+ element.value += char;
1505
+ element.dispatchEvent(new InputEvent("input", { bubbles: true, data: char }));
1506
+ }
1507
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1508
+ }
1509
+
1392
1510
  // src/testing/snapshot.ts
1393
1511
  function serializeElement2(el, indent) {
1394
1512
  const pad = " ".repeat(indent);
@@ -1851,12 +1969,21 @@ export {
1851
1969
  createTimerMock,
1852
1970
  createUniversalAdapter,
1853
1971
  createVisualSuite,
1972
+ findByRole,
1973
+ findByTestId,
1974
+ findByText,
1854
1975
  fireEvent,
1855
1976
  matchSnapshot,
1856
1977
  mockRouter,
1857
1978
  mockStore,
1979
+ queryByLabel,
1980
+ queryByRole,
1981
+ queryByTestId,
1982
+ queryByText,
1858
1983
  render,
1859
1984
  snapshotComponent,
1860
1985
  testComponent,
1861
- waitFor
1986
+ type,
1987
+ waitFor,
1988
+ waitForSignal
1862
1989
  };