@vielzeug/assay 2.2.0 → 3.0.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.
@@ -1 +1 @@
1
- {"version":3,"file":"query.cjs","names":[],"sources":["../src/query.ts"],"sourcesContent":["import { AssayQueryError } from './errors';\n\n/** DOM query helpers for test environments. */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any queryable DOM root — see {@link within}. */\nexport interface QueryScope {\n get<E extends Element = Element>(selector: string): E;\n getByTestId<E extends Element = Element>(testId: string): E;\n getByText<E extends Element = Element>(text: string, selector?: string): E;\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\ntype QueryRoot = ParentNode;\n\nconst describeRoot = (root: QueryRoot): string => {\n if (root instanceof Element) return root.outerHTML.slice(0, 500);\n\n if (root instanceof ShadowRoot) return `<shadow-root>${root.innerHTML.slice(0, 500)}</shadow-root>`;\n\n return root.nodeName;\n};\n\nconst requireMatch = <E extends Element>(element: E | null, description: string, root: QueryRoot): E => {\n if (element) return element;\n\n throw new AssayQueryError(`Unable to find ${description} within ${describeRoot(root)}.`);\n};\n\nconst query = <E extends Element = Element>(root: QueryRoot, selector: string): E | null =>\n root.querySelector<E>(selector);\n\nconst queryAll = <E extends Element = Element>(root: QueryRoot, selector: string): E[] => {\n return Array.from(root.querySelectorAll<E>(selector));\n};\n\nconst queryByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E | null =>\n queryAll<E>(root, '[data-testid]').find((element) => element.getAttribute('data-testid') === testId) ?? null;\n\nconst queryAllByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E[] =>\n queryAll<E>(root, '[data-testid]').filter((element) => element.getAttribute('data-testid') === testId);\n\nconst queryByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E | null => {\n for (const el of root.querySelectorAll<E>(selector)) {\n if (el.textContent?.trim() === text) return el;\n }\n\n return null;\n};\n\nconst queryAllByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E[] => {\n return Array.from(root.querySelectorAll<E>(selector)).filter((el) => el.textContent?.trim() === text);\n};\n\n/**\n * Queries the shadow root of a custom element for a matching CSS selector.\n * Returns `null` if the element has no shadow root or no match is found — safe to call on\n * both open-shadow custom elements and plain elements without checking `shadowRoot` first.\n */\nexport function queryInShadow<E extends Element = Element>(host: Element, selector: string): E | null {\n return host.shadowRoot ? query<E>(host.shadowRoot, selector) : null;\n}\n\n/** Queries all matching elements inside the shadow root of a custom element. */\nexport function queryAllInShadow<E extends Element = Element>(host: Element, selector: string): E[] {\n return host.shadowRoot ? queryAll<E>(host.shadowRoot, selector) : [];\n}\n\n/**\n * Queries a shadow DOM element by its CSS `part` attribute — shorthand for\n * `queryInShadow(host, '[part=\"name\"]')`.\n *\n * @example\n * const btn = queryPart(carousel, 'prev-btn');\n * expect(btn).not.toBeNull();\n */\nexport function queryPart<E extends Element = Element>(host: Element, part: string): E | null {\n return (\n queryAllInShadow<E>(host, '[part]').find((element) => element.getAttribute('part')?.split(/\\s+/).includes(part)) ??\n null\n );\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every child assigned to the\n * default slot when no name is given.\n *\n * @example\n * const slides = getSlotted(carousel);\n * expect(slides).toHaveLength(3);\n */\nexport function getSlotted<E extends Element = Element>(host: Element, slotName?: string): E[] {\n return Array.from(host.children).filter((element) =>\n slotName === undefined ? !element.getAttribute('slot') : element.getAttribute('slot') === slotName,\n ) as E[];\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to a DOM root — useful for light DOM and shadow roots.\n *\n * @example\n * const view = within(fixture.shadow!);\n * expect(view.get('.title').textContent).toBe('Hello');\n */\nexport function within(root: QueryRoot): QueryScope {\n return {\n get: <E extends Element = Element>(selector: string) =>\n requireMatch(query<E>(root, selector), `\"${selector}\"`, root),\n getByTestId: <E extends Element = Element>(testId: string) =>\n requireMatch(queryByTestId<E>(root, testId), `data-testid \"${testId}\"`, root),\n getByText: <E extends Element = Element>(text: string, selector = '*') =>\n requireMatch(queryByText<E>(root, text, selector), `text \"${text}\" matching \"${selector}\"`, root),\n query: <E extends Element = Element>(selector: string) => query<E>(root, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(root, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(root, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(root, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(root, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(root, text, selector),\n };\n}\n"],"mappings":"gCAuBA,IAAM,EAAgB,GAChB,aAAgB,QAAgB,EAAK,UAAU,MAAM,EAAG,GAAG,EAE3D,aAAgB,WAAmB,gBAAgB,EAAK,UAAU,MAAM,EAAG,GAAG,EAAE,gBAE7E,EAAK,SAGR,GAAmC,EAAmB,EAAqB,IAAuB,CACtG,GAAI,EAAS,OAAO,EAEpB,MAAM,IAAI,EAAA,gBAAgB,kBAAkB,EAAY,UAAU,EAAa,CAAI,EAAE,EAAE,CACzF,EAEM,GAAsC,EAAiB,IAC3D,EAAK,cAAiB,CAAQ,EAE1B,GAAyC,EAAiB,IACvD,MAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC,EAGhD,GAA8C,EAAiB,IACnE,EAAY,EAAM,eAAe,CAAC,CAAC,KAAM,GAAY,EAAQ,aAAa,aAAa,IAAM,CAAM,GAAK,KAEpG,GAAiD,EAAiB,IACtE,EAAY,EAAM,eAAe,CAAC,CAAC,OAAQ,GAAY,EAAQ,aAAa,aAAa,IAAM,CAAM,EAEjG,GAA4C,EAAiB,EAAc,EAAW,MAAkB,CAC5G,IAAK,IAAM,KAAM,EAAK,iBAAoB,CAAQ,EAChD,GAAI,EAAG,aAAa,KAAK,IAAM,EAAM,OAAO,EAG9C,OAAO,IACT,EAEM,GAA+C,EAAiB,EAAc,EAAW,MACtF,MAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC,CAAC,CAAC,OAAQ,GAAO,EAAG,aAAa,KAAK,IAAM,CAAI,EAQtG,SAAgB,EAA2C,EAAe,EAA4B,CACpG,OAAO,EAAK,WAAa,EAAS,EAAK,WAAY,CAAQ,EAAI,IACjE,CAGA,SAAgB,EAA8C,EAAe,EAAuB,CAClG,OAAO,EAAK,WAAa,EAAY,EAAK,WAAY,CAAQ,EAAI,CAAC,CACrE,CAUA,SAAgB,EAAuC,EAAe,EAAwB,CAC5F,OACE,EAAoB,EAAM,QAAQ,CAAC,CAAC,KAAM,GAAY,EAAQ,aAAa,MAAM,CAAC,EAAE,MAAM,KAAK,CAAC,CAAC,SAAS,CAAI,CAAC,GAC/G,IAEJ,CAUA,SAAgB,EAAwC,EAAe,EAAwB,CAC7F,OAAO,MAAM,KAAK,EAAK,QAAQ,CAAC,CAAC,OAAQ,GACvC,IAAa,IAAA,GAAY,CAAC,EAAQ,aAAa,MAAM,EAAI,EAAQ,aAAa,MAAM,IAAM,CAC5F,CACF,CAWA,SAAgB,EAAO,EAA6B,CAClD,MAAO,CACL,IAAmC,GACjC,EAAa,EAAS,EAAM,CAAQ,EAAG,IAAI,EAAS,GAAI,CAAI,EAC9D,YAA2C,GACzC,EAAa,EAAiB,EAAM,CAAM,EAAG,gBAAgB,EAAO,GAAI,CAAI,EAC9E,WAAyC,EAAc,EAAW,MAChE,EAAa,EAAe,EAAM,EAAM,CAAQ,EAAG,SAAS,EAAK,cAAc,EAAS,GAAI,CAAI,EAClG,MAAqC,GAAqB,EAAS,EAAM,CAAQ,EACjF,SAAwC,GAAqB,EAAY,EAAM,CAAQ,EACvF,iBAAgD,GAAmB,EAAoB,EAAM,CAAM,EACnG,gBAA8C,EAAc,EAAW,MACrE,EAAkB,EAAM,EAAM,CAAQ,EACxC,cAA6C,GAAmB,EAAiB,EAAM,CAAM,EAC7F,aAA2C,EAAc,EAAW,MAAQ,EAAe,EAAM,EAAM,CAAQ,CACjH,CACF"}
1
+ {"version":3,"file":"query.cjs","names":[],"sources":["../src/query.ts"],"sourcesContent":["import { AssayQueryError } from './errors';\n\n/** DOM query helpers for test environments. */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any queryable DOM root — see {@link within}. */\nexport interface QueryScope {\n get<E extends Element = Element>(selector: string): E;\n getByTestId<E extends Element = Element>(testId: string): E;\n getByText<E extends Element = Element>(text: string, selector?: string): E;\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\ntype QueryRoot = ParentNode;\n\nconst describeRoot = (root: QueryRoot): string => {\n if (root instanceof Element) return root.outerHTML.slice(0, 500);\n\n if (root instanceof ShadowRoot) return `<shadow-root>${root.innerHTML.slice(0, 500)}</shadow-root>`;\n\n return root.nodeName;\n};\n\nconst requireMatch = <E extends Element>(element: E | null, description: string, root: QueryRoot): E => {\n if (element) return element;\n\n throw new AssayQueryError(`Unable to find ${description} within ${describeRoot(root)}.`);\n};\n\nconst query = <E extends Element = Element>(root: QueryRoot, selector: string): E | null =>\n root.querySelector<E>(selector);\n\nconst queryAll = <E extends Element = Element>(root: QueryRoot, selector: string): E[] => {\n return Array.from(root.querySelectorAll<E>(selector));\n};\n\nconst queryByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E | null =>\n queryAll<E>(root, '[data-testid]').find((element) => element.getAttribute('data-testid') === testId) ?? null;\n\nconst queryAllByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E[] =>\n queryAll<E>(root, '[data-testid]').filter((element) => element.getAttribute('data-testid') === testId);\n\nconst queryByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E | null => {\n for (const element of root.querySelectorAll<E>(selector)) {\n if (element.textContent?.trim() === text) return element;\n }\n\n return null;\n};\n\nconst queryAllByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E[] =>\n queryAll<E>(root, selector).filter((element) => element.textContent?.trim() === text);\n\n/**\n * Queries the shadow root of a custom element for a matching CSS selector.\n * Returns `null` if the element has no shadow root or no match is found — safe to call on\n * both open-shadow custom elements and plain elements without checking `shadowRoot` first.\n */\nexport function queryInShadow<E extends Element = Element>(host: Element, selector: string): E | null {\n return host.shadowRoot ? query<E>(host.shadowRoot, selector) : null;\n}\n\n/** Queries all matching elements inside the shadow root of a custom element. */\nexport function queryAllInShadow<E extends Element = Element>(host: Element, selector: string): E[] {\n return host.shadowRoot ? queryAll<E>(host.shadowRoot, selector) : [];\n}\n\n/**\n * Queries a shadow DOM element by its CSS `part` attribute — shorthand for\n * `queryInShadow(host, '[part=\"name\"]')`.\n *\n * @example\n * const btn = queryPart(carousel, 'prev-btn');\n * expect(btn).not.toBeNull();\n */\nexport function queryPart<E extends Element = Element>(host: Element, part: string): E | null {\n return (\n queryAllInShadow<E>(host, '[part]').find((element) => element.getAttribute('part')?.split(/\\s+/).includes(part)) ??\n null\n );\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every child assigned to the\n * default slot when no name is given.\n *\n * @example\n * const slides = getSlotted(carousel);\n * expect(slides).toHaveLength(3);\n */\nexport function getSlotted<E extends Element = Element>(host: Element, slotName?: string): E[] {\n return Array.from(host.children).filter((element) =>\n slotName === undefined ? !element.getAttribute('slot') : element.getAttribute('slot') === slotName,\n ) as E[];\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to a DOM root — useful for light DOM and shadow roots.\n *\n * @example\n * const view = within(fixture.shadow!);\n * expect(view.get('.title').textContent).toBe('Hello');\n */\nexport function within(root: QueryRoot): QueryScope {\n return {\n get: <E extends Element = Element>(selector: string) =>\n requireMatch(query<E>(root, selector), `\"${selector}\"`, root),\n getByTestId: <E extends Element = Element>(testId: string) =>\n requireMatch(queryByTestId<E>(root, testId), `data-testid \"${testId}\"`, root),\n getByText: <E extends Element = Element>(text: string, selector = '*') =>\n requireMatch(queryByText<E>(root, text, selector), `text \"${text}\" matching \"${selector}\"`, root),\n query: <E extends Element = Element>(selector: string) => query<E>(root, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(root, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(root, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(root, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(root, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(root, text, selector),\n };\n}\n"],"mappings":"gCAuBA,IAAM,EAAgB,GAChB,aAAgB,QAAgB,EAAK,UAAU,MAAM,EAAG,GAAG,EAE3D,aAAgB,WAAmB,gBAAgB,EAAK,UAAU,MAAM,EAAG,GAAG,EAAE,gBAE7E,EAAK,SAGR,GAAmC,EAAmB,EAAqB,IAAuB,CACtG,GAAI,EAAS,OAAO,EAEpB,MAAM,IAAI,EAAA,gBAAgB,kBAAkB,EAAY,UAAU,EAAa,CAAI,EAAE,EAAE,CACzF,EAEM,GAAsC,EAAiB,IAC3D,EAAK,cAAiB,CAAQ,EAE1B,GAAyC,EAAiB,IACvD,MAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC,EAGhD,GAA8C,EAAiB,IACnE,EAAY,EAAM,eAAe,CAAC,CAAC,KAAM,GAAY,EAAQ,aAAa,aAAa,IAAM,CAAM,GAAK,KAEpG,GAAiD,EAAiB,IACtE,EAAY,EAAM,eAAe,CAAC,CAAC,OAAQ,GAAY,EAAQ,aAAa,aAAa,IAAM,CAAM,EAEjG,GAA4C,EAAiB,EAAc,EAAW,MAAkB,CAC5G,IAAK,IAAM,KAAW,EAAK,iBAAoB,CAAQ,EACrD,GAAI,EAAQ,aAAa,KAAK,IAAM,EAAM,OAAO,EAGnD,OAAO,IACT,EAEM,GAA+C,EAAiB,EAAc,EAAW,MAC7F,EAAY,EAAM,CAAQ,CAAC,CAAC,OAAQ,GAAY,EAAQ,aAAa,KAAK,IAAM,CAAI,EAOtF,SAAgB,EAA2C,EAAe,EAA4B,CACpG,OAAO,EAAK,WAAa,EAAS,EAAK,WAAY,CAAQ,EAAI,IACjE,CAGA,SAAgB,EAA8C,EAAe,EAAuB,CAClG,OAAO,EAAK,WAAa,EAAY,EAAK,WAAY,CAAQ,EAAI,CAAC,CACrE,CAUA,SAAgB,EAAuC,EAAe,EAAwB,CAC5F,OACE,EAAoB,EAAM,QAAQ,CAAC,CAAC,KAAM,GAAY,EAAQ,aAAa,MAAM,CAAC,EAAE,MAAM,KAAK,CAAC,CAAC,SAAS,CAAI,CAAC,GAC/G,IAEJ,CAUA,SAAgB,EAAwC,EAAe,EAAwB,CAC7F,OAAO,MAAM,KAAK,EAAK,QAAQ,CAAC,CAAC,OAAQ,GACvC,IAAa,IAAA,GAAY,CAAC,EAAQ,aAAa,MAAM,EAAI,EAAQ,aAAa,MAAM,IAAM,CAC5F,CACF,CAWA,SAAgB,EAAO,EAA6B,CAClD,MAAO,CACL,IAAmC,GACjC,EAAa,EAAS,EAAM,CAAQ,EAAG,IAAI,EAAS,GAAI,CAAI,EAC9D,YAA2C,GACzC,EAAa,EAAiB,EAAM,CAAM,EAAG,gBAAgB,EAAO,GAAI,CAAI,EAC9E,WAAyC,EAAc,EAAW,MAChE,EAAa,EAAe,EAAM,EAAM,CAAQ,EAAG,SAAS,EAAK,cAAc,EAAS,GAAI,CAAI,EAClG,MAAqC,GAAqB,EAAS,EAAM,CAAQ,EACjF,SAAwC,GAAqB,EAAY,EAAM,CAAQ,EACvF,iBAAgD,GAAmB,EAAoB,EAAM,CAAM,EACnG,gBAA8C,EAAc,EAAW,MACrE,EAAkB,EAAM,EAAM,CAAQ,EACxC,cAA6C,GAAmB,EAAiB,EAAM,CAAM,EAC7F,aAA2C,EAAc,EAAW,MAAQ,EAAe,EAAM,EAAM,CAAQ,CACjH,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAEA,+CAA+C;AAI/C,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAC;IACtD,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC;IAC5D,SAAS,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;IAC3E,KAAK,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;IAC/D,QAAQ,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;IAC7D,gBAAgB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;IACnE,cAAc,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;IAClF,aAAa,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;IACrE,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;CACrF;AAID,KAAK,SAAS,GAAG,UAAU,CAAC;AAyC5B;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAEpG;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,EAAE,CAElG;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAK5F;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAI7F;AAID;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,CAgBlD"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAEA,+CAA+C;AAI/C,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,CAAC;IACtD,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC;IAC5D,SAAS,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC;IAC3E,KAAK,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;IAC/D,QAAQ,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;IAC7D,gBAAgB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;IACnE,cAAc,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;IAClF,aAAa,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;IACrE,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;CACrF;AAID,KAAK,SAAS,GAAG,UAAU,CAAC;AAwC5B;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAEpG;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,EAAE,CAElG;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAK5F;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,CAI7F;AAID;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,CAgBlD"}
package/dist/query.js CHANGED
@@ -6,7 +6,7 @@ var t = (e) => e instanceof Element ? e.outerHTML.slice(0, 500) : e instanceof S
6
6
  }, r = (e, t) => e.querySelector(t), i = (e, t) => Array.from(e.querySelectorAll(t)), a = (e, t) => i(e, "[data-testid]").find((e) => e.getAttribute("data-testid") === t) ?? null, o = (e, t) => i(e, "[data-testid]").filter((e) => e.getAttribute("data-testid") === t), s = (e, t, n = "*") => {
7
7
  for (let r of e.querySelectorAll(n)) if (r.textContent?.trim() === t) return r;
8
8
  return null;
9
- }, c = (e, t, n = "*") => Array.from(e.querySelectorAll(n)).filter((e) => e.textContent?.trim() === t);
9
+ }, c = (e, t, n = "*") => i(e, n).filter((e) => e.textContent?.trim() === t);
10
10
  function l(e, t) {
11
11
  return e.shadowRoot ? r(e.shadowRoot, t) : null;
12
12
  }
package/dist/query.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"query.js","names":[],"sources":["../src/query.ts"],"sourcesContent":["import { AssayQueryError } from './errors';\n\n/** DOM query helpers for test environments. */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any queryable DOM root — see {@link within}. */\nexport interface QueryScope {\n get<E extends Element = Element>(selector: string): E;\n getByTestId<E extends Element = Element>(testId: string): E;\n getByText<E extends Element = Element>(text: string, selector?: string): E;\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\ntype QueryRoot = ParentNode;\n\nconst describeRoot = (root: QueryRoot): string => {\n if (root instanceof Element) return root.outerHTML.slice(0, 500);\n\n if (root instanceof ShadowRoot) return `<shadow-root>${root.innerHTML.slice(0, 500)}</shadow-root>`;\n\n return root.nodeName;\n};\n\nconst requireMatch = <E extends Element>(element: E | null, description: string, root: QueryRoot): E => {\n if (element) return element;\n\n throw new AssayQueryError(`Unable to find ${description} within ${describeRoot(root)}.`);\n};\n\nconst query = <E extends Element = Element>(root: QueryRoot, selector: string): E | null =>\n root.querySelector<E>(selector);\n\nconst queryAll = <E extends Element = Element>(root: QueryRoot, selector: string): E[] => {\n return Array.from(root.querySelectorAll<E>(selector));\n};\n\nconst queryByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E | null =>\n queryAll<E>(root, '[data-testid]').find((element) => element.getAttribute('data-testid') === testId) ?? null;\n\nconst queryAllByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E[] =>\n queryAll<E>(root, '[data-testid]').filter((element) => element.getAttribute('data-testid') === testId);\n\nconst queryByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E | null => {\n for (const el of root.querySelectorAll<E>(selector)) {\n if (el.textContent?.trim() === text) return el;\n }\n\n return null;\n};\n\nconst queryAllByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E[] => {\n return Array.from(root.querySelectorAll<E>(selector)).filter((el) => el.textContent?.trim() === text);\n};\n\n/**\n * Queries the shadow root of a custom element for a matching CSS selector.\n * Returns `null` if the element has no shadow root or no match is found — safe to call on\n * both open-shadow custom elements and plain elements without checking `shadowRoot` first.\n */\nexport function queryInShadow<E extends Element = Element>(host: Element, selector: string): E | null {\n return host.shadowRoot ? query<E>(host.shadowRoot, selector) : null;\n}\n\n/** Queries all matching elements inside the shadow root of a custom element. */\nexport function queryAllInShadow<E extends Element = Element>(host: Element, selector: string): E[] {\n return host.shadowRoot ? queryAll<E>(host.shadowRoot, selector) : [];\n}\n\n/**\n * Queries a shadow DOM element by its CSS `part` attribute — shorthand for\n * `queryInShadow(host, '[part=\"name\"]')`.\n *\n * @example\n * const btn = queryPart(carousel, 'prev-btn');\n * expect(btn).not.toBeNull();\n */\nexport function queryPart<E extends Element = Element>(host: Element, part: string): E | null {\n return (\n queryAllInShadow<E>(host, '[part]').find((element) => element.getAttribute('part')?.split(/\\s+/).includes(part)) ??\n null\n );\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every child assigned to the\n * default slot when no name is given.\n *\n * @example\n * const slides = getSlotted(carousel);\n * expect(slides).toHaveLength(3);\n */\nexport function getSlotted<E extends Element = Element>(host: Element, slotName?: string): E[] {\n return Array.from(host.children).filter((element) =>\n slotName === undefined ? !element.getAttribute('slot') : element.getAttribute('slot') === slotName,\n ) as E[];\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to a DOM root — useful for light DOM and shadow roots.\n *\n * @example\n * const view = within(fixture.shadow!);\n * expect(view.get('.title').textContent).toBe('Hello');\n */\nexport function within(root: QueryRoot): QueryScope {\n return {\n get: <E extends Element = Element>(selector: string) =>\n requireMatch(query<E>(root, selector), `\"${selector}\"`, root),\n getByTestId: <E extends Element = Element>(testId: string) =>\n requireMatch(queryByTestId<E>(root, testId), `data-testid \"${testId}\"`, root),\n getByText: <E extends Element = Element>(text: string, selector = '*') =>\n requireMatch(queryByText<E>(root, text, selector), `text \"${text}\" matching \"${selector}\"`, root),\n query: <E extends Element = Element>(selector: string) => query<E>(root, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(root, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(root, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(root, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(root, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(root, text, selector),\n };\n}\n"],"mappings":";;AAuBA,IAAM,KAAgB,MAChB,aAAgB,UAAgB,EAAK,UAAU,MAAM,GAAG,GAAG,IAE3D,aAAgB,aAAmB,gBAAgB,EAAK,UAAU,MAAM,GAAG,GAAG,EAAE,kBAE7E,EAAK,UAGR,KAAmC,GAAmB,GAAqB,MAAuB;CACtG,IAAI,GAAS,OAAO;CAEpB,MAAM,IAAI,EAAgB,kBAAkB,EAAY,UAAU,EAAa,CAAI,EAAE,EAAE;AACzF,GAEM,KAAsC,GAAiB,MAC3D,EAAK,cAAiB,CAAQ,GAE1B,KAAyC,GAAiB,MACvD,MAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC,GAGhD,KAA8C,GAAiB,MACnE,EAAY,GAAM,eAAe,CAAC,CAAC,MAAM,MAAY,EAAQ,aAAa,aAAa,MAAM,CAAM,KAAK,MAEpG,KAAiD,GAAiB,MACtE,EAAY,GAAM,eAAe,CAAC,CAAC,QAAQ,MAAY,EAAQ,aAAa,aAAa,MAAM,CAAM,GAEjG,KAA4C,GAAiB,GAAc,IAAW,QAAkB;CAC5G,KAAK,IAAM,KAAM,EAAK,iBAAoB,CAAQ,GAChD,IAAI,EAAG,aAAa,KAAK,MAAM,GAAM,OAAO;CAG9C,OAAO;AACT,GAEM,KAA+C,GAAiB,GAAc,IAAW,QACtF,MAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC,CAAC,CAAC,QAAQ,MAAO,EAAG,aAAa,KAAK,MAAM,CAAI;AAQtG,SAAgB,EAA2C,GAAe,GAA4B;CACpG,OAAO,EAAK,aAAa,EAAS,EAAK,YAAY,CAAQ,IAAI;AACjE;AAGA,SAAgB,EAA8C,GAAe,GAAuB;CAClG,OAAO,EAAK,aAAa,EAAY,EAAK,YAAY,CAAQ,IAAI,CAAC;AACrE;AAUA,SAAgB,EAAuC,GAAe,GAAwB;CAC5F,OACE,EAAoB,GAAM,QAAQ,CAAC,CAAC,MAAM,MAAY,EAAQ,aAAa,MAAM,CAAC,EAAE,MAAM,KAAK,CAAC,CAAC,SAAS,CAAI,CAAC,KAC/G;AAEJ;AAUA,SAAgB,EAAwC,GAAe,GAAwB;CAC7F,OAAO,MAAM,KAAK,EAAK,QAAQ,CAAC,CAAC,QAAQ,MACvC,MAAa,KAAA,IAAY,CAAC,EAAQ,aAAa,MAAM,IAAI,EAAQ,aAAa,MAAM,MAAM,CAC5F;AACF;AAWA,SAAgB,EAAO,GAA6B;CAClD,OAAO;EACL,MAAmC,MACjC,EAAa,EAAS,GAAM,CAAQ,GAAG,IAAI,EAAS,IAAI,CAAI;EAC9D,cAA2C,MACzC,EAAa,EAAiB,GAAM,CAAM,GAAG,gBAAgB,EAAO,IAAI,CAAI;EAC9E,YAAyC,GAAc,IAAW,QAChE,EAAa,EAAe,GAAM,GAAM,CAAQ,GAAG,SAAS,EAAK,cAAc,EAAS,IAAI,CAAI;EAClG,QAAqC,MAAqB,EAAS,GAAM,CAAQ;EACjF,WAAwC,MAAqB,EAAY,GAAM,CAAQ;EACvF,mBAAgD,MAAmB,EAAoB,GAAM,CAAM;EACnG,iBAA8C,GAAc,IAAW,QACrE,EAAkB,GAAM,GAAM,CAAQ;EACxC,gBAA6C,MAAmB,EAAiB,GAAM,CAAM;EAC7F,cAA2C,GAAc,IAAW,QAAQ,EAAe,GAAM,GAAM,CAAQ;CACjH;AACF"}
1
+ {"version":3,"file":"query.js","names":[],"sources":["../src/query.ts"],"sourcesContent":["import { AssayQueryError } from './errors';\n\n/** DOM query helpers for test environments. */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any queryable DOM root — see {@link within}. */\nexport interface QueryScope {\n get<E extends Element = Element>(selector: string): E;\n getByTestId<E extends Element = Element>(testId: string): E;\n getByText<E extends Element = Element>(text: string, selector?: string): E;\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\ntype QueryRoot = ParentNode;\n\nconst describeRoot = (root: QueryRoot): string => {\n if (root instanceof Element) return root.outerHTML.slice(0, 500);\n\n if (root instanceof ShadowRoot) return `<shadow-root>${root.innerHTML.slice(0, 500)}</shadow-root>`;\n\n return root.nodeName;\n};\n\nconst requireMatch = <E extends Element>(element: E | null, description: string, root: QueryRoot): E => {\n if (element) return element;\n\n throw new AssayQueryError(`Unable to find ${description} within ${describeRoot(root)}.`);\n};\n\nconst query = <E extends Element = Element>(root: QueryRoot, selector: string): E | null =>\n root.querySelector<E>(selector);\n\nconst queryAll = <E extends Element = Element>(root: QueryRoot, selector: string): E[] => {\n return Array.from(root.querySelectorAll<E>(selector));\n};\n\nconst queryByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E | null =>\n queryAll<E>(root, '[data-testid]').find((element) => element.getAttribute('data-testid') === testId) ?? null;\n\nconst queryAllByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E[] =>\n queryAll<E>(root, '[data-testid]').filter((element) => element.getAttribute('data-testid') === testId);\n\nconst queryByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E | null => {\n for (const element of root.querySelectorAll<E>(selector)) {\n if (element.textContent?.trim() === text) return element;\n }\n\n return null;\n};\n\nconst queryAllByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E[] =>\n queryAll<E>(root, selector).filter((element) => element.textContent?.trim() === text);\n\n/**\n * Queries the shadow root of a custom element for a matching CSS selector.\n * Returns `null` if the element has no shadow root or no match is found — safe to call on\n * both open-shadow custom elements and plain elements without checking `shadowRoot` first.\n */\nexport function queryInShadow<E extends Element = Element>(host: Element, selector: string): E | null {\n return host.shadowRoot ? query<E>(host.shadowRoot, selector) : null;\n}\n\n/** Queries all matching elements inside the shadow root of a custom element. */\nexport function queryAllInShadow<E extends Element = Element>(host: Element, selector: string): E[] {\n return host.shadowRoot ? queryAll<E>(host.shadowRoot, selector) : [];\n}\n\n/**\n * Queries a shadow DOM element by its CSS `part` attribute — shorthand for\n * `queryInShadow(host, '[part=\"name\"]')`.\n *\n * @example\n * const btn = queryPart(carousel, 'prev-btn');\n * expect(btn).not.toBeNull();\n */\nexport function queryPart<E extends Element = Element>(host: Element, part: string): E | null {\n return (\n queryAllInShadow<E>(host, '[part]').find((element) => element.getAttribute('part')?.split(/\\s+/).includes(part)) ??\n null\n );\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every child assigned to the\n * default slot when no name is given.\n *\n * @example\n * const slides = getSlotted(carousel);\n * expect(slides).toHaveLength(3);\n */\nexport function getSlotted<E extends Element = Element>(host: Element, slotName?: string): E[] {\n return Array.from(host.children).filter((element) =>\n slotName === undefined ? !element.getAttribute('slot') : element.getAttribute('slot') === slotName,\n ) as E[];\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to a DOM root — useful for light DOM and shadow roots.\n *\n * @example\n * const view = within(fixture.shadow!);\n * expect(view.get('.title').textContent).toBe('Hello');\n */\nexport function within(root: QueryRoot): QueryScope {\n return {\n get: <E extends Element = Element>(selector: string) =>\n requireMatch(query<E>(root, selector), `\"${selector}\"`, root),\n getByTestId: <E extends Element = Element>(testId: string) =>\n requireMatch(queryByTestId<E>(root, testId), `data-testid \"${testId}\"`, root),\n getByText: <E extends Element = Element>(text: string, selector = '*') =>\n requireMatch(queryByText<E>(root, text, selector), `text \"${text}\" matching \"${selector}\"`, root),\n query: <E extends Element = Element>(selector: string) => query<E>(root, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(root, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(root, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(root, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(root, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(root, text, selector),\n };\n}\n"],"mappings":";;AAuBA,IAAM,KAAgB,MAChB,aAAgB,UAAgB,EAAK,UAAU,MAAM,GAAG,GAAG,IAE3D,aAAgB,aAAmB,gBAAgB,EAAK,UAAU,MAAM,GAAG,GAAG,EAAE,kBAE7E,EAAK,UAGR,KAAmC,GAAmB,GAAqB,MAAuB;CACtG,IAAI,GAAS,OAAO;CAEpB,MAAM,IAAI,EAAgB,kBAAkB,EAAY,UAAU,EAAa,CAAI,EAAE,EAAE;AACzF,GAEM,KAAsC,GAAiB,MAC3D,EAAK,cAAiB,CAAQ,GAE1B,KAAyC,GAAiB,MACvD,MAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC,GAGhD,KAA8C,GAAiB,MACnE,EAAY,GAAM,eAAe,CAAC,CAAC,MAAM,MAAY,EAAQ,aAAa,aAAa,MAAM,CAAM,KAAK,MAEpG,KAAiD,GAAiB,MACtE,EAAY,GAAM,eAAe,CAAC,CAAC,QAAQ,MAAY,EAAQ,aAAa,aAAa,MAAM,CAAM,GAEjG,KAA4C,GAAiB,GAAc,IAAW,QAAkB;CAC5G,KAAK,IAAM,KAAW,EAAK,iBAAoB,CAAQ,GACrD,IAAI,EAAQ,aAAa,KAAK,MAAM,GAAM,OAAO;CAGnD,OAAO;AACT,GAEM,KAA+C,GAAiB,GAAc,IAAW,QAC7F,EAAY,GAAM,CAAQ,CAAC,CAAC,QAAQ,MAAY,EAAQ,aAAa,KAAK,MAAM,CAAI;AAOtF,SAAgB,EAA2C,GAAe,GAA4B;CACpG,OAAO,EAAK,aAAa,EAAS,EAAK,YAAY,CAAQ,IAAI;AACjE;AAGA,SAAgB,EAA8C,GAAe,GAAuB;CAClG,OAAO,EAAK,aAAa,EAAY,EAAK,YAAY,CAAQ,IAAI,CAAC;AACrE;AAUA,SAAgB,EAAuC,GAAe,GAAwB;CAC5F,OACE,EAAoB,GAAM,QAAQ,CAAC,CAAC,MAAM,MAAY,EAAQ,aAAa,MAAM,CAAC,EAAE,MAAM,KAAK,CAAC,CAAC,SAAS,CAAI,CAAC,KAC/G;AAEJ;AAUA,SAAgB,EAAwC,GAAe,GAAwB;CAC7F,OAAO,MAAM,KAAK,EAAK,QAAQ,CAAC,CAAC,QAAQ,MACvC,MAAa,KAAA,IAAY,CAAC,EAAQ,aAAa,MAAM,IAAI,EAAQ,aAAa,MAAM,MAAM,CAC5F;AACF;AAWA,SAAgB,EAAO,GAA6B;CAClD,OAAO;EACL,MAAmC,MACjC,EAAa,EAAS,GAAM,CAAQ,GAAG,IAAI,EAAS,IAAI,CAAI;EAC9D,cAA2C,MACzC,EAAa,EAAiB,GAAM,CAAM,GAAG,gBAAgB,EAAO,IAAI,CAAI;EAC9E,YAAyC,GAAc,IAAW,QAChE,EAAa,EAAe,GAAM,GAAM,CAAQ,GAAG,SAAS,EAAK,cAAc,EAAS,IAAI,CAAI;EAClG,QAAqC,MAAqB,EAAS,GAAM,CAAQ;EACjF,WAAwC,MAAqB,EAAY,GAAM,CAAQ;EACvF,mBAAgD,MAAmB,EAAoB,GAAM,CAAM;EACnG,iBAA8C,GAAc,IAAW,QACrE,EAAkB,GAAM,GAAM,CAAQ;EACxC,gBAA6C,MAAmB,EAAiB,GAAM,CAAM;EAC7F,cAA2C,GAAc,IAAW,QAAQ,EAAe,GAAM,GAAM,CAAQ;CACjH;AACF"}
package/dist/wait.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs");var t=e=>e.reason??new DOMException(`The operation was aborted.`,`AbortError`),n=(e=0,{signal:n}={})=>new Promise((r,i)=>{if(n?.aborted){i(t(n));return}let a=setTimeout(o,e);function o(){n?.removeEventListener(`abort`,s),r()}function s(){clearTimeout(a),i(t(n))}n?.addEventListener(`abort`,s,{once:!0})});async function r(r,{interval:i=50,signal:a,timeout:o=1e3}={}){let s=Date.now()+o;for(;;){if(a?.aborted)throw t(a);if(await r())return;if(Date.now()>=s)break;await n(i,{signal:a})}throw new e.AssayTimeoutError(`waitUntil timed out after ${o}ms`)}async function i(r,{interval:i=50,message:a,signal:o,timeout:s=1e3}={}){let c=Date.now()+s,l;for(;;){if(o?.aborted)throw t(o);try{await r();return}catch(e){l=e}if(Date.now()>=c)break;await n(i,{signal:o})}let u=`retry timed out after ${s}ms`,d=a?`${a} (${u})`:u,f=l instanceof Error?l.message:l==null?``:String(l);throw new e.AssayTimeoutError(f?`${d}\n${f}`:d,{cause:l})}function a(n,r,{signal:i,timeout:a=1e3}={}){return new Promise((o,s)=>{if(i?.aborted){s(t(i));return}let c=()=>{clearTimeout(u),i?.removeEventListener(`abort`,d),n.removeEventListener(r,l)},l=e=>{c(),o(e)},u=setTimeout(()=>{c(),s(new e.AssayTimeoutError(`waitForEvent: "${r}" timed out after ${a}ms`))},a),d=()=>{c(),s(t(i))};n.addEventListener(r,l,{once:!0}),i?.addEventListener(`abort`,d,{once:!0})})}var o=()=>new Promise(e=>{queueMicrotask(e)});exports.delay=n,exports.nextTick=o,exports.retry=i,exports.waitForEvent=a,exports.waitUntil=r;
1
+ const e=require("./errors.cjs");let t=require("@vielzeug/arsenal");var n=Symbol(`assay.deadline`),r=2147483647,i=()=>performance.now(),a=(e,t,n)=>{if(!Number.isFinite(t)||t>r||(n?t<0:t<=0))throw RangeError(`${e} must be a ${n?`non-negative`:`positive`} finite number no greater than ${r}`)},o=(e,t)=>{a(`interval`,e,!1),a(`timeout`,t,!0)},s=(e,n)=>new Promise((r,i)=>{if(n?.aborted){i((0,t.abortError)(n,`The operation was aborted.`));return}let a=setTimeout(o,e);function o(){n?.removeEventListener(`abort`,s),r()}function s(){clearTimeout(a),i((0,t.abortError)(n,`The operation was aborted.`))}n?.addEventListener(`abort`,s,{once:!0})}),c=(e,r,a)=>new Promise((o,s)=>{if(a?.aborted){s((0,t.abortError)(a,`The operation was aborted.`));return}let c=!1,l=()=>!c&&(c=!0,clearTimeout(f),a?.removeEventListener(`abort`,p),!0),u=e=>{l()&&o(e)},d=e=>{l()&&s(e)},f=setTimeout(()=>d(n),Math.max(0,r-i())),p=()=>d((0,t.abortError)(a,`The operation was aborted.`));a?.addEventListener(`abort`,p,{once:!0});try{Promise.resolve(e()).then(u,d)}catch(e){d(e)}}),l=(e=0,t={})=>(a(`ms`,e,!0),s(e,t.signal)),u=()=>new Promise(e=>queueMicrotask(e));async function d(r,{interval:a=50,signal:l,timeout:u=1e3}={}){o(a,u);let d=i()+u,f=!0;for(;;){if(l?.aborted)throw(0,t.abortError)(l,`The operation was aborted.`);if(!f&&i()>=d)throw new e.AssayTimeoutError(`waitUntil timed out after ${u}ms`);f=!1;let o;try{o=await c(r,d,l)}catch(t){throw t===n?new e.AssayTimeoutError(`waitUntil timed out after ${u}ms`):t}if(o)return;let p=d-i();if(p<=0)throw new e.AssayTimeoutError(`waitUntil timed out after ${u}ms`);await s(Math.min(a,p),l)}}async function f(r,{interval:a=50,message:l,signal:u,timeout:d=1e3}={}){o(a,d);let f=i()+d,p=!0,m;for(;;){if(u?.aborted)throw(0,t.abortError)(u,`The operation was aborted.`);if(!p&&i()>=f)break;p=!1;try{await c(r,f,u);return}catch(e){if(u?.aborted)throw(0,t.abortError)(u,`The operation was aborted.`);if(e===n)break;m=e}let e=f-i();if(e<=0)break;await s(Math.min(a,e),u)}let h=`eventually timed out after ${d}ms`,g=l?`${l} (${h})`:h,_=m instanceof Error?m.message:m==null?``:String(m);throw new e.AssayTimeoutError(_?`${g}\n${_}`:g,{cause:m})}function p(n,r,{signal:i,timeout:o=1e3}={}){return a(`timeout`,o,!0),new Promise((a,s)=>{if(i?.aborted){s((0,t.abortError)(i,`The operation was aborted.`));return}let c=()=>{clearTimeout(u),i?.removeEventListener(`abort`,d),n.removeEventListener(r,l)},l=e=>{c(),a(e)},u=setTimeout(()=>{c(),s(new e.AssayTimeoutError(`waitForEvent: "${r}" timed out after ${o}ms`))},o),d=()=>{c(),s((0,t.abortError)(i,`The operation was aborted.`))};n.addEventListener(r,l,{once:!0}),i?.addEventListener(`abort`,d,{once:!0})})}exports.delay=l,exports.eventually=f,exports.nextTick=u,exports.waitForEvent=p,exports.waitUntil=d;
2
2
  //# sourceMappingURL=wait.cjs.map
package/dist/wait.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"wait.cjs","names":[],"sources":["../src/wait.ts"],"sourcesContent":["import { AssayTimeoutError } from './errors';\n\nexport interface WaitOptions {\n /** Polling interval in ms (default: 50). */\n interval?: number;\n /** Cancel the pending wait. */\n signal?: AbortSignal;\n /** Maximum wait time in ms (default: 1000). */\n timeout?: number;\n}\n\nexport interface RetryOptions extends WaitOptions {\n /** Context included in the timeout error. */\n message?: string;\n}\n\nexport interface DelayOptions {\n /** Cancel the pending delay. */\n signal?: AbortSignal;\n}\n\nconst abortReason = (signal: AbortSignal): unknown =>\n signal.reason ?? new DOMException('The operation was aborted.', 'AbortError');\n\n/**\n * Resolve after `ms` milliseconds. Rejects with the abort reason when cancelled.\n */\nexport const delay = (ms = 0, { signal }: DelayOptions = {}): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortReason(signal));\n\n return;\n }\n\n const timer = setTimeout(done, ms);\n\n function done(): void {\n signal?.removeEventListener('abort', abort);\n resolve();\n }\n\n function abort(): void {\n clearTimeout(timer);\n reject(abortReason(signal!));\n }\n\n signal?.addEventListener('abort', abort, { once: true });\n });\n\n/** Poll until a synchronous or asynchronous predicate returns `true`. */\nexport async function waitUntil(\n predicate: () => boolean | Promise<boolean>,\n { interval = 50, signal, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n\n for (;;) {\n if (signal?.aborted) throw abortReason(signal);\n\n if (await predicate()) return;\n\n if (Date.now() >= deadline) break;\n\n await delay(interval, { signal });\n }\n\n throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n}\n\n/** Retry an assertion until it stops throwing. */\nexport async function retry(\n assertion: () => void | Promise<void>,\n { interval = 50, message, signal, timeout = 1000 }: RetryOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n let lastError: unknown;\n\n for (;;) {\n if (signal?.aborted) throw abortReason(signal);\n\n try {\n await assertion();\n\n return;\n } catch (error) {\n lastError = error;\n }\n\n if (Date.now() >= deadline) break;\n\n await delay(interval, { signal });\n }\n\n const timing = `retry timed out after ${timeout}ms`;\n const context = message ? `${message} (${timing})` : timing;\n const detail = lastError instanceof Error ? lastError.message : lastError == null ? '' : String(lastError);\n\n throw new AssayTimeoutError(detail ? `${context}\\n${detail}` : context, { cause: lastError });\n}\n\n/** Resolve with the next event of `type`, or reject when it times out or is aborted. */\nexport function waitForEvent<T extends Event = Event>(\n target: EventTarget,\n type: string,\n { signal, timeout = 1000 }: Omit<WaitOptions, 'interval'> = {},\n): Promise<T> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortReason(signal));\n\n return;\n }\n\n const dispose = () => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n target.removeEventListener(type, onEvent);\n };\n const onEvent = (e: Event): void => {\n dispose();\n resolve(e as T);\n };\n const timer = setTimeout(() => {\n dispose();\n reject(new AssayTimeoutError(`waitForEvent: \"${type}\" timed out after ${timeout}ms`));\n }, timeout);\n const onAbort = () => {\n dispose();\n reject(abortReason(signal!));\n };\n\n target.addEventListener(type, onEvent, { once: true });\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/** Resolve after one microtask tick. */\nexport const nextTick = (): Promise<void> =>\n new Promise((resolve) => {\n queueMicrotask(resolve);\n });\n"],"mappings":"gCAqBA,IAAM,EAAe,GACnB,EAAO,QAAU,IAAI,aAAa,6BAA8B,YAAY,EAKjE,GAAS,EAAK,EAAG,CAAE,UAAyB,CAAC,IACxD,IAAI,SAAS,EAAS,IAAW,CAC/B,GAAI,GAAQ,QAAS,CACnB,EAAO,EAAY,CAAM,CAAC,EAE1B,MACF,CAEA,IAAM,EAAQ,WAAW,EAAM,CAAE,EAEjC,SAAS,GAAa,CACpB,GAAQ,oBAAoB,QAAS,CAAK,EAC1C,EAAQ,CACV,CAEA,SAAS,GAAc,CACrB,aAAa,CAAK,EAClB,EAAO,EAAY,CAAO,CAAC,CAC7B,CAEA,GAAQ,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,CACzD,CAAC,EAGH,eAAsB,EACpB,EACA,CAAE,WAAW,GAAI,SAAQ,UAAU,KAAsB,CAAC,EAC3C,CACf,IAAM,EAAW,KAAK,IAAI,EAAI,EAE9B,OAAS,CACP,GAAI,GAAQ,QAAS,MAAM,EAAY,CAAM,EAE7C,GAAI,MAAM,EAAU,EAAG,OAEvB,GAAI,KAAK,IAAI,GAAK,EAAU,MAE5B,MAAM,EAAM,EAAU,CAAE,QAAO,CAAC,CAClC,CAEA,MAAM,IAAI,EAAA,kBAAkB,6BAA6B,EAAQ,GAAG,CACtE,CAGA,eAAsB,EACpB,EACA,CAAE,WAAW,GAAI,UAAS,SAAQ,UAAU,KAAuB,CAAC,EACrD,CACf,IAAM,EAAW,KAAK,IAAI,EAAI,EAC1B,EAEJ,OAAS,CACP,GAAI,GAAQ,QAAS,MAAM,EAAY,CAAM,EAE7C,GAAI,CACF,MAAM,EAAU,EAEhB,MACF,OAAS,EAAO,CACd,EAAY,CACd,CAEA,GAAI,KAAK,IAAI,GAAK,EAAU,MAE5B,MAAM,EAAM,EAAU,CAAE,QAAO,CAAC,CAClC,CAEA,IAAM,EAAS,yBAAyB,EAAQ,IAC1C,EAAU,EAAU,GAAG,EAAQ,IAAI,EAAO,GAAK,EAC/C,EAAS,aAAqB,MAAQ,EAAU,QAAU,GAAa,KAAO,GAAK,OAAO,CAAS,EAEzG,MAAM,IAAI,EAAA,kBAAkB,EAAS,GAAG,EAAQ,IAAI,IAAW,EAAS,CAAE,MAAO,CAAU,CAAC,CAC9F,CAGA,SAAgB,EACd,EACA,EACA,CAAE,SAAQ,UAAU,KAAwC,CAAC,EACjD,CACZ,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,GAAI,GAAQ,QAAS,CACnB,EAAO,EAAY,CAAM,CAAC,EAE1B,MACF,CAEA,IAAM,MAAgB,CACpB,aAAa,CAAK,EAClB,GAAQ,oBAAoB,QAAS,CAAO,EAC5C,EAAO,oBAAoB,EAAM,CAAO,CAC1C,EACM,EAAW,GAAmB,CAClC,EAAQ,EACR,EAAQ,CAAM,CAChB,EACM,EAAQ,eAAiB,CAC7B,EAAQ,EACR,EAAO,IAAI,EAAA,kBAAkB,kBAAkB,EAAK,oBAAoB,EAAQ,GAAG,CAAC,CACtF,EAAG,CAAO,EACJ,MAAgB,CACpB,EAAQ,EACR,EAAO,EAAY,CAAO,CAAC,CAC7B,EAEA,EAAO,iBAAiB,EAAM,EAAS,CAAE,KAAM,EAAK,CAAC,EACrD,GAAQ,iBAAiB,QAAS,EAAS,CAAE,KAAM,EAAK,CAAC,CAC3D,CAAC,CACH,CAGA,IAAa,MACX,IAAI,QAAS,GAAY,CACvB,eAAe,CAAO,CACxB,CAAC"}
1
+ {"version":3,"file":"wait.cjs","names":[],"sources":["../src/wait.ts"],"sourcesContent":["import { abortError } from '@vielzeug/arsenal';\n\nimport { AssayTimeoutError } from './errors';\n\nexport interface DelayOptions {\n signal?: AbortSignal;\n}\n\nexport interface WaitOptions {\n /** Polling interval in ms (default: 50). */\n interval?: number;\n /** Cancel the pending wait. */\n signal?: AbortSignal;\n /** Maximum wait time in ms (default: 1000). */\n timeout?: number;\n}\n\nexport interface EventuallyOptions extends WaitOptions {\n /** Context included in the timeout error. */\n message?: string;\n}\n\nconst DEADLINE = Symbol('assay.deadline');\nconst MAX_TIMER_MS = 2_147_483_647;\nconst now = (): number => performance.now();\n\nconst validateDuration = (name: string, value: number, allowZero: boolean): void => {\n if (!Number.isFinite(value) || value > MAX_TIMER_MS || (allowZero ? value < 0 : value <= 0)) {\n throw new RangeError(\n `${name} must be a ${allowZero ? 'non-negative' : 'positive'} finite number no greater than ${MAX_TIMER_MS}`,\n );\n }\n};\n\nconst validateWait = (interval: number, timeout: number): void => {\n validateDuration('interval', interval, false);\n validateDuration('timeout', timeout, true);\n};\n\nconst sleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal, 'The operation was aborted.'));\n return;\n }\n\n const timer = setTimeout(done, ms);\n\n function done(): void {\n signal?.removeEventListener('abort', abort);\n resolve();\n }\n\n function abort(): void {\n clearTimeout(timer);\n reject(abortError(signal, 'The operation was aborted.'));\n }\n\n signal?.addEventListener('abort', abort, { once: true });\n });\n\nconst runBounded = <T>(run: () => T | Promise<T>, deadline: number, signal?: AbortSignal): Promise<T> =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal, 'The operation was aborted.'));\n return;\n }\n\n let settled = false;\n const cleanup = (): boolean => {\n if (settled) return false;\n settled = true;\n clearTimeout(timer);\n signal?.removeEventListener('abort', abort);\n return true;\n };\n const resolveOnce = (value: T): void => {\n if (cleanup()) resolve(value);\n };\n const rejectOnce = (error: unknown): void => {\n if (cleanup()) reject(error);\n };\n const timer = setTimeout(() => rejectOnce(DEADLINE), Math.max(0, deadline - now()));\n const abort = () => rejectOnce(abortError(signal, 'The operation was aborted.'));\n\n signal?.addEventListener('abort', abort, { once: true });\n\n try {\n Promise.resolve(run()).then(resolveOnce, rejectOnce);\n } catch (error) {\n rejectOnce(error);\n }\n });\n\nexport const delay = (ms = 0, options: DelayOptions = {}): Promise<void> => {\n validateDuration('ms', ms, true);\n return sleep(ms, options.signal);\n};\n\nexport const nextTick = (): Promise<void> => new Promise((resolve) => queueMicrotask(resolve));\n\nexport async function waitUntil(\n predicate: () => boolean | Promise<boolean>,\n { interval = 50, signal, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n validateWait(interval, timeout);\n const deadline = now() + timeout;\n let first = true;\n\n for (;;) {\n if (signal?.aborted) throw abortError(signal, 'The operation was aborted.');\n if (!first && now() >= deadline) throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n first = false;\n\n let matched: boolean;\n try {\n matched = await runBounded(predicate, deadline, signal);\n } catch (error) {\n if (error === DEADLINE) throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n throw error;\n }\n if (matched) return;\n\n const remaining = deadline - now();\n if (remaining <= 0) throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n await sleep(Math.min(interval, remaining), signal);\n }\n}\n\n/**\n * Retry an assertion until it stops throwing or the timeout elapses.\n *\n * Polls the assertion on a fixed interval and preserves the last failure as the\n * timeout cause. Pending asynchronous assertions remain bounded by the same\n * deadline. Use `waitUntil()` for boolean conditions, `delay()` for explicit\n * timer dependencies, and `nextTick()` for one queued microtask boundary.\n *\n * @example\n * await eventually(() => expect(region.textContent).toBe('Saved'));\n */\nexport async function eventually(\n assertion: () => void | Promise<void>,\n { interval = 50, message, signal, timeout = 1000 }: EventuallyOptions = {},\n): Promise<void> {\n validateWait(interval, timeout);\n const deadline = now() + timeout;\n let first = true;\n let lastError: unknown;\n\n for (;;) {\n if (signal?.aborted) throw abortError(signal, 'The operation was aborted.');\n if (!first && now() >= deadline) break;\n first = false;\n\n try {\n await runBounded(assertion, deadline, signal);\n return;\n } catch (error) {\n if (signal?.aborted) throw abortError(signal, 'The operation was aborted.');\n if (error === DEADLINE) break;\n lastError = error;\n }\n\n const remaining = deadline - now();\n if (remaining <= 0) break;\n await sleep(Math.min(interval, remaining), signal);\n }\n\n const timing = `eventually timed out after ${timeout}ms`;\n const context = message ? `${message} (${timing})` : timing;\n const detail = lastError instanceof Error ? lastError.message : lastError == null ? '' : String(lastError);\n\n throw new AssayTimeoutError(detail ? `${context}\\n${detail}` : context, { cause: lastError });\n}\n\n/** Resolve with the next event of `type`, or reject when it times out or is aborted. */\nexport function waitForEvent<T extends Event = Event>(\n target: EventTarget,\n type: string,\n { signal, timeout = 1000 }: Omit<WaitOptions, 'interval'> = {},\n): Promise<T> {\n validateDuration('timeout', timeout, true);\n\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal, 'The operation was aborted.'));\n return;\n }\n\n const dispose = () => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n target.removeEventListener(type, onEvent);\n };\n const onEvent = (event: Event): void => {\n dispose();\n resolve(event as T);\n };\n const timer = setTimeout(() => {\n dispose();\n reject(new AssayTimeoutError(`waitForEvent: \"${type}\" timed out after ${timeout}ms`));\n }, timeout);\n const onAbort = () => {\n dispose();\n reject(abortError(signal, 'The operation was aborted.'));\n };\n\n target.addEventListener(type, onEvent, { once: true });\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n"],"mappings":"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"}
package/dist/wait.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export interface DelayOptions {
2
+ signal?: AbortSignal;
3
+ }
1
4
  export interface WaitOptions {
2
5
  /** Polling interval in ms (default: 50). */
3
6
  interval?: number;
@@ -6,24 +9,25 @@ export interface WaitOptions {
6
9
  /** Maximum wait time in ms (default: 1000). */
7
10
  timeout?: number;
8
11
  }
9
- export interface RetryOptions extends WaitOptions {
12
+ export interface EventuallyOptions extends WaitOptions {
10
13
  /** Context included in the timeout error. */
11
14
  message?: string;
12
15
  }
13
- export interface DelayOptions {
14
- /** Cancel the pending delay. */
15
- signal?: AbortSignal;
16
- }
16
+ export declare const delay: (ms?: number, options?: DelayOptions) => Promise<void>;
17
+ export declare const nextTick: () => Promise<void>;
18
+ export declare function waitUntil(predicate: () => boolean | Promise<boolean>, { interval, signal, timeout }?: WaitOptions): Promise<void>;
17
19
  /**
18
- * Resolve after `ms` milliseconds. Rejects with the abort reason when cancelled.
20
+ * Retry an assertion until it stops throwing or the timeout elapses.
21
+ *
22
+ * Polls the assertion on a fixed interval and preserves the last failure as the
23
+ * timeout cause. Pending asynchronous assertions remain bounded by the same
24
+ * deadline. Use `waitUntil()` for boolean conditions, `delay()` for explicit
25
+ * timer dependencies, and `nextTick()` for one queued microtask boundary.
26
+ *
27
+ * @example
28
+ * await eventually(() => expect(region.textContent).toBe('Saved'));
19
29
  */
20
- export declare const delay: (ms?: number, { signal }?: DelayOptions) => Promise<void>;
21
- /** Poll until a synchronous or asynchronous predicate returns `true`. */
22
- export declare function waitUntil(predicate: () => boolean | Promise<boolean>, { interval, signal, timeout }?: WaitOptions): Promise<void>;
23
- /** Retry an assertion until it stops throwing. */
24
- export declare function retry(assertion: () => void | Promise<void>, { interval, message, signal, timeout }?: RetryOptions): Promise<void>;
30
+ export declare function eventually(assertion: () => void | Promise<void>, { interval, message, signal, timeout }?: EventuallyOptions): Promise<void>;
25
31
  /** Resolve with the next event of `type`, or reject when it times out or is aborted. */
26
32
  export declare function waitForEvent<T extends Event = Event>(target: EventTarget, type: string, { signal, timeout }?: Omit<WaitOptions, 'interval'>): Promise<T>;
27
- /** Resolve after one microtask tick. */
28
- export declare const nextTick: () => Promise<void>;
29
33
  //# sourceMappingURL=wait.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../src/wait.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,gCAAgC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAKD;;GAEG;AACH,eAAO,MAAM,KAAK,GAAI,WAAM,EAAE,aAAY,YAAiB,KAAG,OAAO,CAAC,IAAI,CAqBtE,CAAC;AAEL,yEAAyE;AACzE,wBAAsB,SAAS,CAC7B,SAAS,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EAC3C,EAAE,QAAa,EAAE,MAAM,EAAE,OAAc,EAAE,GAAE,WAAgB,GAC1D,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,kDAAkD;AAClD,wBAAsB,KAAK,CACzB,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACrC,EAAE,QAAa,EAAE,OAAO,EAAE,MAAM,EAAE,OAAc,EAAE,GAAE,YAAiB,GACpE,OAAO,CAAC,IAAI,CAAC,CAyBf;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAClD,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,EAAE,MAAM,EAAE,OAAc,EAAE,GAAE,IAAI,CAAC,WAAW,EAAE,UAAU,CAAM,GAC7D,OAAO,CAAC,CAAC,CAAC,CA6BZ;AAED,wCAAwC;AACxC,eAAO,MAAM,QAAQ,QAAO,OAAO,CAAC,IAAI,CAGpC,CAAC"}
1
+ {"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../src/wait.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,6CAA6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA0ED,eAAO,MAAM,KAAK,GAAI,WAAM,EAAE,UAAS,YAAiB,KAAG,OAAO,CAAC,IAAI,CAGtE,CAAC;AAEF,eAAO,MAAM,QAAQ,QAAO,OAAO,CAAC,IAAI,CAAsD,CAAC;AAE/F,wBAAsB,SAAS,CAC7B,SAAS,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EAC3C,EAAE,QAAa,EAAE,MAAM,EAAE,OAAc,EAAE,GAAE,WAAgB,GAC1D,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACrC,EAAE,QAAa,EAAE,OAAO,EAAE,MAAM,EAAE,OAAc,EAAE,GAAE,iBAAsB,GACzE,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAClD,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,EAAE,MAAM,EAAE,OAAc,EAAE,GAAE,IAAI,CAAC,WAAW,EAAE,UAAU,CAAM,GAC7D,OAAO,CAAC,CAAC,CAAC,CA8BZ"}
package/dist/wait.js CHANGED
@@ -1,8 +1,13 @@
1
1
  import { AssayTimeoutError as e } from "./errors.js";
2
+ import { abortError as t } from "@vielzeug/arsenal";
2
3
  //#region src/wait.ts
3
- var t = (e) => e.reason ?? new DOMException("The operation was aborted.", "AbortError"), n = (e = 0, { signal: n } = {}) => new Promise((r, i) => {
4
+ var n = Symbol("assay.deadline"), r = 2147483647, i = () => performance.now(), a = (e, t, n) => {
5
+ if (!Number.isFinite(t) || t > r || (n ? t < 0 : t <= 0)) throw RangeError(`${e} must be a ${n ? "non-negative" : "positive"} finite number no greater than ${r}`);
6
+ }, o = (e, t) => {
7
+ a("interval", e, !1), a("timeout", t, !0);
8
+ }, s = (e, n) => new Promise((r, i) => {
4
9
  if (n?.aborted) {
5
- i(t(n));
10
+ i(t(n, "The operation was aborted."));
6
11
  return;
7
12
  }
8
13
  let a = setTimeout(o, e);
@@ -10,58 +15,86 @@ var t = (e) => e.reason ?? new DOMException("The operation was aborted.", "Abort
10
15
  n?.removeEventListener("abort", s), r();
11
16
  }
12
17
  function s() {
13
- clearTimeout(a), i(t(n));
18
+ clearTimeout(a), i(t(n, "The operation was aborted."));
14
19
  }
15
20
  n?.addEventListener("abort", s, { once: !0 });
16
- });
17
- async function r(r, { interval: i = 50, signal: a, timeout: o = 1e3 } = {}) {
18
- let s = Date.now() + o;
21
+ }), c = (e, r, a) => new Promise((o, s) => {
22
+ if (a?.aborted) {
23
+ s(t(a, "The operation was aborted."));
24
+ return;
25
+ }
26
+ let c = !1, l = () => !c && (c = !0, clearTimeout(f), a?.removeEventListener("abort", p), !0), u = (e) => {
27
+ l() && o(e);
28
+ }, d = (e) => {
29
+ l() && s(e);
30
+ }, f = setTimeout(() => d(n), Math.max(0, r - i())), p = () => d(t(a, "The operation was aborted."));
31
+ a?.addEventListener("abort", p, { once: !0 });
32
+ try {
33
+ Promise.resolve(e()).then(u, d);
34
+ } catch (e) {
35
+ d(e);
36
+ }
37
+ }), l = (e = 0, t = {}) => (a("ms", e, !0), s(e, t.signal)), u = () => new Promise((e) => queueMicrotask(e));
38
+ async function d(r, { interval: a = 50, signal: l, timeout: u = 1e3 } = {}) {
39
+ o(a, u);
40
+ let d = i() + u, f = !0;
19
41
  for (;;) {
20
- if (a?.aborted) throw t(a);
21
- if (await r()) return;
22
- if (Date.now() >= s) break;
23
- await n(i, { signal: a });
42
+ if (l?.aborted) throw t(l, "The operation was aborted.");
43
+ if (!f && i() >= d) throw new e(`waitUntil timed out after ${u}ms`);
44
+ f = !1;
45
+ let o;
46
+ try {
47
+ o = await c(r, d, l);
48
+ } catch (t) {
49
+ throw t === n ? new e(`waitUntil timed out after ${u}ms`) : t;
50
+ }
51
+ if (o) return;
52
+ let p = d - i();
53
+ if (p <= 0) throw new e(`waitUntil timed out after ${u}ms`);
54
+ await s(Math.min(a, p), l);
24
55
  }
25
- throw new e(`waitUntil timed out after ${o}ms`);
26
56
  }
27
- async function i(r, { interval: i = 50, message: a, signal: o, timeout: s = 1e3 } = {}) {
28
- let c = Date.now() + s, l;
57
+ async function f(r, { interval: a = 50, message: l, signal: u, timeout: d = 1e3 } = {}) {
58
+ o(a, d);
59
+ let f = i() + d, p = !0, m;
29
60
  for (;;) {
30
- if (o?.aborted) throw t(o);
61
+ if (u?.aborted) throw t(u, "The operation was aborted.");
62
+ if (!p && i() >= f) break;
63
+ p = !1;
31
64
  try {
32
- await r();
65
+ await c(r, f, u);
33
66
  return;
34
67
  } catch (e) {
35
- l = e;
68
+ if (u?.aborted) throw t(u, "The operation was aborted.");
69
+ if (e === n) break;
70
+ m = e;
36
71
  }
37
- if (Date.now() >= c) break;
38
- await n(i, { signal: o });
72
+ let e = f - i();
73
+ if (e <= 0) break;
74
+ await s(Math.min(a, e), u);
39
75
  }
40
- let u = `retry timed out after ${s}ms`, d = a ? `${a} (${u})` : u, f = l instanceof Error ? l.message : l == null ? "" : String(l);
41
- throw new e(f ? `${d}\n${f}` : d, { cause: l });
76
+ let h = `eventually timed out after ${d}ms`, g = l ? `${l} (${h})` : h, _ = m instanceof Error ? m.message : m == null ? "" : String(m);
77
+ throw new e(_ ? `${g}\n${_}` : g, { cause: m });
42
78
  }
43
- function a(n, r, { signal: i, timeout: a = 1e3 } = {}) {
44
- return new Promise((o, s) => {
79
+ function p(n, r, { signal: i, timeout: o = 1e3 } = {}) {
80
+ return a("timeout", o, !0), new Promise((a, s) => {
45
81
  if (i?.aborted) {
46
- s(t(i));
82
+ s(t(i, "The operation was aborted."));
47
83
  return;
48
84
  }
49
85
  let c = () => {
50
86
  clearTimeout(u), i?.removeEventListener("abort", d), n.removeEventListener(r, l);
51
87
  }, l = (e) => {
52
- c(), o(e);
88
+ c(), a(e);
53
89
  }, u = setTimeout(() => {
54
- c(), s(new e(`waitForEvent: "${r}" timed out after ${a}ms`));
55
- }, a), d = () => {
56
- c(), s(t(i));
90
+ c(), s(new e(`waitForEvent: "${r}" timed out after ${o}ms`));
91
+ }, o), d = () => {
92
+ c(), s(t(i, "The operation was aborted."));
57
93
  };
58
94
  n.addEventListener(r, l, { once: !0 }), i?.addEventListener("abort", d, { once: !0 });
59
95
  });
60
96
  }
61
- var o = () => new Promise((e) => {
62
- queueMicrotask(e);
63
- });
64
97
  //#endregion
65
- export { n as delay, o as nextTick, i as retry, a as waitForEvent, r as waitUntil };
98
+ export { l as delay, f as eventually, u as nextTick, p as waitForEvent, d as waitUntil };
66
99
 
67
100
  //# sourceMappingURL=wait.js.map
package/dist/wait.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"wait.js","names":[],"sources":["../src/wait.ts"],"sourcesContent":["import { AssayTimeoutError } from './errors';\n\nexport interface WaitOptions {\n /** Polling interval in ms (default: 50). */\n interval?: number;\n /** Cancel the pending wait. */\n signal?: AbortSignal;\n /** Maximum wait time in ms (default: 1000). */\n timeout?: number;\n}\n\nexport interface RetryOptions extends WaitOptions {\n /** Context included in the timeout error. */\n message?: string;\n}\n\nexport interface DelayOptions {\n /** Cancel the pending delay. */\n signal?: AbortSignal;\n}\n\nconst abortReason = (signal: AbortSignal): unknown =>\n signal.reason ?? new DOMException('The operation was aborted.', 'AbortError');\n\n/**\n * Resolve after `ms` milliseconds. Rejects with the abort reason when cancelled.\n */\nexport const delay = (ms = 0, { signal }: DelayOptions = {}): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortReason(signal));\n\n return;\n }\n\n const timer = setTimeout(done, ms);\n\n function done(): void {\n signal?.removeEventListener('abort', abort);\n resolve();\n }\n\n function abort(): void {\n clearTimeout(timer);\n reject(abortReason(signal!));\n }\n\n signal?.addEventListener('abort', abort, { once: true });\n });\n\n/** Poll until a synchronous or asynchronous predicate returns `true`. */\nexport async function waitUntil(\n predicate: () => boolean | Promise<boolean>,\n { interval = 50, signal, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n\n for (;;) {\n if (signal?.aborted) throw abortReason(signal);\n\n if (await predicate()) return;\n\n if (Date.now() >= deadline) break;\n\n await delay(interval, { signal });\n }\n\n throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n}\n\n/** Retry an assertion until it stops throwing. */\nexport async function retry(\n assertion: () => void | Promise<void>,\n { interval = 50, message, signal, timeout = 1000 }: RetryOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n let lastError: unknown;\n\n for (;;) {\n if (signal?.aborted) throw abortReason(signal);\n\n try {\n await assertion();\n\n return;\n } catch (error) {\n lastError = error;\n }\n\n if (Date.now() >= deadline) break;\n\n await delay(interval, { signal });\n }\n\n const timing = `retry timed out after ${timeout}ms`;\n const context = message ? `${message} (${timing})` : timing;\n const detail = lastError instanceof Error ? lastError.message : lastError == null ? '' : String(lastError);\n\n throw new AssayTimeoutError(detail ? `${context}\\n${detail}` : context, { cause: lastError });\n}\n\n/** Resolve with the next event of `type`, or reject when it times out or is aborted. */\nexport function waitForEvent<T extends Event = Event>(\n target: EventTarget,\n type: string,\n { signal, timeout = 1000 }: Omit<WaitOptions, 'interval'> = {},\n): Promise<T> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortReason(signal));\n\n return;\n }\n\n const dispose = () => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n target.removeEventListener(type, onEvent);\n };\n const onEvent = (e: Event): void => {\n dispose();\n resolve(e as T);\n };\n const timer = setTimeout(() => {\n dispose();\n reject(new AssayTimeoutError(`waitForEvent: \"${type}\" timed out after ${timeout}ms`));\n }, timeout);\n const onAbort = () => {\n dispose();\n reject(abortReason(signal!));\n };\n\n target.addEventListener(type, onEvent, { once: true });\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/** Resolve after one microtask tick. */\nexport const nextTick = (): Promise<void> =>\n new Promise((resolve) => {\n queueMicrotask(resolve);\n });\n"],"mappings":";;AAqBA,IAAM,KAAe,MACnB,EAAO,UAAU,IAAI,aAAa,8BAA8B,YAAY,GAKjE,KAAS,IAAK,GAAG,EAAE,cAAyB,CAAC,MACxD,IAAI,SAAS,GAAS,MAAW;CAC/B,IAAI,GAAQ,SAAS;EACnB,EAAO,EAAY,CAAM,CAAC;EAE1B;CACF;CAEA,IAAM,IAAQ,WAAW,GAAM,CAAE;CAEjC,SAAS,IAAa;EAEpB,AADA,GAAQ,oBAAoB,SAAS,CAAK,GAC1C,EAAQ;CACV;CAEA,SAAS,IAAc;EAErB,AADA,aAAa,CAAK,GAClB,EAAO,EAAY,CAAO,CAAC;CAC7B;CAEA,GAAQ,iBAAiB,SAAS,GAAO,EAAE,MAAM,GAAK,CAAC;AACzD,CAAC;AAGH,eAAsB,EACpB,GACA,EAAE,cAAW,IAAI,WAAQ,aAAU,QAAsB,CAAC,GAC3C;CACf,IAAM,IAAW,KAAK,IAAI,IAAI;CAE9B,SAAS;EACP,IAAI,GAAQ,SAAS,MAAM,EAAY,CAAM;EAE7C,IAAI,MAAM,EAAU,GAAG;EAEvB,IAAI,KAAK,IAAI,KAAK,GAAU;EAE5B,MAAM,EAAM,GAAU,EAAE,UAAO,CAAC;CAClC;CAEA,MAAM,IAAI,EAAkB,6BAA6B,EAAQ,GAAG;AACtE;AAGA,eAAsB,EACpB,GACA,EAAE,cAAW,IAAI,YAAS,WAAQ,aAAU,QAAuB,CAAC,GACrD;CACf,IAAM,IAAW,KAAK,IAAI,IAAI,GAC1B;CAEJ,SAAS;EACP,IAAI,GAAQ,SAAS,MAAM,EAAY,CAAM;EAE7C,IAAI;GACF,MAAM,EAAU;GAEhB;EACF,SAAS,GAAO;GACd,IAAY;EACd;EAEA,IAAI,KAAK,IAAI,KAAK,GAAU;EAE5B,MAAM,EAAM,GAAU,EAAE,UAAO,CAAC;CAClC;CAEA,IAAM,IAAS,yBAAyB,EAAQ,KAC1C,IAAU,IAAU,GAAG,EAAQ,IAAI,EAAO,KAAK,GAC/C,IAAS,aAAqB,QAAQ,EAAU,UAAU,KAAa,OAAO,KAAK,OAAO,CAAS;CAEzG,MAAM,IAAI,EAAkB,IAAS,GAAG,EAAQ,IAAI,MAAW,GAAS,EAAE,OAAO,EAAU,CAAC;AAC9F;AAGA,SAAgB,EACd,GACA,GACA,EAAE,WAAQ,aAAU,QAAwC,CAAC,GACjD;CACZ,OAAO,IAAI,SAAS,GAAS,MAAW;EACtC,IAAI,GAAQ,SAAS;GACnB,EAAO,EAAY,CAAM,CAAC;GAE1B;EACF;EAEA,IAAM,UAAgB;GAGpB,AAFA,aAAa,CAAK,GAClB,GAAQ,oBAAoB,SAAS,CAAO,GAC5C,EAAO,oBAAoB,GAAM,CAAO;EAC1C,GACM,KAAW,MAAmB;GAElC,AADA,EAAQ,GACR,EAAQ,CAAM;EAChB,GACM,IAAQ,iBAAiB;GAE7B,AADA,EAAQ,GACR,EAAO,IAAI,EAAkB,kBAAkB,EAAK,oBAAoB,EAAQ,GAAG,CAAC;EACtF,GAAG,CAAO,GACJ,UAAgB;GAEpB,AADA,EAAQ,GACR,EAAO,EAAY,CAAO,CAAC;EAC7B;EAGA,AADA,EAAO,iBAAiB,GAAM,GAAS,EAAE,MAAM,GAAK,CAAC,GACrD,GAAQ,iBAAiB,SAAS,GAAS,EAAE,MAAM,GAAK,CAAC;CAC3D,CAAC;AACH;AAGA,IAAa,UACX,IAAI,SAAS,MAAY;CACvB,eAAe,CAAO;AACxB,CAAC"}
1
+ {"version":3,"file":"wait.js","names":[],"sources":["../src/wait.ts"],"sourcesContent":["import { abortError } from '@vielzeug/arsenal';\n\nimport { AssayTimeoutError } from './errors';\n\nexport interface DelayOptions {\n signal?: AbortSignal;\n}\n\nexport interface WaitOptions {\n /** Polling interval in ms (default: 50). */\n interval?: number;\n /** Cancel the pending wait. */\n signal?: AbortSignal;\n /** Maximum wait time in ms (default: 1000). */\n timeout?: number;\n}\n\nexport interface EventuallyOptions extends WaitOptions {\n /** Context included in the timeout error. */\n message?: string;\n}\n\nconst DEADLINE = Symbol('assay.deadline');\nconst MAX_TIMER_MS = 2_147_483_647;\nconst now = (): number => performance.now();\n\nconst validateDuration = (name: string, value: number, allowZero: boolean): void => {\n if (!Number.isFinite(value) || value > MAX_TIMER_MS || (allowZero ? value < 0 : value <= 0)) {\n throw new RangeError(\n `${name} must be a ${allowZero ? 'non-negative' : 'positive'} finite number no greater than ${MAX_TIMER_MS}`,\n );\n }\n};\n\nconst validateWait = (interval: number, timeout: number): void => {\n validateDuration('interval', interval, false);\n validateDuration('timeout', timeout, true);\n};\n\nconst sleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal, 'The operation was aborted.'));\n return;\n }\n\n const timer = setTimeout(done, ms);\n\n function done(): void {\n signal?.removeEventListener('abort', abort);\n resolve();\n }\n\n function abort(): void {\n clearTimeout(timer);\n reject(abortError(signal, 'The operation was aborted.'));\n }\n\n signal?.addEventListener('abort', abort, { once: true });\n });\n\nconst runBounded = <T>(run: () => T | Promise<T>, deadline: number, signal?: AbortSignal): Promise<T> =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal, 'The operation was aborted.'));\n return;\n }\n\n let settled = false;\n const cleanup = (): boolean => {\n if (settled) return false;\n settled = true;\n clearTimeout(timer);\n signal?.removeEventListener('abort', abort);\n return true;\n };\n const resolveOnce = (value: T): void => {\n if (cleanup()) resolve(value);\n };\n const rejectOnce = (error: unknown): void => {\n if (cleanup()) reject(error);\n };\n const timer = setTimeout(() => rejectOnce(DEADLINE), Math.max(0, deadline - now()));\n const abort = () => rejectOnce(abortError(signal, 'The operation was aborted.'));\n\n signal?.addEventListener('abort', abort, { once: true });\n\n try {\n Promise.resolve(run()).then(resolveOnce, rejectOnce);\n } catch (error) {\n rejectOnce(error);\n }\n });\n\nexport const delay = (ms = 0, options: DelayOptions = {}): Promise<void> => {\n validateDuration('ms', ms, true);\n return sleep(ms, options.signal);\n};\n\nexport const nextTick = (): Promise<void> => new Promise((resolve) => queueMicrotask(resolve));\n\nexport async function waitUntil(\n predicate: () => boolean | Promise<boolean>,\n { interval = 50, signal, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n validateWait(interval, timeout);\n const deadline = now() + timeout;\n let first = true;\n\n for (;;) {\n if (signal?.aborted) throw abortError(signal, 'The operation was aborted.');\n if (!first && now() >= deadline) throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n first = false;\n\n let matched: boolean;\n try {\n matched = await runBounded(predicate, deadline, signal);\n } catch (error) {\n if (error === DEADLINE) throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n throw error;\n }\n if (matched) return;\n\n const remaining = deadline - now();\n if (remaining <= 0) throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n await sleep(Math.min(interval, remaining), signal);\n }\n}\n\n/**\n * Retry an assertion until it stops throwing or the timeout elapses.\n *\n * Polls the assertion on a fixed interval and preserves the last failure as the\n * timeout cause. Pending asynchronous assertions remain bounded by the same\n * deadline. Use `waitUntil()` for boolean conditions, `delay()` for explicit\n * timer dependencies, and `nextTick()` for one queued microtask boundary.\n *\n * @example\n * await eventually(() => expect(region.textContent).toBe('Saved'));\n */\nexport async function eventually(\n assertion: () => void | Promise<void>,\n { interval = 50, message, signal, timeout = 1000 }: EventuallyOptions = {},\n): Promise<void> {\n validateWait(interval, timeout);\n const deadline = now() + timeout;\n let first = true;\n let lastError: unknown;\n\n for (;;) {\n if (signal?.aborted) throw abortError(signal, 'The operation was aborted.');\n if (!first && now() >= deadline) break;\n first = false;\n\n try {\n await runBounded(assertion, deadline, signal);\n return;\n } catch (error) {\n if (signal?.aborted) throw abortError(signal, 'The operation was aborted.');\n if (error === DEADLINE) break;\n lastError = error;\n }\n\n const remaining = deadline - now();\n if (remaining <= 0) break;\n await sleep(Math.min(interval, remaining), signal);\n }\n\n const timing = `eventually timed out after ${timeout}ms`;\n const context = message ? `${message} (${timing})` : timing;\n const detail = lastError instanceof Error ? lastError.message : lastError == null ? '' : String(lastError);\n\n throw new AssayTimeoutError(detail ? `${context}\\n${detail}` : context, { cause: lastError });\n}\n\n/** Resolve with the next event of `type`, or reject when it times out or is aborted. */\nexport function waitForEvent<T extends Event = Event>(\n target: EventTarget,\n type: string,\n { signal, timeout = 1000 }: Omit<WaitOptions, 'interval'> = {},\n): Promise<T> {\n validateDuration('timeout', timeout, true);\n\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal, 'The operation was aborted.'));\n return;\n }\n\n const dispose = () => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n target.removeEventListener(type, onEvent);\n };\n const onEvent = (event: Event): void => {\n dispose();\n resolve(event as T);\n };\n const timer = setTimeout(() => {\n dispose();\n reject(new AssayTimeoutError(`waitForEvent: \"${type}\" timed out after ${timeout}ms`));\n }, timeout);\n const onAbort = () => {\n dispose();\n reject(abortError(signal, 'The operation was aborted.'));\n };\n\n target.addEventListener(type, onEvent, { once: true });\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/assay",
3
- "version": "2.2.0",
3
+ "version": "3.0.0",
4
4
  "description": "Framework-agnostic DOM testing primitives — scoped queries, event dispatch, and async waiting",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,20 +30,24 @@
30
30
  "registry": "https://registry.npmjs.org/"
31
31
  },
32
32
  "scripts": {
33
- "build": "vite build && pnpm run build:bundle && pnpm run build:types",
33
+ "build": "vite build && pnpm --silent run build:bundle && pnpm --silent run build:types && pnpm --silent run verify:exports",
34
34
  "build:bundle": "vite build --config vite.bundle.config.ts",
35
35
  "build:types": "tsc -p tsconfig.declarations.json",
36
36
  "fix": "biome check --write src",
37
37
  "lint": "biome ci src",
38
- "prepublishOnly": "pnpm run build",
38
+ "prepublishOnly": "pnpm --silent run build",
39
39
  "preview": "vite preview",
40
- "test": "vitest"
40
+ "test": "vitest",
41
+ "verify:exports": "node verify-exports.mjs"
42
+ },
43
+ "dependencies": {
44
+ "@vielzeug/arsenal": "3.0.0"
41
45
  },
42
46
  "devDependencies": {
43
- "@types/node": "^26.2.0",
47
+ "@types/node": "^26.4.1",
44
48
  "jsdom": "^30.0.1",
45
49
  "typescript": "^6.0.3",
46
- "vite": "^8.2.1",
47
- "vitest": "^4.1.10"
50
+ "vite": "^8.2.2",
51
+ "vitest": "^5.0.0"
48
52
  }
49
53
  }