@vielzeug/assay 1.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.
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # @vielzeug/assay
2
+
3
+ > Framework-agnostic DOM testing primitives — scoped queries, synchronous event dispatchers, and async wait helpers, with zero dependency on any particular UI library.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@vielzeug/assay)](https://www.npmjs.com/package/@vielzeug/assay) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
7
+ <details>
8
+ <summary>Quick Reference</summary>
9
+
10
+ **Package:** `@vielzeug/assay` &nbsp;·&nbsp; **Category:** Testing
11
+
12
+ **Key exports:** `within`, `queryByText`, `queryAllByText`, `fire`, `createPointerEvent`, `waitFor`, `waitForEvent`, `AssayError`, `AssayTimeoutError`
13
+
14
+ **When to use:** Writing DOM-level tests for any custom-element or vanilla-DOM code — scoped element queries, low-level synchronous event dispatch, and deterministic async waiting — without pulling in a full testing-library dependency or coupling to a specific component framework.
15
+
16
+ **Related:** [@vielzeug/ore](https://vielzeug.dev/ore/) · [@vielzeug/refine](https://vielzeug.dev/refine/)
17
+
18
+ </details>
19
+
20
+ `@vielzeug/assay` is part of Vielzeug and ships as a zero-dependency TypeScript package with ESM+CJS output.
21
+
22
+ ## Installation
23
+
24
+ ```sh
25
+ pnpm add -D @vielzeug/assay
26
+ npm install -D @vielzeug/assay
27
+ yarn add -D @vielzeug/assay
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```ts
33
+ import { fire, waitFor, within } from '@vielzeug/assay';
34
+
35
+ const panel = document.querySelector('.panel')!;
36
+ const { query, queryByText } = within(panel);
37
+
38
+ fire.click(query('button.submit')!);
39
+
40
+ await waitFor(() => queryByText('Saved') !== null);
41
+ ```
42
+
43
+ ## Documentation
44
+
45
+ - [Overview](https://vielzeug.dev/assay/)
46
+ - [Usage Guide](https://vielzeug.dev/assay/usage)
47
+ - [API Reference](https://vielzeug.dev/assay/api)
48
+
49
+ ## License
50
+
51
+ MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
package/dist/_dev.cjs ADDED
@@ -0,0 +1,2 @@
1
+ var e=!globalThis.__ASSAY_PROD__,t=new Set;function n(n,r){!e||t.has(n)||(t.add(n),console.warn(`[@vielzeug/assay] ${r}`))}exports.warnOnce=n;
2
+ //# sourceMappingURL=_dev.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __ASSAY_PROD__?: boolean }).__ASSAY_PROD__;\n\nconst warnedKeys = new Set<string>();\n\n/**\n * @internal Warn at most once per `key` for the lifetime of the module — used for\n * environment-capability fallbacks (e.g. missing `PointerEvent`/`TouchEvent`), which would\n * otherwise log once per dispatched event in a suite that fires hundreds of them.\n */\nexport function warnOnce(key: string, msg: string): void {\n if (!isDev || warnedKeys.has(key)) return;\n\n warnedKeys.add(key);\n console.warn(`[@vielzeug/assay] ${msg}`);\n}\n\n/** @internal Test-only — clears \"warned once\" state so fallback-warning tests can run in any order. */\nexport function _resetWarnings(): void {\n warnedKeys.clear();\n}\n"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA4C,eAEtD,EAAa,IAAI,IAOvB,SAAgB,EAAS,EAAa,EAAmB,CACnD,CAAC,GAAS,EAAW,IAAI,CAAG,IAEhC,EAAW,IAAI,CAAG,EAClB,QAAQ,KAAK,qBAAqB,GAAK,EACzC"}
package/dist/_dev.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=_dev.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dev.d.ts","sourceRoot":"","sources":["../src/_dev.ts"],"names":[],"mappings":""}
package/dist/_dev.js ADDED
@@ -0,0 +1,9 @@
1
+ //#region src/_dev.ts
2
+ var e = !globalThis.__ASSAY_PROD__, t = /* @__PURE__ */ new Set();
3
+ function n(n, r) {
4
+ !e || t.has(n) || (t.add(n), console.warn(`[@vielzeug/assay] ${r}`));
5
+ }
6
+ //#endregion
7
+ export { n as warnOnce };
8
+
9
+ //# sourceMappingURL=_dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __ASSAY_PROD__?: boolean }).__ASSAY_PROD__;\n\nconst warnedKeys = new Set<string>();\n\n/**\n * @internal Warn at most once per `key` for the lifetime of the module — used for\n * environment-capability fallbacks (e.g. missing `PointerEvent`/`TouchEvent`), which would\n * otherwise log once per dispatched event in a suite that fires hundreds of them.\n */\nexport function warnOnce(key: string, msg: string): void {\n if (!isDev || warnedKeys.has(key)) return;\n\n warnedKeys.add(key);\n console.warn(`[@vielzeug/assay] ${msg}`);\n}\n\n/** @internal Test-only — clears \"warned once\" state so fallback-warning tests can run in any order. */\nexport function _resetWarnings(): void {\n warnedKeys.clear();\n}\n"],"mappings":";AAAA,IAAM,IAAQ,CAAE,WAA4C,gBAEtD,oBAAa,IAAI,IAAY;AAOnC,SAAgB,EAAS,GAAa,GAAmB;CACnD,CAAC,KAAS,EAAW,IAAI,CAAG,MAEhC,EAAW,IAAI,CAAG,GAClB,QAAQ,KAAK,qBAAqB,GAAK;AACzC"}
package/dist/assay.cjs ADDED
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{},n=!globalThis.__ASSAY_PROD__,r=new Set;function i(e,t){!n||r.has(e)||(r.add(e),console.warn(`[@vielzeug/assay] ${t}`))}var a=(e,t={})=>typeof PointerEvent<`u`?new PointerEvent(e,t):(i(`pointer-fallback`,`PointerEvent is unavailable in this environment — falling back to MouseEvent. pointerId/pointerType/isPrimary and similar pointer-only fields will be missing from every dispatched event.`),new MouseEvent(e,t)),o={blur:(e,t)=>e.dispatchEvent(new FocusEvent(`blur`,{bubbles:!0,...t})),change:(e,t)=>e.dispatchEvent(new Event(`change`,{bubbles:!0,...t})),click:(e,t)=>e.dispatchEvent(new MouseEvent(`click`,{bubbles:!0,cancelable:!0,...t})),custom:(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!0,composed:!1,...n})),event:(e,t)=>e.dispatchEvent(t),focus:(e,t,n)=>e.dispatchEvent(new FocusEvent(`focus`,{bubbles:!0,...n})),input:(e,t)=>e.dispatchEvent(new Event(`input`,{bubbles:!0,...t})),keyboard:(e,t,n)=>e.dispatchEvent(new KeyboardEvent(t,{bubbles:!0,cancelable:!0,...n})),keyDown:(e,t)=>e.dispatchEvent(new KeyboardEvent(`keydown`,{bubbles:!0,cancelable:!0,...t})),keyUp:(e,t)=>e.dispatchEvent(new KeyboardEvent(`keyup`,{bubbles:!0,cancelable:!0,...t})),mouse:(e,t,n)=>e.dispatchEvent(new MouseEvent(t,{bubbles:!0,cancelable:!0,...n})),pointerCancel:(e,t)=>e.dispatchEvent(a(`pointercancel`,{bubbles:!0,cancelable:!1,...t})),pointerDown:(e,t)=>e.dispatchEvent(a(`pointerdown`,{bubbles:!0,cancelable:!0,...t})),pointerEnter:(e,t)=>e.dispatchEvent(a(`pointerenter`,{bubbles:!1,...t})),pointerLeave:(e,t)=>e.dispatchEvent(a(`pointerleave`,{bubbles:!1,...t})),pointerMove:(e,t)=>e.dispatchEvent(a(`pointermove`,{bubbles:!0,cancelable:!0,...t})),pointerUp:(e,t)=>e.dispatchEvent(a(`pointerup`,{bubbles:!0,cancelable:!0,...t})),submit:(e,t)=>e.dispatchEvent(new Event(`submit`,{bubbles:!0,cancelable:!0,...t})),touch:(e,t,n)=>typeof TouchEvent<`u`?e.dispatchEvent(new TouchEvent(t,{bubbles:!0,cancelable:!0,...n})):(i(`touch-fallback`,"TouchEvent is unavailable in this environment — falling back to CustomEvent. The dispatched event will have no `touches`/`changedTouches` data."),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!0,...n})))};function s(e,t){return e.querySelector(t)}function c(e,t){return Array.from(e.querySelectorAll(t))}function l(e,t){return e.querySelector(`[data-testid="${t}"]`)}function u(e,t){return Array.from(e.querySelectorAll(`[data-testid="${t}"]`))}function d(e,t,n){for(let r of e.querySelectorAll(n))if(r.textContent?.trim()===t)return r;return null}function f(e,t,n){return Array.from(e.querySelectorAll(n)).filter(e=>e.textContent?.trim()===t)}function p(e,t){return e.shadowRoot?.querySelector(t)??null}function m(e,t){return Array.from(e.shadowRoot?.querySelectorAll(t)??[])}function h(e,t){return p(e,`[part="${t}"]`)}function g(e,t){let n=t?`[slot="${t}"]`:`:not([slot])`;return Array.from(e.querySelectorAll(`:scope > ${n}`))}function _(e){return{query:t=>s(e,t),queryAll:t=>c(e,t),queryAllByTestId:t=>u(e,t),queryAllByText:(t,n=`*`)=>f(e,t,n),queryByTestId:t=>l(e,t),queryByText:(t,n=`*`)=>d(e,t,n)}}async function v(e,{interval:n=50,message:r,timeout:i=1e3}={}){let a=Date.now()+i,o;for(;;){try{let t=await e();if(t===void 0||t)return}catch(e){o=e}if(Date.now()>=a)break;await x(n)}let s=`waitFor timed out after ${i}ms`,c=r?`${r} (${s})`:s,l=o instanceof Error?o.message:o==null?void 0:String(o);throw new t(l?`${c}\n${l}`:c,{cause:o})}function y(e,n,r=1e3){return new Promise((i,a)=>{let o=e=>{clearTimeout(s),i(e)},s=setTimeout(()=>{e.removeEventListener(n,o),a(new t(`waitForEvent: "${n}" timed out after ${r}ms`))},r);e.addEventListener(n,o,{once:!0})})}var b=()=>new Promise(e=>{queueMicrotask(e)}),x=(e=0)=>new Promise(t=>{setTimeout(t,e)});exports.AssayError=e,exports.AssayTimeoutError=t,exports.createPointerEvent=a,exports.fire=o,exports.getSlotted=g,exports.nextTick=b,exports.query=s,exports.queryAll=c,exports.queryAllByTestId=u,exports.queryAllByText=f,exports.queryAllInShadow=m,exports.queryByTestId=l,exports.queryByText=d,exports.queryInShadow=p,exports.queryPart=h,exports.wait=x,exports.waitFor=v,exports.waitForEvent=y,exports.within=_;
2
+ //# sourceMappingURL=assay.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assay.cjs","names":[],"sources":["../src/errors.ts","../src/_dev.ts","../src/events.ts","../src/query.ts","../src/wait.ts"],"sourcesContent":["/** Base class for all Assay errors. Use `instanceof AssayError` to catch any Assay-originated error. */\nexport class AssayError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is AssayError {\n return err instanceof AssayError;\n }\n}\n\n/** Thrown by `waitFor`/`waitForEvent` when a condition is not met within the timeout. */\nexport class AssayTimeoutError extends AssayError {}\n","const isDev = !(globalThis as { __ASSAY_PROD__?: boolean }).__ASSAY_PROD__;\n\nconst warnedKeys = new Set<string>();\n\n/**\n * @internal Warn at most once per `key` for the lifetime of the module — used for\n * environment-capability fallbacks (e.g. missing `PointerEvent`/`TouchEvent`), which would\n * otherwise log once per dispatched event in a suite that fires hundreds of them.\n */\nexport function warnOnce(key: string, msg: string): void {\n if (!isDev || warnedKeys.has(key)) return;\n\n warnedKeys.add(key);\n console.warn(`[@vielzeug/assay] ${msg}`);\n}\n\n/** @internal Test-only — clears \"warned once\" state so fallback-warning tests can run in any order. */\nexport function _resetWarnings(): void {\n warnedKeys.clear();\n}\n","/**\n * Synchronous DOM event dispatchers for test environments.\n */\n\nimport { warnOnce } from './_dev';\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nexport const createPointerEvent = (type: string, init: PointerEventInit = {}): Event => {\n if (typeof PointerEvent !== 'undefined') {\n return new PointerEvent(type, init);\n }\n\n warnOnce(\n 'pointer-fallback',\n 'PointerEvent is unavailable in this environment — falling back to MouseEvent. ' +\n 'pointerId/pointerType/isPrimary and similar pointer-only fields will be missing from every dispatched event.',\n );\n\n return new MouseEvent(type, init);\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Fire low-level DOM events synchronously. Every method dispatches an appropriately-typed\n * `Event` subclass with sensible defaults and returns `dispatchEvent`'s own boolean result\n * (`false` when a listener called `preventDefault()`) — the same contract for all of them,\n * with no exceptions.\n *\n * @example\n * fire.click(button);\n * fire.keyDown(input, { key: 'Enter' });\n * fire.custom(el, 'value-change', { detail: 42 });\n */\nexport const fire = {\n blur: (el: Element, opts?: FocusEventInit) => el.dispatchEvent(new FocusEvent('blur', { bubbles: true, ...opts })),\n change: (el: Element, opts?: EventInit) => el.dispatchEvent(new Event('change', { bubbles: true, ...opts })),\n click: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a CustomEvent. Pass `{ detail: ... }` for event detail. */\n custom: (el: Element, name: string, opts?: CustomEventInit) =>\n el.dispatchEvent(new CustomEvent(name, { bubbles: true, cancelable: true, composed: false, ...opts })),\n /** Dispatch any pre-built Event instance directly. */\n event: (el: Element, event: Event) => el.dispatchEvent(event),\n focus: (el: Element, _name?: string, opts?: FocusEventInit) =>\n el.dispatchEvent(new FocusEvent('focus', { bubbles: true, ...opts })),\n input: (el: Element, opts?: EventInit) => el.dispatchEvent(new Event('input', { bubbles: true, ...opts })),\n /** Fire a KeyboardEvent with a given type (keydown, keyup, keypress). */\n keyboard: (el: Element, type: string, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent(type, { bubbles: true, cancelable: true, ...opts })),\n keyDown: (el: Element, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...opts })),\n keyUp: (el: Element, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a MouseEvent with a given type (click, mousedown, etc.). */\n mouse: (el: Element, type: string, opts?: MouseEventInit) =>\n el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, ...opts })),\n pointerCancel: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointercancel', { bubbles: true, cancelable: false, ...opts })),\n pointerDown: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerdown', { bubbles: true, cancelable: true, ...opts })),\n pointerEnter: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerenter', { bubbles: false, ...opts })),\n pointerLeave: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerleave', { bubbles: false, ...opts })),\n pointerMove: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointermove', { bubbles: true, cancelable: true, ...opts })),\n pointerUp: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerup', { bubbles: true, cancelable: true, ...opts })),\n submit: (el: Element, opts?: EventInit) =>\n el.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a touch event; falls back to CustomEvent in environments without TouchEvent. */\n touch: (el: Element, type: string, opts?: EventInit) => {\n if (typeof TouchEvent !== 'undefined') {\n return el.dispatchEvent(new TouchEvent(type, { bubbles: true, cancelable: true, ...opts }));\n }\n\n warnOnce(\n 'touch-fallback',\n 'TouchEvent is unavailable in this environment — falling back to CustomEvent. The dispatched event will have no `touches`/`changedTouches` data.',\n );\n\n return el.dispatchEvent(new CustomEvent(type, { bubbles: true, cancelable: true, ...opts }));\n },\n} as const;\n","/**\n * DOM query helpers for test environments.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any DOM element — see {@link within} */\nexport interface QueryScope {\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/** Query a single element within `root`. Equivalent to `root.querySelector(selector)`. */\nexport function query<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E | null {\n return root.querySelector<E>(selector);\n}\n\n/** Query every element within `root` matching `selector`. */\nexport function queryAll<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E[] {\n return Array.from(root.querySelectorAll<E>(selector));\n}\n\n/** Query a single element within `root` by its `data-testid` attribute. */\nexport function queryByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E | null {\n return root.querySelector<E>(`[data-testid=\"${testId}\"]`);\n}\n\n/** Query every element within `root` matching a `data-testid` attribute. */\nexport function queryAllByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E[] {\n return Array.from(root.querySelectorAll<E>(`[data-testid=\"${testId}\"]`));\n}\n\nexport function queryByText<E extends Element = Element>(\n root: Element | ShadowRoot,\n text: string,\n selector: string,\n): 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\nexport function queryAllByText<E extends Element = Element>(\n root: Element | ShadowRoot,\n text: string,\n selector: string,\n): 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?.querySelector<E>(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 Array.from(host.shadowRoot?.querySelectorAll<E>(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 queryInShadow<E>(host, `[part=\"${part}\"]`);\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every slotted child (elements\n * with no `slot` attribute) 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 const selector = slotName ? `[slot=\"${slotName}\"]` : ':not([slot])';\n\n return Array.from(host.querySelectorAll<E>(`:scope > ${selector}`));\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to any element — useful for slotted/light DOM content.\n *\n * @example\n * const panel = fixture.query('.panel')!;\n * const { query } = within(panel);\n * expect(query('.title')?.textContent).toBe('Hello');\n */\nexport function within(element: Element): QueryScope {\n return {\n query: <E extends Element = Element>(selector: string) => query<E>(element, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(element, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(element, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(element, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(element, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(element, text, selector),\n };\n}\n","/**\n * Async waiting utilities for test environments.\n */\n\nimport { AssayTimeoutError } from './errors';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface WaitOptions {\n /** Maximum wait time in ms (default: 1000) */\n timeout?: number;\n /** Polling interval in ms (default: 50) */\n interval?: number;\n /** Message included in timeout error */\n message?: string;\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Poll until a callback returns truthy (or void) without throwing.\n * Supports both boolean conditions and `expect()` assertions.\n *\n * - Returns truthy → success\n * - Returns `undefined` (e.g. bare `expect()` call) → success\n * - Returns falsy value → retry\n * - Throws → retry\n *\n * Always rejects with `AssayTimeoutError` on timeout, regardless of which of the two retry\n * reasons above was last observed — `instanceof AssayTimeoutError` is a reliable way to catch\n * \"the condition never became true\" without caring whether the last attempt returned falsy or\n * threw. The original failure (an assertion's message, a thrown error) is preserved as `.cause`,\n * not merged into a mutated copy of it — `fn`'s thrown errors are caller-owned and never mutated.\n *\n * @example\n * await waitFor(() => query('.status')?.textContent === 'loaded');\n * await waitFor(() => expect(count).toBe(3));\n */\nexport async function waitFor(\n fn: () => unknown,\n { interval = 50, message, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n let lastError: unknown;\n\n // Single loop, one `fn()` call site — always tries once more before checking the deadline,\n // so a condition that becomes true exactly as the timeout elapses still succeeds.\n for (;;) {\n try {\n const result = await fn();\n\n if (result === undefined || result) return;\n } catch (e) {\n lastError = e;\n }\n\n if (Date.now() >= deadline) break;\n\n await wait(interval);\n }\n\n // The default timing summary always appears — a custom `message` adds context in front of it\n // rather than replacing it, so a debugging session never loses \"how long did this actually wait\".\n const timing = `waitFor timed out after ${timeout}ms`;\n const base = message ? `${message} (${timing})` : timing;\n const cause = lastError instanceof Error ? lastError.message : lastError != null ? String(lastError) : undefined;\n\n throw new AssayTimeoutError(cause ? `${base}\\n${cause}` : base, { cause: lastError });\n}\n\n/**\n * Resolve when the target element emits the given event.\n *\n * @example\n * const promise = waitForEvent(el, 'change');\n * fire.click(trigger);\n * const event = await promise;\n */\nexport function waitForEvent<T extends Event = Event>(element: Element, name: string, timeout = 1000): Promise<T> {\n return new Promise((resolve, reject) => {\n const onEvent = (e: Event): void => {\n clearTimeout(timer);\n resolve(e as T);\n };\n const timer = setTimeout(() => {\n element.removeEventListener(name, onEvent);\n reject(new AssayTimeoutError(`waitForEvent: \"${name}\" timed out after ${timeout}ms`));\n }, timeout);\n\n element.addEventListener(name, onEvent, { once: true });\n });\n}\n\n/**\n * Resolves after one microtask tick (`queueMicrotask`). Use to wait for reactivity (signal\n * effects, promise-chain continuations) to settle without moving into the macrotask queue —\n * cheaper and more precise than `wait(0)`, which yields to a full `setTimeout` turn instead.\n */\nexport const nextTick = (): Promise<void> =>\n new Promise((resolve) => {\n queueMicrotask(resolve);\n });\n\n/**\n * Resolves after `ms` milliseconds. Prefer `nextTick()` for reactive updates and `waitFor()`\n * for polling a condition — reach for this only when a real macrotask delay is what the code\n * under test actually depends on (e.g. a debounce timer).\n */\nexport const wait = (ms = 0): Promise<void> =>\n new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var Assay=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var t=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},n=class extends t{},r=!globalThis.__ASSAY_PROD__,i=new Set;function a(e,t){!r||i.has(e)||(i.add(e),console.warn(`[@vielzeug/assay] ${t}`))}var o=(e,t={})=>typeof PointerEvent<`u`?new PointerEvent(e,t):(a(`pointer-fallback`,`PointerEvent is unavailable in this environment — falling back to MouseEvent. pointerId/pointerType/isPrimary and similar pointer-only fields will be missing from every dispatched event.`),new MouseEvent(e,t)),s={blur:(e,t)=>e.dispatchEvent(new FocusEvent(`blur`,{bubbles:!0,...t})),change:(e,t)=>e.dispatchEvent(new Event(`change`,{bubbles:!0,...t})),click:(e,t)=>e.dispatchEvent(new MouseEvent(`click`,{bubbles:!0,cancelable:!0,...t})),custom:(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!0,composed:!1,...n})),event:(e,t)=>e.dispatchEvent(t),focus:(e,t,n)=>e.dispatchEvent(new FocusEvent(`focus`,{bubbles:!0,...n})),input:(e,t)=>e.dispatchEvent(new Event(`input`,{bubbles:!0,...t})),keyboard:(e,t,n)=>e.dispatchEvent(new KeyboardEvent(t,{bubbles:!0,cancelable:!0,...n})),keyDown:(e,t)=>e.dispatchEvent(new KeyboardEvent(`keydown`,{bubbles:!0,cancelable:!0,...t})),keyUp:(e,t)=>e.dispatchEvent(new KeyboardEvent(`keyup`,{bubbles:!0,cancelable:!0,...t})),mouse:(e,t,n)=>e.dispatchEvent(new MouseEvent(t,{bubbles:!0,cancelable:!0,...n})),pointerCancel:(e,t)=>e.dispatchEvent(o(`pointercancel`,{bubbles:!0,cancelable:!1,...t})),pointerDown:(e,t)=>e.dispatchEvent(o(`pointerdown`,{bubbles:!0,cancelable:!0,...t})),pointerEnter:(e,t)=>e.dispatchEvent(o(`pointerenter`,{bubbles:!1,...t})),pointerLeave:(e,t)=>e.dispatchEvent(o(`pointerleave`,{bubbles:!1,...t})),pointerMove:(e,t)=>e.dispatchEvent(o(`pointermove`,{bubbles:!0,cancelable:!0,...t})),pointerUp:(e,t)=>e.dispatchEvent(o(`pointerup`,{bubbles:!0,cancelable:!0,...t})),submit:(e,t)=>e.dispatchEvent(new Event(`submit`,{bubbles:!0,cancelable:!0,...t})),touch:(e,t,n)=>typeof TouchEvent<`u`?e.dispatchEvent(new TouchEvent(t,{bubbles:!0,cancelable:!0,...n})):(a(`touch-fallback`,"TouchEvent is unavailable in this environment — falling back to CustomEvent. The dispatched event will have no `touches`/`changedTouches` data."),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!0,...n})))};function c(e,t){return e.querySelector(t)}function l(e,t){return Array.from(e.querySelectorAll(t))}function u(e,t){return e.querySelector(`[data-testid="${t}"]`)}function d(e,t){return Array.from(e.querySelectorAll(`[data-testid="${t}"]`))}function f(e,t,n){for(let r of e.querySelectorAll(n))if(r.textContent?.trim()===t)return r;return null}function p(e,t,n){return Array.from(e.querySelectorAll(n)).filter(e=>e.textContent?.trim()===t)}function m(e,t){return e.shadowRoot?.querySelector(t)??null}function h(e,t){return Array.from(e.shadowRoot?.querySelectorAll(t)??[])}function g(e,t){return m(e,`[part="${t}"]`)}function _(e,t){let n=t?`[slot="${t}"]`:`:not([slot])`;return Array.from(e.querySelectorAll(`:scope > ${n}`))}function v(e){return{query:t=>c(e,t),queryAll:t=>l(e,t),queryAllByTestId:t=>d(e,t),queryAllByText:(t,n=`*`)=>p(e,t,n),queryByTestId:t=>u(e,t),queryByText:(t,n=`*`)=>f(e,t,n)}}async function y(e,{interval:t=50,message:r,timeout:i=1e3}={}){let a=Date.now()+i,o;for(;;){try{let t=await e();if(t===void 0||t)return}catch(e){o=e}if(Date.now()>=a)break;await S(t)}let s=`waitFor timed out after ${i}ms`,c=r?`${r} (${s})`:s,l=o instanceof Error?o.message:o==null?void 0:String(o);throw new n(l?`${c}\n${l}`:c,{cause:o})}function b(e,t,r=1e3){return new Promise((i,a)=>{let o=e=>{clearTimeout(s),i(e)},s=setTimeout(()=>{e.removeEventListener(t,o),a(new n(`waitForEvent: "${t}" timed out after ${r}ms`))},r);e.addEventListener(t,o,{once:!0})})}var x=()=>new Promise(e=>{queueMicrotask(e)}),S=(e=0)=>new Promise(t=>{setTimeout(t,e)});return e.AssayError=t,e.AssayTimeoutError=n,e.createPointerEvent=o,e.fire=s,e.getSlotted=_,e.nextTick=x,e.query=c,e.queryAll=l,e.queryAllByTestId=d,e.queryAllByText=p,e.queryAllInShadow=h,e.queryByTestId=u,e.queryByText=f,e.queryInShadow=m,e.queryPart=g,e.wait=S,e.waitFor=y,e.waitForEvent=b,e.within=v,e})({});
2
+ //# sourceMappingURL=assay.iife.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assay.iife.js","names":[],"sources":["../src/errors.ts","../src/_dev.ts","../src/events.ts","../src/query.ts","../src/wait.ts"],"sourcesContent":["/** Base class for all Assay errors. Use `instanceof AssayError` to catch any Assay-originated error. */\nexport class AssayError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is AssayError {\n return err instanceof AssayError;\n }\n}\n\n/** Thrown by `waitFor`/`waitForEvent` when a condition is not met within the timeout. */\nexport class AssayTimeoutError extends AssayError {}\n","const isDev = !(globalThis as { __ASSAY_PROD__?: boolean }).__ASSAY_PROD__;\n\nconst warnedKeys = new Set<string>();\n\n/**\n * @internal Warn at most once per `key` for the lifetime of the module — used for\n * environment-capability fallbacks (e.g. missing `PointerEvent`/`TouchEvent`), which would\n * otherwise log once per dispatched event in a suite that fires hundreds of them.\n */\nexport function warnOnce(key: string, msg: string): void {\n if (!isDev || warnedKeys.has(key)) return;\n\n warnedKeys.add(key);\n console.warn(`[@vielzeug/assay] ${msg}`);\n}\n\n/** @internal Test-only — clears \"warned once\" state so fallback-warning tests can run in any order. */\nexport function _resetWarnings(): void {\n warnedKeys.clear();\n}\n","/**\n * Synchronous DOM event dispatchers for test environments.\n */\n\nimport { warnOnce } from './_dev';\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nexport const createPointerEvent = (type: string, init: PointerEventInit = {}): Event => {\n if (typeof PointerEvent !== 'undefined') {\n return new PointerEvent(type, init);\n }\n\n warnOnce(\n 'pointer-fallback',\n 'PointerEvent is unavailable in this environment — falling back to MouseEvent. ' +\n 'pointerId/pointerType/isPrimary and similar pointer-only fields will be missing from every dispatched event.',\n );\n\n return new MouseEvent(type, init);\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Fire low-level DOM events synchronously. Every method dispatches an appropriately-typed\n * `Event` subclass with sensible defaults and returns `dispatchEvent`'s own boolean result\n * (`false` when a listener called `preventDefault()`) — the same contract for all of them,\n * with no exceptions.\n *\n * @example\n * fire.click(button);\n * fire.keyDown(input, { key: 'Enter' });\n * fire.custom(el, 'value-change', { detail: 42 });\n */\nexport const fire = {\n blur: (el: Element, opts?: FocusEventInit) => el.dispatchEvent(new FocusEvent('blur', { bubbles: true, ...opts })),\n change: (el: Element, opts?: EventInit) => el.dispatchEvent(new Event('change', { bubbles: true, ...opts })),\n click: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a CustomEvent. Pass `{ detail: ... }` for event detail. */\n custom: (el: Element, name: string, opts?: CustomEventInit) =>\n el.dispatchEvent(new CustomEvent(name, { bubbles: true, cancelable: true, composed: false, ...opts })),\n /** Dispatch any pre-built Event instance directly. */\n event: (el: Element, event: Event) => el.dispatchEvent(event),\n focus: (el: Element, _name?: string, opts?: FocusEventInit) =>\n el.dispatchEvent(new FocusEvent('focus', { bubbles: true, ...opts })),\n input: (el: Element, opts?: EventInit) => el.dispatchEvent(new Event('input', { bubbles: true, ...opts })),\n /** Fire a KeyboardEvent with a given type (keydown, keyup, keypress). */\n keyboard: (el: Element, type: string, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent(type, { bubbles: true, cancelable: true, ...opts })),\n keyDown: (el: Element, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...opts })),\n keyUp: (el: Element, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a MouseEvent with a given type (click, mousedown, etc.). */\n mouse: (el: Element, type: string, opts?: MouseEventInit) =>\n el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, ...opts })),\n pointerCancel: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointercancel', { bubbles: true, cancelable: false, ...opts })),\n pointerDown: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerdown', { bubbles: true, cancelable: true, ...opts })),\n pointerEnter: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerenter', { bubbles: false, ...opts })),\n pointerLeave: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerleave', { bubbles: false, ...opts })),\n pointerMove: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointermove', { bubbles: true, cancelable: true, ...opts })),\n pointerUp: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerup', { bubbles: true, cancelable: true, ...opts })),\n submit: (el: Element, opts?: EventInit) =>\n el.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a touch event; falls back to CustomEvent in environments without TouchEvent. */\n touch: (el: Element, type: string, opts?: EventInit) => {\n if (typeof TouchEvent !== 'undefined') {\n return el.dispatchEvent(new TouchEvent(type, { bubbles: true, cancelable: true, ...opts }));\n }\n\n warnOnce(\n 'touch-fallback',\n 'TouchEvent is unavailable in this environment — falling back to CustomEvent. The dispatched event will have no `touches`/`changedTouches` data.',\n );\n\n return el.dispatchEvent(new CustomEvent(type, { bubbles: true, cancelable: true, ...opts }));\n },\n} as const;\n","/**\n * DOM query helpers for test environments.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any DOM element — see {@link within} */\nexport interface QueryScope {\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/** Query a single element within `root`. Equivalent to `root.querySelector(selector)`. */\nexport function query<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E | null {\n return root.querySelector<E>(selector);\n}\n\n/** Query every element within `root` matching `selector`. */\nexport function queryAll<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E[] {\n return Array.from(root.querySelectorAll<E>(selector));\n}\n\n/** Query a single element within `root` by its `data-testid` attribute. */\nexport function queryByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E | null {\n return root.querySelector<E>(`[data-testid=\"${testId}\"]`);\n}\n\n/** Query every element within `root` matching a `data-testid` attribute. */\nexport function queryAllByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E[] {\n return Array.from(root.querySelectorAll<E>(`[data-testid=\"${testId}\"]`));\n}\n\nexport function queryByText<E extends Element = Element>(\n root: Element | ShadowRoot,\n text: string,\n selector: string,\n): 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\nexport function queryAllByText<E extends Element = Element>(\n root: Element | ShadowRoot,\n text: string,\n selector: string,\n): 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?.querySelector<E>(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 Array.from(host.shadowRoot?.querySelectorAll<E>(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 queryInShadow<E>(host, `[part=\"${part}\"]`);\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every slotted child (elements\n * with no `slot` attribute) 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 const selector = slotName ? `[slot=\"${slotName}\"]` : ':not([slot])';\n\n return Array.from(host.querySelectorAll<E>(`:scope > ${selector}`));\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to any element — useful for slotted/light DOM content.\n *\n * @example\n * const panel = fixture.query('.panel')!;\n * const { query } = within(panel);\n * expect(query('.title')?.textContent).toBe('Hello');\n */\nexport function within(element: Element): QueryScope {\n return {\n query: <E extends Element = Element>(selector: string) => query<E>(element, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(element, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(element, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(element, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(element, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(element, text, selector),\n };\n}\n","/**\n * Async waiting utilities for test environments.\n */\n\nimport { AssayTimeoutError } from './errors';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface WaitOptions {\n /** Maximum wait time in ms (default: 1000) */\n timeout?: number;\n /** Polling interval in ms (default: 50) */\n interval?: number;\n /** Message included in timeout error */\n message?: string;\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Poll until a callback returns truthy (or void) without throwing.\n * Supports both boolean conditions and `expect()` assertions.\n *\n * - Returns truthy → success\n * - Returns `undefined` (e.g. bare `expect()` call) → success\n * - Returns falsy value → retry\n * - Throws → retry\n *\n * Always rejects with `AssayTimeoutError` on timeout, regardless of which of the two retry\n * reasons above was last observed — `instanceof AssayTimeoutError` is a reliable way to catch\n * \"the condition never became true\" without caring whether the last attempt returned falsy or\n * threw. The original failure (an assertion's message, a thrown error) is preserved as `.cause`,\n * not merged into a mutated copy of it — `fn`'s thrown errors are caller-owned and never mutated.\n *\n * @example\n * await waitFor(() => query('.status')?.textContent === 'loaded');\n * await waitFor(() => expect(count).toBe(3));\n */\nexport async function waitFor(\n fn: () => unknown,\n { interval = 50, message, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n let lastError: unknown;\n\n // Single loop, one `fn()` call site — always tries once more before checking the deadline,\n // so a condition that becomes true exactly as the timeout elapses still succeeds.\n for (;;) {\n try {\n const result = await fn();\n\n if (result === undefined || result) return;\n } catch (e) {\n lastError = e;\n }\n\n if (Date.now() >= deadline) break;\n\n await wait(interval);\n }\n\n // The default timing summary always appears — a custom `message` adds context in front of it\n // rather than replacing it, so a debugging session never loses \"how long did this actually wait\".\n const timing = `waitFor timed out after ${timeout}ms`;\n const base = message ? `${message} (${timing})` : timing;\n const cause = lastError instanceof Error ? lastError.message : lastError != null ? String(lastError) : undefined;\n\n throw new AssayTimeoutError(cause ? `${base}\\n${cause}` : base, { cause: lastError });\n}\n\n/**\n * Resolve when the target element emits the given event.\n *\n * @example\n * const promise = waitForEvent(el, 'change');\n * fire.click(trigger);\n * const event = await promise;\n */\nexport function waitForEvent<T extends Event = Event>(element: Element, name: string, timeout = 1000): Promise<T> {\n return new Promise((resolve, reject) => {\n const onEvent = (e: Event): void => {\n clearTimeout(timer);\n resolve(e as T);\n };\n const timer = setTimeout(() => {\n element.removeEventListener(name, onEvent);\n reject(new AssayTimeoutError(`waitForEvent: \"${name}\" timed out after ${timeout}ms`));\n }, timeout);\n\n element.addEventListener(name, onEvent, { once: true });\n });\n}\n\n/**\n * Resolves after one microtask tick (`queueMicrotask`). Use to wait for reactivity (signal\n * effects, promise-chain continuations) to settle without moving into the macrotask queue —\n * cheaper and more precise than `wait(0)`, which yields to a full `setTimeout` turn instead.\n */\nexport const nextTick = (): Promise<void> =>\n new Promise((resolve) => {\n queueMicrotask(resolve);\n });\n\n/**\n * Resolves after `ms` milliseconds. Prefer `nextTick()` for reactive updates and `waitFor()`\n * for polling a condition — reach for this only when a real macrotask delay is what the code\n * under test actually depends on (e.g. a debounce timer).\n */\nexport const wait = (ms = 0): Promise<void> =>\n new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n"],"mappings":"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"}
package/dist/assay.js ADDED
@@ -0,0 +1,2 @@
1
+ var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{},n=!globalThis.__ASSAY_PROD__,r=new Set;function i(e,t){!n||r.has(e)||(r.add(e),console.warn(`[@vielzeug/assay] ${t}`))}var a=(e,t={})=>typeof PointerEvent<`u`?new PointerEvent(e,t):(i(`pointer-fallback`,`PointerEvent is unavailable in this environment — falling back to MouseEvent. pointerId/pointerType/isPrimary and similar pointer-only fields will be missing from every dispatched event.`),new MouseEvent(e,t)),o={blur:(e,t)=>e.dispatchEvent(new FocusEvent(`blur`,{bubbles:!0,...t})),change:(e,t)=>e.dispatchEvent(new Event(`change`,{bubbles:!0,...t})),click:(e,t)=>e.dispatchEvent(new MouseEvent(`click`,{bubbles:!0,cancelable:!0,...t})),custom:(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!0,composed:!1,...n})),event:(e,t)=>e.dispatchEvent(t),focus:(e,t,n)=>e.dispatchEvent(new FocusEvent(`focus`,{bubbles:!0,...n})),input:(e,t)=>e.dispatchEvent(new Event(`input`,{bubbles:!0,...t})),keyboard:(e,t,n)=>e.dispatchEvent(new KeyboardEvent(t,{bubbles:!0,cancelable:!0,...n})),keyDown:(e,t)=>e.dispatchEvent(new KeyboardEvent(`keydown`,{bubbles:!0,cancelable:!0,...t})),keyUp:(e,t)=>e.dispatchEvent(new KeyboardEvent(`keyup`,{bubbles:!0,cancelable:!0,...t})),mouse:(e,t,n)=>e.dispatchEvent(new MouseEvent(t,{bubbles:!0,cancelable:!0,...n})),pointerCancel:(e,t)=>e.dispatchEvent(a(`pointercancel`,{bubbles:!0,cancelable:!1,...t})),pointerDown:(e,t)=>e.dispatchEvent(a(`pointerdown`,{bubbles:!0,cancelable:!0,...t})),pointerEnter:(e,t)=>e.dispatchEvent(a(`pointerenter`,{bubbles:!1,...t})),pointerLeave:(e,t)=>e.dispatchEvent(a(`pointerleave`,{bubbles:!1,...t})),pointerMove:(e,t)=>e.dispatchEvent(a(`pointermove`,{bubbles:!0,cancelable:!0,...t})),pointerUp:(e,t)=>e.dispatchEvent(a(`pointerup`,{bubbles:!0,cancelable:!0,...t})),submit:(e,t)=>e.dispatchEvent(new Event(`submit`,{bubbles:!0,cancelable:!0,...t})),touch:(e,t,n)=>typeof TouchEvent<`u`?e.dispatchEvent(new TouchEvent(t,{bubbles:!0,cancelable:!0,...n})):(i(`touch-fallback`,"TouchEvent is unavailable in this environment — falling back to CustomEvent. The dispatched event will have no `touches`/`changedTouches` data."),e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!0,...n})))};function s(e,t){return e.querySelector(t)}function c(e,t){return Array.from(e.querySelectorAll(t))}function l(e,t){return e.querySelector(`[data-testid="${t}"]`)}function u(e,t){return Array.from(e.querySelectorAll(`[data-testid="${t}"]`))}function d(e,t,n){for(let r of e.querySelectorAll(n))if(r.textContent?.trim()===t)return r;return null}function f(e,t,n){return Array.from(e.querySelectorAll(n)).filter(e=>e.textContent?.trim()===t)}function p(e,t){return e.shadowRoot?.querySelector(t)??null}function m(e,t){return Array.from(e.shadowRoot?.querySelectorAll(t)??[])}function h(e,t){return p(e,`[part="${t}"]`)}function g(e,t){let n=t?`[slot="${t}"]`:`:not([slot])`;return Array.from(e.querySelectorAll(`:scope > ${n}`))}function _(e){return{query:t=>s(e,t),queryAll:t=>c(e,t),queryAllByTestId:t=>u(e,t),queryAllByText:(t,n=`*`)=>f(e,t,n),queryByTestId:t=>l(e,t),queryByText:(t,n=`*`)=>d(e,t,n)}}async function v(e,{interval:n=50,message:r,timeout:i=1e3}={}){let a=Date.now()+i,o;for(;;){try{let t=await e();if(t===void 0||t)return}catch(e){o=e}if(Date.now()>=a)break;await x(n)}let s=`waitFor timed out after ${i}ms`,c=r?`${r} (${s})`:s,l=o instanceof Error?o.message:o==null?void 0:String(o);throw new t(l?`${c}\n${l}`:c,{cause:o})}function y(e,n,r=1e3){return new Promise((i,a)=>{let o=e=>{clearTimeout(s),i(e)},s=setTimeout(()=>{e.removeEventListener(n,o),a(new t(`waitForEvent: "${n}" timed out after ${r}ms`))},r);e.addEventListener(n,o,{once:!0})})}var b=()=>new Promise(e=>{queueMicrotask(e)}),x=(e=0)=>new Promise(t=>{setTimeout(t,e)});export{e as AssayError,t as AssayTimeoutError,a as createPointerEvent,o as fire,g as getSlotted,b as nextTick,s as query,c as queryAll,u as queryAllByTestId,f as queryAllByText,m as queryAllInShadow,l as queryByTestId,d as queryByText,p as queryInShadow,h as queryPart,x as wait,v as waitFor,y as waitForEvent,_ as within};
2
+ //# sourceMappingURL=assay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assay.js","names":[],"sources":["../src/errors.ts","../src/_dev.ts","../src/events.ts","../src/query.ts","../src/wait.ts"],"sourcesContent":["/** Base class for all Assay errors. Use `instanceof AssayError` to catch any Assay-originated error. */\nexport class AssayError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is AssayError {\n return err instanceof AssayError;\n }\n}\n\n/** Thrown by `waitFor`/`waitForEvent` when a condition is not met within the timeout. */\nexport class AssayTimeoutError extends AssayError {}\n","const isDev = !(globalThis as { __ASSAY_PROD__?: boolean }).__ASSAY_PROD__;\n\nconst warnedKeys = new Set<string>();\n\n/**\n * @internal Warn at most once per `key` for the lifetime of the module — used for\n * environment-capability fallbacks (e.g. missing `PointerEvent`/`TouchEvent`), which would\n * otherwise log once per dispatched event in a suite that fires hundreds of them.\n */\nexport function warnOnce(key: string, msg: string): void {\n if (!isDev || warnedKeys.has(key)) return;\n\n warnedKeys.add(key);\n console.warn(`[@vielzeug/assay] ${msg}`);\n}\n\n/** @internal Test-only — clears \"warned once\" state so fallback-warning tests can run in any order. */\nexport function _resetWarnings(): void {\n warnedKeys.clear();\n}\n","/**\n * Synchronous DOM event dispatchers for test environments.\n */\n\nimport { warnOnce } from './_dev';\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nexport const createPointerEvent = (type: string, init: PointerEventInit = {}): Event => {\n if (typeof PointerEvent !== 'undefined') {\n return new PointerEvent(type, init);\n }\n\n warnOnce(\n 'pointer-fallback',\n 'PointerEvent is unavailable in this environment — falling back to MouseEvent. ' +\n 'pointerId/pointerType/isPrimary and similar pointer-only fields will be missing from every dispatched event.',\n );\n\n return new MouseEvent(type, init);\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Fire low-level DOM events synchronously. Every method dispatches an appropriately-typed\n * `Event` subclass with sensible defaults and returns `dispatchEvent`'s own boolean result\n * (`false` when a listener called `preventDefault()`) — the same contract for all of them,\n * with no exceptions.\n *\n * @example\n * fire.click(button);\n * fire.keyDown(input, { key: 'Enter' });\n * fire.custom(el, 'value-change', { detail: 42 });\n */\nexport const fire = {\n blur: (el: Element, opts?: FocusEventInit) => el.dispatchEvent(new FocusEvent('blur', { bubbles: true, ...opts })),\n change: (el: Element, opts?: EventInit) => el.dispatchEvent(new Event('change', { bubbles: true, ...opts })),\n click: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a CustomEvent. Pass `{ detail: ... }` for event detail. */\n custom: (el: Element, name: string, opts?: CustomEventInit) =>\n el.dispatchEvent(new CustomEvent(name, { bubbles: true, cancelable: true, composed: false, ...opts })),\n /** Dispatch any pre-built Event instance directly. */\n event: (el: Element, event: Event) => el.dispatchEvent(event),\n focus: (el: Element, _name?: string, opts?: FocusEventInit) =>\n el.dispatchEvent(new FocusEvent('focus', { bubbles: true, ...opts })),\n input: (el: Element, opts?: EventInit) => el.dispatchEvent(new Event('input', { bubbles: true, ...opts })),\n /** Fire a KeyboardEvent with a given type (keydown, keyup, keypress). */\n keyboard: (el: Element, type: string, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent(type, { bubbles: true, cancelable: true, ...opts })),\n keyDown: (el: Element, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...opts })),\n keyUp: (el: Element, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a MouseEvent with a given type (click, mousedown, etc.). */\n mouse: (el: Element, type: string, opts?: MouseEventInit) =>\n el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, ...opts })),\n pointerCancel: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointercancel', { bubbles: true, cancelable: false, ...opts })),\n pointerDown: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerdown', { bubbles: true, cancelable: true, ...opts })),\n pointerEnter: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerenter', { bubbles: false, ...opts })),\n pointerLeave: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerleave', { bubbles: false, ...opts })),\n pointerMove: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointermove', { bubbles: true, cancelable: true, ...opts })),\n pointerUp: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerup', { bubbles: true, cancelable: true, ...opts })),\n submit: (el: Element, opts?: EventInit) =>\n el.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a touch event; falls back to CustomEvent in environments without TouchEvent. */\n touch: (el: Element, type: string, opts?: EventInit) => {\n if (typeof TouchEvent !== 'undefined') {\n return el.dispatchEvent(new TouchEvent(type, { bubbles: true, cancelable: true, ...opts }));\n }\n\n warnOnce(\n 'touch-fallback',\n 'TouchEvent is unavailable in this environment — falling back to CustomEvent. The dispatched event will have no `touches`/`changedTouches` data.',\n );\n\n return el.dispatchEvent(new CustomEvent(type, { bubbles: true, cancelable: true, ...opts }));\n },\n} as const;\n","/**\n * DOM query helpers for test environments.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any DOM element — see {@link within} */\nexport interface QueryScope {\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/** Query a single element within `root`. Equivalent to `root.querySelector(selector)`. */\nexport function query<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E | null {\n return root.querySelector<E>(selector);\n}\n\n/** Query every element within `root` matching `selector`. */\nexport function queryAll<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E[] {\n return Array.from(root.querySelectorAll<E>(selector));\n}\n\n/** Query a single element within `root` by its `data-testid` attribute. */\nexport function queryByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E | null {\n return root.querySelector<E>(`[data-testid=\"${testId}\"]`);\n}\n\n/** Query every element within `root` matching a `data-testid` attribute. */\nexport function queryAllByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E[] {\n return Array.from(root.querySelectorAll<E>(`[data-testid=\"${testId}\"]`));\n}\n\nexport function queryByText<E extends Element = Element>(\n root: Element | ShadowRoot,\n text: string,\n selector: string,\n): 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\nexport function queryAllByText<E extends Element = Element>(\n root: Element | ShadowRoot,\n text: string,\n selector: string,\n): 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?.querySelector<E>(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 Array.from(host.shadowRoot?.querySelectorAll<E>(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 queryInShadow<E>(host, `[part=\"${part}\"]`);\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every slotted child (elements\n * with no `slot` attribute) 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 const selector = slotName ? `[slot=\"${slotName}\"]` : ':not([slot])';\n\n return Array.from(host.querySelectorAll<E>(`:scope > ${selector}`));\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to any element — useful for slotted/light DOM content.\n *\n * @example\n * const panel = fixture.query('.panel')!;\n * const { query } = within(panel);\n * expect(query('.title')?.textContent).toBe('Hello');\n */\nexport function within(element: Element): QueryScope {\n return {\n query: <E extends Element = Element>(selector: string) => query<E>(element, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(element, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(element, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(element, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(element, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(element, text, selector),\n };\n}\n","/**\n * Async waiting utilities for test environments.\n */\n\nimport { AssayTimeoutError } from './errors';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface WaitOptions {\n /** Maximum wait time in ms (default: 1000) */\n timeout?: number;\n /** Polling interval in ms (default: 50) */\n interval?: number;\n /** Message included in timeout error */\n message?: string;\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Poll until a callback returns truthy (or void) without throwing.\n * Supports both boolean conditions and `expect()` assertions.\n *\n * - Returns truthy → success\n * - Returns `undefined` (e.g. bare `expect()` call) → success\n * - Returns falsy value → retry\n * - Throws → retry\n *\n * Always rejects with `AssayTimeoutError` on timeout, regardless of which of the two retry\n * reasons above was last observed — `instanceof AssayTimeoutError` is a reliable way to catch\n * \"the condition never became true\" without caring whether the last attempt returned falsy or\n * threw. The original failure (an assertion's message, a thrown error) is preserved as `.cause`,\n * not merged into a mutated copy of it — `fn`'s thrown errors are caller-owned and never mutated.\n *\n * @example\n * await waitFor(() => query('.status')?.textContent === 'loaded');\n * await waitFor(() => expect(count).toBe(3));\n */\nexport async function waitFor(\n fn: () => unknown,\n { interval = 50, message, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n let lastError: unknown;\n\n // Single loop, one `fn()` call site — always tries once more before checking the deadline,\n // so a condition that becomes true exactly as the timeout elapses still succeeds.\n for (;;) {\n try {\n const result = await fn();\n\n if (result === undefined || result) return;\n } catch (e) {\n lastError = e;\n }\n\n if (Date.now() >= deadline) break;\n\n await wait(interval);\n }\n\n // The default timing summary always appears — a custom `message` adds context in front of it\n // rather than replacing it, so a debugging session never loses \"how long did this actually wait\".\n const timing = `waitFor timed out after ${timeout}ms`;\n const base = message ? `${message} (${timing})` : timing;\n const cause = lastError instanceof Error ? lastError.message : lastError != null ? String(lastError) : undefined;\n\n throw new AssayTimeoutError(cause ? `${base}\\n${cause}` : base, { cause: lastError });\n}\n\n/**\n * Resolve when the target element emits the given event.\n *\n * @example\n * const promise = waitForEvent(el, 'change');\n * fire.click(trigger);\n * const event = await promise;\n */\nexport function waitForEvent<T extends Event = Event>(element: Element, name: string, timeout = 1000): Promise<T> {\n return new Promise((resolve, reject) => {\n const onEvent = (e: Event): void => {\n clearTimeout(timer);\n resolve(e as T);\n };\n const timer = setTimeout(() => {\n element.removeEventListener(name, onEvent);\n reject(new AssayTimeoutError(`waitForEvent: \"${name}\" timed out after ${timeout}ms`));\n }, timeout);\n\n element.addEventListener(name, onEvent, { once: true });\n });\n}\n\n/**\n * Resolves after one microtask tick (`queueMicrotask`). Use to wait for reactivity (signal\n * effects, promise-chain continuations) to settle without moving into the macrotask queue —\n * cheaper and more precise than `wait(0)`, which yields to a full `setTimeout` turn instead.\n */\nexport const nextTick = (): Promise<void> =>\n new Promise((resolve) => {\n queueMicrotask(resolve);\n });\n\n/**\n * Resolves after `ms` milliseconds. Prefer `nextTick()` for reactive updates and `waitFor()`\n * for polling a condition — reach for this only when a real macrotask delay is what the code\n * under test actually depends on (e.g. a debounce timer).\n */\nexport const wait = (ms = 0): Promise<void> =>\n new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{};exports.AssayError=e,exports.AssayTimeoutError=t;
2
+ //# sourceMappingURL=errors.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all Assay errors. Use `instanceof AssayError` to catch any Assay-originated error. */\nexport class AssayError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is AssayError {\n return err instanceof AssayError;\n }\n}\n\n/** Thrown by `waitFor`/`waitForEvent` when a condition is not met within the timeout. */\nexport class AssayTimeoutError extends AssayError {}\n"],"mappings":"AACA,IAAa,EAAb,MAAa,UAAmB,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,IAAI,OAAO,KACvB,OAAO,eAAe,KAAM,IAAI,OAAO,SAAS,CAClD,CAEA,OAAO,GAAG,EAAiC,CACzC,OAAO,aAAe,CACxB,CACF,EAGa,EAAb,cAAuC,CAAW,CAAC"}
@@ -0,0 +1,9 @@
1
+ /** Base class for all Assay errors. Use `instanceof AssayError` to catch any Assay-originated error. */
2
+ export declare class AssayError extends Error {
3
+ constructor(message: string, opts?: ErrorOptions);
4
+ static is(err: unknown): err is AssayError;
5
+ }
6
+ /** Thrown by `waitFor`/`waitForEvent` when a condition is not met within the timeout. */
7
+ export declare class AssayTimeoutError extends AssayError {
8
+ }
9
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;IAMhD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,UAAU;CAG3C;AAED,yFAAyF;AACzF,qBAAa,iBAAkB,SAAQ,UAAU;CAAG"}
package/dist/errors.js ADDED
@@ -0,0 +1,13 @@
1
+ //#region src/errors.ts
2
+ var e = class e extends Error {
3
+ constructor(e, t) {
4
+ super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
5
+ }
6
+ static is(t) {
7
+ return t instanceof e;
8
+ }
9
+ }, t = class extends e {};
10
+ //#endregion
11
+ export { e as AssayError, t as AssayTimeoutError };
12
+
13
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all Assay errors. Use `instanceof AssayError` to catch any Assay-originated error. */\nexport class AssayError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is AssayError {\n return err instanceof AssayError;\n }\n}\n\n/** Thrown by `waitFor`/`waitForEvent` when a condition is not met within the timeout. */\nexport class AssayTimeoutError extends AssayError {}\n"],"mappings":";AACA,IAAa,IAAb,MAAa,UAAmB,MAAM;CACpC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,IAAI,OAAO,MACvB,OAAO,eAAe,MAAM,IAAI,OAAO,SAAS;CAClD;CAEA,OAAO,GAAG,GAAiC;EACzC,OAAO,aAAe;CACxB;AACF,GAGa,IAAb,cAAuC,EAAW,CAAC"}
@@ -0,0 +1,2 @@
1
+ const e=require("./_dev.cjs");var t=(t,n={})=>typeof PointerEvent<`u`?new PointerEvent(t,n):(e.warnOnce(`pointer-fallback`,`PointerEvent is unavailable in this environment — falling back to MouseEvent. pointerId/pointerType/isPrimary and similar pointer-only fields will be missing from every dispatched event.`),new MouseEvent(t,n)),n={blur:(e,t)=>e.dispatchEvent(new FocusEvent(`blur`,{bubbles:!0,...t})),change:(e,t)=>e.dispatchEvent(new Event(`change`,{bubbles:!0,...t})),click:(e,t)=>e.dispatchEvent(new MouseEvent(`click`,{bubbles:!0,cancelable:!0,...t})),custom:(e,t,n)=>e.dispatchEvent(new CustomEvent(t,{bubbles:!0,cancelable:!0,composed:!1,...n})),event:(e,t)=>e.dispatchEvent(t),focus:(e,t,n)=>e.dispatchEvent(new FocusEvent(`focus`,{bubbles:!0,...n})),input:(e,t)=>e.dispatchEvent(new Event(`input`,{bubbles:!0,...t})),keyboard:(e,t,n)=>e.dispatchEvent(new KeyboardEvent(t,{bubbles:!0,cancelable:!0,...n})),keyDown:(e,t)=>e.dispatchEvent(new KeyboardEvent(`keydown`,{bubbles:!0,cancelable:!0,...t})),keyUp:(e,t)=>e.dispatchEvent(new KeyboardEvent(`keyup`,{bubbles:!0,cancelable:!0,...t})),mouse:(e,t,n)=>e.dispatchEvent(new MouseEvent(t,{bubbles:!0,cancelable:!0,...n})),pointerCancel:(e,n)=>e.dispatchEvent(t(`pointercancel`,{bubbles:!0,cancelable:!1,...n})),pointerDown:(e,n)=>e.dispatchEvent(t(`pointerdown`,{bubbles:!0,cancelable:!0,...n})),pointerEnter:(e,n)=>e.dispatchEvent(t(`pointerenter`,{bubbles:!1,...n})),pointerLeave:(e,n)=>e.dispatchEvent(t(`pointerleave`,{bubbles:!1,...n})),pointerMove:(e,n)=>e.dispatchEvent(t(`pointermove`,{bubbles:!0,cancelable:!0,...n})),pointerUp:(e,n)=>e.dispatchEvent(t(`pointerup`,{bubbles:!0,cancelable:!0,...n})),submit:(e,t)=>e.dispatchEvent(new Event(`submit`,{bubbles:!0,cancelable:!0,...t})),touch:(t,n,r)=>typeof TouchEvent<`u`?t.dispatchEvent(new TouchEvent(n,{bubbles:!0,cancelable:!0,...r})):(e.warnOnce(`touch-fallback`,"TouchEvent is unavailable in this environment — falling back to CustomEvent. The dispatched event will have no `touches`/`changedTouches` data."),t.dispatchEvent(new CustomEvent(n,{bubbles:!0,cancelable:!0,...r})))};exports.createPointerEvent=t,exports.fire=n;
2
+ //# sourceMappingURL=events.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.cjs","names":[],"sources":["../src/events.ts"],"sourcesContent":["/**\n * Synchronous DOM event dispatchers for test environments.\n */\n\nimport { warnOnce } from './_dev';\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nexport const createPointerEvent = (type: string, init: PointerEventInit = {}): Event => {\n if (typeof PointerEvent !== 'undefined') {\n return new PointerEvent(type, init);\n }\n\n warnOnce(\n 'pointer-fallback',\n 'PointerEvent is unavailable in this environment — falling back to MouseEvent. ' +\n 'pointerId/pointerType/isPrimary and similar pointer-only fields will be missing from every dispatched event.',\n );\n\n return new MouseEvent(type, init);\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Fire low-level DOM events synchronously. Every method dispatches an appropriately-typed\n * `Event` subclass with sensible defaults and returns `dispatchEvent`'s own boolean result\n * (`false` when a listener called `preventDefault()`) — the same contract for all of them,\n * with no exceptions.\n *\n * @example\n * fire.click(button);\n * fire.keyDown(input, { key: 'Enter' });\n * fire.custom(el, 'value-change', { detail: 42 });\n */\nexport const fire = {\n blur: (el: Element, opts?: FocusEventInit) => el.dispatchEvent(new FocusEvent('blur', { bubbles: true, ...opts })),\n change: (el: Element, opts?: EventInit) => el.dispatchEvent(new Event('change', { bubbles: true, ...opts })),\n click: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a CustomEvent. Pass `{ detail: ... }` for event detail. */\n custom: (el: Element, name: string, opts?: CustomEventInit) =>\n el.dispatchEvent(new CustomEvent(name, { bubbles: true, cancelable: true, composed: false, ...opts })),\n /** Dispatch any pre-built Event instance directly. */\n event: (el: Element, event: Event) => el.dispatchEvent(event),\n focus: (el: Element, _name?: string, opts?: FocusEventInit) =>\n el.dispatchEvent(new FocusEvent('focus', { bubbles: true, ...opts })),\n input: (el: Element, opts?: EventInit) => el.dispatchEvent(new Event('input', { bubbles: true, ...opts })),\n /** Fire a KeyboardEvent with a given type (keydown, keyup, keypress). */\n keyboard: (el: Element, type: string, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent(type, { bubbles: true, cancelable: true, ...opts })),\n keyDown: (el: Element, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...opts })),\n keyUp: (el: Element, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a MouseEvent with a given type (click, mousedown, etc.). */\n mouse: (el: Element, type: string, opts?: MouseEventInit) =>\n el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, ...opts })),\n pointerCancel: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointercancel', { bubbles: true, cancelable: false, ...opts })),\n pointerDown: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerdown', { bubbles: true, cancelable: true, ...opts })),\n pointerEnter: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerenter', { bubbles: false, ...opts })),\n pointerLeave: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerleave', { bubbles: false, ...opts })),\n pointerMove: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointermove', { bubbles: true, cancelable: true, ...opts })),\n pointerUp: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerup', { bubbles: true, cancelable: true, ...opts })),\n submit: (el: Element, opts?: EventInit) =>\n el.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a touch event; falls back to CustomEvent in environments without TouchEvent. */\n touch: (el: Element, type: string, opts?: EventInit) => {\n if (typeof TouchEvent !== 'undefined') {\n return el.dispatchEvent(new TouchEvent(type, { bubbles: true, cancelable: true, ...opts }));\n }\n\n warnOnce(\n 'touch-fallback',\n 'TouchEvent is unavailable in this environment — falling back to CustomEvent. The dispatched event will have no `touches`/`changedTouches` data.',\n );\n\n return el.dispatchEvent(new CustomEvent(type, { bubbles: true, cancelable: true, ...opts }));\n },\n} as const;\n"],"mappings":"8BAQA,IAAa,GAAsB,EAAc,EAAyB,CAAC,IACrE,OAAO,aAAiB,IACnB,IAAI,aAAa,EAAM,CAAI,GAGpC,EAAA,SACE,mBACA,4LAEF,EAEO,IAAI,WAAW,EAAM,CAAI,GAgBrB,EAAO,CAClB,MAAO,EAAa,IAA0B,EAAG,cAAc,IAAI,WAAW,OAAQ,CAAE,QAAS,GAAM,GAAG,CAAK,CAAC,CAAC,EACjH,QAAS,EAAa,IAAqB,EAAG,cAAc,IAAI,MAAM,SAAU,CAAE,QAAS,GAAM,GAAG,CAAK,CAAC,CAAC,EAC3G,OAAQ,EAAa,IACnB,EAAG,cAAc,IAAI,WAAW,QAAS,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAExF,QAAS,EAAa,EAAc,IAClC,EAAG,cAAc,IAAI,YAAY,EAAM,CAAE,QAAS,GAAM,WAAY,GAAM,SAAU,GAAO,GAAG,CAAK,CAAC,CAAC,EAEvG,OAAQ,EAAa,IAAiB,EAAG,cAAc,CAAK,EAC5D,OAAQ,EAAa,EAAgB,IACnC,EAAG,cAAc,IAAI,WAAW,QAAS,CAAE,QAAS,GAAM,GAAG,CAAK,CAAC,CAAC,EACtE,OAAQ,EAAa,IAAqB,EAAG,cAAc,IAAI,MAAM,QAAS,CAAE,QAAS,GAAM,GAAG,CAAK,CAAC,CAAC,EAEzG,UAAW,EAAa,EAAc,IACpC,EAAG,cAAc,IAAI,cAAc,EAAM,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EACxF,SAAU,EAAa,IACrB,EAAG,cAAc,IAAI,cAAc,UAAW,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAC7F,OAAQ,EAAa,IACnB,EAAG,cAAc,IAAI,cAAc,QAAS,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAE3F,OAAQ,EAAa,EAAc,IACjC,EAAG,cAAc,IAAI,WAAW,EAAM,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EACrF,eAAgB,EAAa,IAC3B,EAAG,cAAc,EAAmB,gBAAiB,CAAE,QAAS,GAAM,WAAY,GAAO,GAAG,CAAK,CAAC,CAAC,EACrG,aAAc,EAAa,IACzB,EAAG,cAAc,EAAmB,cAAe,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAClG,cAAe,EAAa,IAC1B,EAAG,cAAc,EAAmB,eAAgB,CAAE,QAAS,GAAO,GAAG,CAAK,CAAC,CAAC,EAClF,cAAe,EAAa,IAC1B,EAAG,cAAc,EAAmB,eAAgB,CAAE,QAAS,GAAO,GAAG,CAAK,CAAC,CAAC,EAClF,aAAc,EAAa,IACzB,EAAG,cAAc,EAAmB,cAAe,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAClG,WAAY,EAAa,IACvB,EAAG,cAAc,EAAmB,YAAa,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAChG,QAAS,EAAa,IACpB,EAAG,cAAc,IAAI,MAAM,SAAU,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAEpF,OAAQ,EAAa,EAAc,IAC7B,OAAO,WAAe,IACjB,EAAG,cAAc,IAAI,WAAW,EAAM,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,GAG5F,EAAA,SACE,iBACA,iJACF,EAEO,EAAG,cAAc,IAAI,YAAY,EAAM,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAE/F"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Synchronous DOM event dispatchers for test environments.
3
+ */
4
+ export declare const createPointerEvent: (type: string, init?: PointerEventInit) => Event;
5
+ /**
6
+ * Fire low-level DOM events synchronously. Every method dispatches an appropriately-typed
7
+ * `Event` subclass with sensible defaults and returns `dispatchEvent`'s own boolean result
8
+ * (`false` when a listener called `preventDefault()`) — the same contract for all of them,
9
+ * with no exceptions.
10
+ *
11
+ * @example
12
+ * fire.click(button);
13
+ * fire.keyDown(input, { key: 'Enter' });
14
+ * fire.custom(el, 'value-change', { detail: 42 });
15
+ */
16
+ export declare const fire: {
17
+ readonly blur: (el: Element, opts?: FocusEventInit) => boolean;
18
+ readonly change: (el: Element, opts?: EventInit) => boolean;
19
+ readonly click: (el: Element, opts?: PointerEventInit) => boolean;
20
+ /** Fire a CustomEvent. Pass `{ detail: ... }` for event detail. */
21
+ readonly custom: (el: Element, name: string, opts?: CustomEventInit) => boolean;
22
+ /** Dispatch any pre-built Event instance directly. */
23
+ readonly event: (el: Element, event: Event) => boolean;
24
+ readonly focus: (el: Element, _name?: string, opts?: FocusEventInit) => boolean;
25
+ readonly input: (el: Element, opts?: EventInit) => boolean;
26
+ /** Fire a KeyboardEvent with a given type (keydown, keyup, keypress). */
27
+ readonly keyboard: (el: Element, type: string, opts?: KeyboardEventInit) => boolean;
28
+ readonly keyDown: (el: Element, opts?: KeyboardEventInit) => boolean;
29
+ readonly keyUp: (el: Element, opts?: KeyboardEventInit) => boolean;
30
+ /** Fire a MouseEvent with a given type (click, mousedown, etc.). */
31
+ readonly mouse: (el: Element, type: string, opts?: MouseEventInit) => boolean;
32
+ readonly pointerCancel: (el: Element, opts?: PointerEventInit) => boolean;
33
+ readonly pointerDown: (el: Element, opts?: PointerEventInit) => boolean;
34
+ readonly pointerEnter: (el: Element, opts?: PointerEventInit) => boolean;
35
+ readonly pointerLeave: (el: Element, opts?: PointerEventInit) => boolean;
36
+ readonly pointerMove: (el: Element, opts?: PointerEventInit) => boolean;
37
+ readonly pointerUp: (el: Element, opts?: PointerEventInit) => boolean;
38
+ readonly submit: (el: Element, opts?: EventInit) => boolean;
39
+ /** Fire a touch event; falls back to CustomEvent in environments without TouchEvent. */
40
+ readonly touch: (el: Element, type: string, opts?: EventInit) => boolean;
41
+ };
42
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,EAAE,OAAM,gBAAqB,KAAG,KAY9E,CAAC;AAIF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,IAAI;wBACJ,OAAO,SAAS,cAAc;0BAC5B,OAAO,SAAS,SAAS;yBAC1B,OAAO,SAAS,gBAAgB;IAE5C,mEAAmE;0BACtD,OAAO,QAAQ,MAAM,SAAS,eAAe;IAE1D,sDAAsD;yBAC1C,OAAO,SAAS,KAAK;yBACrB,OAAO,UAAU,MAAM,SAAS,cAAc;yBAE9C,OAAO,SAAS,SAAS;IACrC,yEAAyE;4BAC1D,OAAO,QAAQ,MAAM,SAAS,iBAAiB;2BAEhD,OAAO,SAAS,iBAAiB;yBAEnC,OAAO,SAAS,iBAAiB;IAE7C,oEAAoE;yBACxD,OAAO,QAAQ,MAAM,SAAS,cAAc;iCAEpC,OAAO,SAAS,gBAAgB;+BAElC,OAAO,SAAS,gBAAgB;gCAE/B,OAAO,SAAS,gBAAgB;gCAEhC,OAAO,SAAS,gBAAgB;+BAEjC,OAAO,SAAS,gBAAgB;6BAElC,OAAO,SAAS,gBAAgB;0BAEnC,OAAO,SAAS,SAAS;IAEtC,wFAAwF;yBAC5E,OAAO,QAAQ,MAAM,SAAS,SAAS;CAY3C,CAAC"}
package/dist/events.js ADDED
@@ -0,0 +1,98 @@
1
+ import { warnOnce as e } from "./_dev.js";
2
+ //#region src/events.ts
3
+ var t = (t, n = {}) => typeof PointerEvent < "u" ? new PointerEvent(t, n) : (e("pointer-fallback", "PointerEvent is unavailable in this environment — falling back to MouseEvent. pointerId/pointerType/isPrimary and similar pointer-only fields will be missing from every dispatched event."), new MouseEvent(t, n)), n = {
4
+ blur: (e, t) => e.dispatchEvent(new FocusEvent("blur", {
5
+ bubbles: !0,
6
+ ...t
7
+ })),
8
+ change: (e, t) => e.dispatchEvent(new Event("change", {
9
+ bubbles: !0,
10
+ ...t
11
+ })),
12
+ click: (e, t) => e.dispatchEvent(new MouseEvent("click", {
13
+ bubbles: !0,
14
+ cancelable: !0,
15
+ ...t
16
+ })),
17
+ custom: (e, t, n) => e.dispatchEvent(new CustomEvent(t, {
18
+ bubbles: !0,
19
+ cancelable: !0,
20
+ composed: !1,
21
+ ...n
22
+ })),
23
+ event: (e, t) => e.dispatchEvent(t),
24
+ focus: (e, t, n) => e.dispatchEvent(new FocusEvent("focus", {
25
+ bubbles: !0,
26
+ ...n
27
+ })),
28
+ input: (e, t) => e.dispatchEvent(new Event("input", {
29
+ bubbles: !0,
30
+ ...t
31
+ })),
32
+ keyboard: (e, t, n) => e.dispatchEvent(new KeyboardEvent(t, {
33
+ bubbles: !0,
34
+ cancelable: !0,
35
+ ...n
36
+ })),
37
+ keyDown: (e, t) => e.dispatchEvent(new KeyboardEvent("keydown", {
38
+ bubbles: !0,
39
+ cancelable: !0,
40
+ ...t
41
+ })),
42
+ keyUp: (e, t) => e.dispatchEvent(new KeyboardEvent("keyup", {
43
+ bubbles: !0,
44
+ cancelable: !0,
45
+ ...t
46
+ })),
47
+ mouse: (e, t, n) => e.dispatchEvent(new MouseEvent(t, {
48
+ bubbles: !0,
49
+ cancelable: !0,
50
+ ...n
51
+ })),
52
+ pointerCancel: (e, n) => e.dispatchEvent(t("pointercancel", {
53
+ bubbles: !0,
54
+ cancelable: !1,
55
+ ...n
56
+ })),
57
+ pointerDown: (e, n) => e.dispatchEvent(t("pointerdown", {
58
+ bubbles: !0,
59
+ cancelable: !0,
60
+ ...n
61
+ })),
62
+ pointerEnter: (e, n) => e.dispatchEvent(t("pointerenter", {
63
+ bubbles: !1,
64
+ ...n
65
+ })),
66
+ pointerLeave: (e, n) => e.dispatchEvent(t("pointerleave", {
67
+ bubbles: !1,
68
+ ...n
69
+ })),
70
+ pointerMove: (e, n) => e.dispatchEvent(t("pointermove", {
71
+ bubbles: !0,
72
+ cancelable: !0,
73
+ ...n
74
+ })),
75
+ pointerUp: (e, n) => e.dispatchEvent(t("pointerup", {
76
+ bubbles: !0,
77
+ cancelable: !0,
78
+ ...n
79
+ })),
80
+ submit: (e, t) => e.dispatchEvent(new Event("submit", {
81
+ bubbles: !0,
82
+ cancelable: !0,
83
+ ...t
84
+ })),
85
+ touch: (t, n, r) => typeof TouchEvent < "u" ? t.dispatchEvent(new TouchEvent(n, {
86
+ bubbles: !0,
87
+ cancelable: !0,
88
+ ...r
89
+ })) : (e("touch-fallback", "TouchEvent is unavailable in this environment — falling back to CustomEvent. The dispatched event will have no `touches`/`changedTouches` data."), t.dispatchEvent(new CustomEvent(n, {
90
+ bubbles: !0,
91
+ cancelable: !0,
92
+ ...r
93
+ })))
94
+ };
95
+ //#endregion
96
+ export { t as createPointerEvent, n as fire };
97
+
98
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","names":[],"sources":["../src/events.ts"],"sourcesContent":["/**\n * Synchronous DOM event dispatchers for test environments.\n */\n\nimport { warnOnce } from './_dev';\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nexport const createPointerEvent = (type: string, init: PointerEventInit = {}): Event => {\n if (typeof PointerEvent !== 'undefined') {\n return new PointerEvent(type, init);\n }\n\n warnOnce(\n 'pointer-fallback',\n 'PointerEvent is unavailable in this environment — falling back to MouseEvent. ' +\n 'pointerId/pointerType/isPrimary and similar pointer-only fields will be missing from every dispatched event.',\n );\n\n return new MouseEvent(type, init);\n};\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Fire low-level DOM events synchronously. Every method dispatches an appropriately-typed\n * `Event` subclass with sensible defaults and returns `dispatchEvent`'s own boolean result\n * (`false` when a listener called `preventDefault()`) — the same contract for all of them,\n * with no exceptions.\n *\n * @example\n * fire.click(button);\n * fire.keyDown(input, { key: 'Enter' });\n * fire.custom(el, 'value-change', { detail: 42 });\n */\nexport const fire = {\n blur: (el: Element, opts?: FocusEventInit) => el.dispatchEvent(new FocusEvent('blur', { bubbles: true, ...opts })),\n change: (el: Element, opts?: EventInit) => el.dispatchEvent(new Event('change', { bubbles: true, ...opts })),\n click: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a CustomEvent. Pass `{ detail: ... }` for event detail. */\n custom: (el: Element, name: string, opts?: CustomEventInit) =>\n el.dispatchEvent(new CustomEvent(name, { bubbles: true, cancelable: true, composed: false, ...opts })),\n /** Dispatch any pre-built Event instance directly. */\n event: (el: Element, event: Event) => el.dispatchEvent(event),\n focus: (el: Element, _name?: string, opts?: FocusEventInit) =>\n el.dispatchEvent(new FocusEvent('focus', { bubbles: true, ...opts })),\n input: (el: Element, opts?: EventInit) => el.dispatchEvent(new Event('input', { bubbles: true, ...opts })),\n /** Fire a KeyboardEvent with a given type (keydown, keyup, keypress). */\n keyboard: (el: Element, type: string, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent(type, { bubbles: true, cancelable: true, ...opts })),\n keyDown: (el: Element, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...opts })),\n keyUp: (el: Element, opts?: KeyboardEventInit) =>\n el.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a MouseEvent with a given type (click, mousedown, etc.). */\n mouse: (el: Element, type: string, opts?: MouseEventInit) =>\n el.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, ...opts })),\n pointerCancel: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointercancel', { bubbles: true, cancelable: false, ...opts })),\n pointerDown: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerdown', { bubbles: true, cancelable: true, ...opts })),\n pointerEnter: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerenter', { bubbles: false, ...opts })),\n pointerLeave: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerleave', { bubbles: false, ...opts })),\n pointerMove: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointermove', { bubbles: true, cancelable: true, ...opts })),\n pointerUp: (el: Element, opts?: PointerEventInit) =>\n el.dispatchEvent(createPointerEvent('pointerup', { bubbles: true, cancelable: true, ...opts })),\n submit: (el: Element, opts?: EventInit) =>\n el.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true, ...opts })),\n /** Fire a touch event; falls back to CustomEvent in environments without TouchEvent. */\n touch: (el: Element, type: string, opts?: EventInit) => {\n if (typeof TouchEvent !== 'undefined') {\n return el.dispatchEvent(new TouchEvent(type, { bubbles: true, cancelable: true, ...opts }));\n }\n\n warnOnce(\n 'touch-fallback',\n 'TouchEvent is unavailable in this environment — falling back to CustomEvent. The dispatched event will have no `touches`/`changedTouches` data.',\n );\n\n return el.dispatchEvent(new CustomEvent(type, { bubbles: true, cancelable: true, ...opts }));\n },\n} as const;\n"],"mappings":";;AAQA,IAAa,KAAsB,GAAc,IAAyB,CAAC,MACrE,OAAO,eAAiB,MACnB,IAAI,aAAa,GAAM,CAAI,KAGpC,EACE,oBACA,4LAEF,GAEO,IAAI,WAAW,GAAM,CAAI,IAgBrB,IAAO;CAClB,OAAO,GAAa,MAA0B,EAAG,cAAc,IAAI,WAAW,QAAQ;EAAE,SAAS;EAAM,GAAG;CAAK,CAAC,CAAC;CACjH,SAAS,GAAa,MAAqB,EAAG,cAAc,IAAI,MAAM,UAAU;EAAE,SAAS;EAAM,GAAG;CAAK,CAAC,CAAC;CAC3G,QAAQ,GAAa,MACnB,EAAG,cAAc,IAAI,WAAW,SAAS;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC;CAExF,SAAS,GAAa,GAAc,MAClC,EAAG,cAAc,IAAI,YAAY,GAAM;EAAE,SAAS;EAAM,YAAY;EAAM,UAAU;EAAO,GAAG;CAAK,CAAC,CAAC;CAEvG,QAAQ,GAAa,MAAiB,EAAG,cAAc,CAAK;CAC5D,QAAQ,GAAa,GAAgB,MACnC,EAAG,cAAc,IAAI,WAAW,SAAS;EAAE,SAAS;EAAM,GAAG;CAAK,CAAC,CAAC;CACtE,QAAQ,GAAa,MAAqB,EAAG,cAAc,IAAI,MAAM,SAAS;EAAE,SAAS;EAAM,GAAG;CAAK,CAAC,CAAC;CAEzG,WAAW,GAAa,GAAc,MACpC,EAAG,cAAc,IAAI,cAAc,GAAM;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC;CACxF,UAAU,GAAa,MACrB,EAAG,cAAc,IAAI,cAAc,WAAW;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC;CAC7F,QAAQ,GAAa,MACnB,EAAG,cAAc,IAAI,cAAc,SAAS;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC;CAE3F,QAAQ,GAAa,GAAc,MACjC,EAAG,cAAc,IAAI,WAAW,GAAM;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC;CACrF,gBAAgB,GAAa,MAC3B,EAAG,cAAc,EAAmB,iBAAiB;EAAE,SAAS;EAAM,YAAY;EAAO,GAAG;CAAK,CAAC,CAAC;CACrG,cAAc,GAAa,MACzB,EAAG,cAAc,EAAmB,eAAe;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC;CAClG,eAAe,GAAa,MAC1B,EAAG,cAAc,EAAmB,gBAAgB;EAAE,SAAS;EAAO,GAAG;CAAK,CAAC,CAAC;CAClF,eAAe,GAAa,MAC1B,EAAG,cAAc,EAAmB,gBAAgB;EAAE,SAAS;EAAO,GAAG;CAAK,CAAC,CAAC;CAClF,cAAc,GAAa,MACzB,EAAG,cAAc,EAAmB,eAAe;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC;CAClG,YAAY,GAAa,MACvB,EAAG,cAAc,EAAmB,aAAa;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC;CAChG,SAAS,GAAa,MACpB,EAAG,cAAc,IAAI,MAAM,UAAU;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC;CAEpF,QAAQ,GAAa,GAAc,MAC7B,OAAO,aAAe,MACjB,EAAG,cAAc,IAAI,WAAW,GAAM;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC,KAG5F,EACE,kBACA,iJACF,GAEO,EAAG,cAAc,IAAI,YAAY,GAAM;EAAE,SAAS;EAAM,YAAY;EAAM,GAAG;CAAK,CAAC,CAAC;AAE/F"}
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./events.cjs"),n=require("./query.cjs"),r=require("./wait.cjs");exports.AssayError=e.AssayError,exports.AssayTimeoutError=e.AssayTimeoutError,exports.createPointerEvent=t.createPointerEvent,exports.fire=t.fire,exports.getSlotted=n.getSlotted,exports.nextTick=r.nextTick,exports.query=n.query,exports.queryAll=n.queryAll,exports.queryAllByTestId=n.queryAllByTestId,exports.queryAllByText=n.queryAllByText,exports.queryAllInShadow=n.queryAllInShadow,exports.queryByTestId=n.queryByTestId,exports.queryByText=n.queryByText,exports.queryInShadow=n.queryInShadow,exports.queryPart=n.queryPart,exports.wait=r.wait,exports.waitFor=r.waitFor,exports.waitForEvent=r.waitForEvent,exports.within=n.within;
@@ -0,0 +1,5 @@
1
+ export { AssayError, AssayTimeoutError } from './errors';
2
+ export { fire, createPointerEvent } from './events';
3
+ export { within, query, queryAll, queryByTestId, queryAllByTestId, queryByText, queryAllByText, queryInShadow, queryAllInShadow, queryPart, getSlotted, type QueryScope, } from './query';
4
+ export { waitFor, waitForEvent, nextTick, wait, type WaitOptions } from './wait';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAEzD,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EACL,MAAM,EACN,KAAK,EACL,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,KAAK,UAAU,GAChB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { AssayError as e, AssayTimeoutError as t } from "./errors.js";
2
+ import { createPointerEvent as n, fire as r } from "./events.js";
3
+ import { getSlotted as i, query as a, queryAll as o, queryAllByTestId as s, queryAllByText as c, queryAllInShadow as l, queryByTestId as u, queryByText as d, queryInShadow as f, queryPart as p, within as m } from "./query.js";
4
+ import { nextTick as h, wait as g, waitFor as _, waitForEvent as v } from "./wait.js";
5
+ export { e as AssayError, t as AssayTimeoutError, n as createPointerEvent, r as fire, i as getSlotted, h as nextTick, a as query, o as queryAll, s as queryAllByTestId, c as queryAllByText, l as queryAllInShadow, u as queryByTestId, d as queryByText, f as queryInShadow, p as queryPart, g as wait, _ as waitFor, v as waitForEvent, m as within };
package/dist/query.cjs ADDED
@@ -0,0 +1,2 @@
1
+ function e(e,t){return e.querySelector(t)}function t(e,t){return Array.from(e.querySelectorAll(t))}function n(e,t){return e.querySelector(`[data-testid="${t}"]`)}function r(e,t){return Array.from(e.querySelectorAll(`[data-testid="${t}"]`))}function i(e,t,n){for(let r of e.querySelectorAll(n))if(r.textContent?.trim()===t)return r;return null}function a(e,t,n){return Array.from(e.querySelectorAll(n)).filter(e=>e.textContent?.trim()===t)}function o(e,t){return e.shadowRoot?.querySelector(t)??null}function s(e,t){return Array.from(e.shadowRoot?.querySelectorAll(t)??[])}function c(e,t){return o(e,`[part="${t}"]`)}function l(e,t){let n=t?`[slot="${t}"]`:`:not([slot])`;return Array.from(e.querySelectorAll(`:scope > ${n}`))}function u(o){return{query:t=>e(o,t),queryAll:e=>t(o,e),queryAllByTestId:e=>r(o,e),queryAllByText:(e,t=`*`)=>a(o,e,t),queryByTestId:e=>n(o,e),queryByText:(e,t=`*`)=>i(o,e,t)}}exports.getSlotted=l,exports.query=e,exports.queryAll=t,exports.queryAllByTestId=r,exports.queryAllByText=a,exports.queryAllInShadow=s,exports.queryByTestId=n,exports.queryByText=i,exports.queryInShadow=o,exports.queryPart=c,exports.within=u;
2
+ //# sourceMappingURL=query.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.cjs","names":[],"sources":["../src/query.ts"],"sourcesContent":["/**\n * DOM query helpers for test environments.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any DOM element — see {@link within} */\nexport interface QueryScope {\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/** Query a single element within `root`. Equivalent to `root.querySelector(selector)`. */\nexport function query<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E | null {\n return root.querySelector<E>(selector);\n}\n\n/** Query every element within `root` matching `selector`. */\nexport function queryAll<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E[] {\n return Array.from(root.querySelectorAll<E>(selector));\n}\n\n/** Query a single element within `root` by its `data-testid` attribute. */\nexport function queryByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E | null {\n return root.querySelector<E>(`[data-testid=\"${testId}\"]`);\n}\n\n/** Query every element within `root` matching a `data-testid` attribute. */\nexport function queryAllByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E[] {\n return Array.from(root.querySelectorAll<E>(`[data-testid=\"${testId}\"]`));\n}\n\nexport function queryByText<E extends Element = Element>(\n root: Element | ShadowRoot,\n text: string,\n selector: string,\n): 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\nexport function queryAllByText<E extends Element = Element>(\n root: Element | ShadowRoot,\n text: string,\n selector: string,\n): 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?.querySelector<E>(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 Array.from(host.shadowRoot?.querySelectorAll<E>(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 queryInShadow<E>(host, `[part=\"${part}\"]`);\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every slotted child (elements\n * with no `slot` attribute) 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 const selector = slotName ? `[slot=\"${slotName}\"]` : ':not([slot])';\n\n return Array.from(host.querySelectorAll<E>(`:scope > ${selector}`));\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to any element — useful for slotted/light DOM content.\n *\n * @example\n * const panel = fixture.query('.panel')!;\n * const { query } = within(panel);\n * expect(query('.title')?.textContent).toBe('Hello');\n */\nexport function within(element: Element): QueryScope {\n return {\n query: <E extends Element = Element>(selector: string) => query<E>(element, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(element, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(element, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(element, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(element, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(element, text, selector),\n };\n}\n"],"mappings":"AAmBA,SAAgB,EAAmC,EAA4B,EAA4B,CACzG,OAAO,EAAK,cAAiB,CAAQ,CACvC,CAGA,SAAgB,EAAsC,EAA4B,EAAuB,CACvG,OAAO,MAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC,CACtD,CAGA,SAAgB,EAA2C,EAA4B,EAA0B,CAC/G,OAAO,EAAK,cAAiB,iBAAiB,EAAO,GAAG,CAC1D,CAGA,SAAgB,EAA8C,EAA4B,EAAqB,CAC7G,OAAO,MAAM,KAAK,EAAK,iBAAoB,iBAAiB,EAAO,GAAG,CAAC,CACzE,CAEA,SAAgB,EACd,EACA,EACA,EACU,CACV,IAAK,IAAM,KAAM,EAAK,iBAAoB,CAAQ,EAChD,GAAI,EAAG,aAAa,KAAK,IAAM,EAAM,OAAO,EAG9C,OAAO,IACT,CAEA,SAAgB,EACd,EACA,EACA,EACK,CACL,OAAO,MAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC,CAAC,CAAC,OAAQ,GAAO,EAAG,aAAa,KAAK,IAAM,CAAI,CACtG,CAOA,SAAgB,EAA2C,EAAe,EAA4B,CACpG,OAAO,EAAK,YAAY,cAAiB,CAAQ,GAAK,IACxD,CAGA,SAAgB,EAA8C,EAAe,EAAuB,CAClG,OAAO,MAAM,KAAK,EAAK,YAAY,iBAAoB,CAAQ,GAAK,CAAC,CAAC,CACxE,CAUA,SAAgB,EAAuC,EAAe,EAAwB,CAC5F,OAAO,EAAiB,EAAM,UAAU,EAAK,GAAG,CAClD,CAUA,SAAgB,EAAwC,EAAe,EAAwB,CAC7F,IAAM,EAAW,EAAW,UAAU,EAAS,IAAM,eAErD,OAAO,MAAM,KAAK,EAAK,iBAAoB,YAAY,GAAU,CAAC,CACpE,CAYA,SAAgB,EAAO,EAA8B,CACnD,MAAO,CACL,MAAqC,GAAqB,EAAS,EAAS,CAAQ,EACpF,SAAwC,GAAqB,EAAY,EAAS,CAAQ,EAC1F,iBAAgD,GAAmB,EAAoB,EAAS,CAAM,EACtG,gBAA8C,EAAc,EAAW,MACrE,EAAkB,EAAS,EAAM,CAAQ,EAC3C,cAA6C,GAAmB,EAAiB,EAAS,CAAM,EAChG,aAA2C,EAAc,EAAW,MAAQ,EAAe,EAAS,EAAM,CAAQ,CACpH,CACF"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * DOM query helpers for test environments.
3
+ */
4
+ /** Scoped query helpers for any DOM element — see {@link within} */
5
+ export interface QueryScope {
6
+ query<E extends Element = Element>(selector: string): E | null;
7
+ queryAll<E extends Element = Element>(selector: string): E[];
8
+ queryByText<E extends Element = Element>(text: string, selector?: string): E | null;
9
+ queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];
10
+ queryByTestId<E extends Element = Element>(testId: string): E | null;
11
+ queryAllByTestId<E extends Element = Element>(testId: string): E[];
12
+ }
13
+ /** Query a single element within `root`. Equivalent to `root.querySelector(selector)`. */
14
+ export declare function query<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E | null;
15
+ /** Query every element within `root` matching `selector`. */
16
+ export declare function queryAll<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E[];
17
+ /** Query a single element within `root` by its `data-testid` attribute. */
18
+ export declare function queryByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E | null;
19
+ /** Query every element within `root` matching a `data-testid` attribute. */
20
+ export declare function queryAllByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E[];
21
+ export declare function queryByText<E extends Element = Element>(root: Element | ShadowRoot, text: string, selector: string): E | null;
22
+ export declare function queryAllByText<E extends Element = Element>(root: Element | ShadowRoot, text: string, selector: string): E[];
23
+ /**
24
+ * Queries the shadow root of a custom element for a matching CSS selector.
25
+ * Returns `null` if the element has no shadow root or no match is found — safe to call on
26
+ * both open-shadow custom elements and plain elements without checking `shadowRoot` first.
27
+ */
28
+ export declare function queryInShadow<E extends Element = Element>(host: Element, selector: string): E | null;
29
+ /** Queries all matching elements inside the shadow root of a custom element. */
30
+ export declare function queryAllInShadow<E extends Element = Element>(host: Element, selector: string): E[];
31
+ /**
32
+ * Queries a shadow DOM element by its CSS `part` attribute — shorthand for
33
+ * `queryInShadow(host, '[part="name"]')`.
34
+ *
35
+ * @example
36
+ * const btn = queryPart(carousel, 'prev-btn');
37
+ * expect(btn).not.toBeNull();
38
+ */
39
+ export declare function queryPart<E extends Element = Element>(host: Element, part: string): E | null;
40
+ /**
41
+ * Returns the light-DOM children assigned to a named slot, or every slotted child (elements
42
+ * with no `slot` attribute) when no name is given.
43
+ *
44
+ * @example
45
+ * const slides = getSlotted(carousel);
46
+ * expect(slides).toHaveLength(3);
47
+ */
48
+ export declare function getSlotted<E extends Element = Element>(host: Element, slotName?: string): E[];
49
+ /**
50
+ * Create query helpers scoped to any element — useful for slotted/light DOM content.
51
+ *
52
+ * @example
53
+ * const panel = fixture.query('.panel')!;
54
+ * const { query } = within(panel);
55
+ * expect(query('.title')?.textContent).toBe('Hello');
56
+ */
57
+ export declare function within(element: Element): QueryScope;
58
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,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,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC;IACpF,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,gBAAgB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,EAAE,CAAC;CACpE;AAID,0FAA0F;AAC1F,wBAAgB,KAAK,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAEzG;AAED,6DAA6D;AAC7D,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,CAAC,EAAE,CAEvG;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAE/G;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,EAAE,CAE7G;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EACrD,IAAI,EAAE,OAAO,GAAG,UAAU,EAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,CAAC,GAAG,IAAI,CAMV;AAED,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,EACxD,IAAI,EAAE,OAAO,GAAG,UAAU,EAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,CAAC,EAAE,CAEL;AAED;;;;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,CAE5F;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;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,CAUnD"}
package/dist/query.js ADDED
@@ -0,0 +1,47 @@
1
+ //#region src/query.ts
2
+ function e(e, t) {
3
+ return e.querySelector(t);
4
+ }
5
+ function t(e, t) {
6
+ return Array.from(e.querySelectorAll(t));
7
+ }
8
+ function n(e, t) {
9
+ return e.querySelector(`[data-testid="${t}"]`);
10
+ }
11
+ function r(e, t) {
12
+ return Array.from(e.querySelectorAll(`[data-testid="${t}"]`));
13
+ }
14
+ function i(e, t, n) {
15
+ for (let r of e.querySelectorAll(n)) if (r.textContent?.trim() === t) return r;
16
+ return null;
17
+ }
18
+ function a(e, t, n) {
19
+ return Array.from(e.querySelectorAll(n)).filter((e) => e.textContent?.trim() === t);
20
+ }
21
+ function o(e, t) {
22
+ return e.shadowRoot?.querySelector(t) ?? null;
23
+ }
24
+ function s(e, t) {
25
+ return Array.from(e.shadowRoot?.querySelectorAll(t) ?? []);
26
+ }
27
+ function c(e, t) {
28
+ return o(e, `[part="${t}"]`);
29
+ }
30
+ function l(e, t) {
31
+ let n = t ? `[slot="${t}"]` : ":not([slot])";
32
+ return Array.from(e.querySelectorAll(`:scope > ${n}`));
33
+ }
34
+ function u(o) {
35
+ return {
36
+ query: (t) => e(o, t),
37
+ queryAll: (e) => t(o, e),
38
+ queryAllByTestId: (e) => r(o, e),
39
+ queryAllByText: (e, t = "*") => a(o, e, t),
40
+ queryByTestId: (e) => n(o, e),
41
+ queryByText: (e, t = "*") => i(o, e, t)
42
+ };
43
+ }
44
+ //#endregion
45
+ export { l as getSlotted, e as query, t as queryAll, r as queryAllByTestId, a as queryAllByText, s as queryAllInShadow, n as queryByTestId, i as queryByText, o as queryInShadow, c as queryPart, u as within };
46
+
47
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","names":[],"sources":["../src/query.ts"],"sourcesContent":["/**\n * DOM query helpers for test environments.\n */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any DOM element — see {@link within} */\nexport interface QueryScope {\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/** Query a single element within `root`. Equivalent to `root.querySelector(selector)`. */\nexport function query<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E | null {\n return root.querySelector<E>(selector);\n}\n\n/** Query every element within `root` matching `selector`. */\nexport function queryAll<E extends Element = Element>(root: Element | ShadowRoot, selector: string): E[] {\n return Array.from(root.querySelectorAll<E>(selector));\n}\n\n/** Query a single element within `root` by its `data-testid` attribute. */\nexport function queryByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E | null {\n return root.querySelector<E>(`[data-testid=\"${testId}\"]`);\n}\n\n/** Query every element within `root` matching a `data-testid` attribute. */\nexport function queryAllByTestId<E extends Element = Element>(root: Element | ShadowRoot, testId: string): E[] {\n return Array.from(root.querySelectorAll<E>(`[data-testid=\"${testId}\"]`));\n}\n\nexport function queryByText<E extends Element = Element>(\n root: Element | ShadowRoot,\n text: string,\n selector: string,\n): 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\nexport function queryAllByText<E extends Element = Element>(\n root: Element | ShadowRoot,\n text: string,\n selector: string,\n): 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?.querySelector<E>(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 Array.from(host.shadowRoot?.querySelectorAll<E>(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 queryInShadow<E>(host, `[part=\"${part}\"]`);\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every slotted child (elements\n * with no `slot` attribute) 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 const selector = slotName ? `[slot=\"${slotName}\"]` : ':not([slot])';\n\n return Array.from(host.querySelectorAll<E>(`:scope > ${selector}`));\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to any element — useful for slotted/light DOM content.\n *\n * @example\n * const panel = fixture.query('.panel')!;\n * const { query } = within(panel);\n * expect(query('.title')?.textContent).toBe('Hello');\n */\nexport function within(element: Element): QueryScope {\n return {\n query: <E extends Element = Element>(selector: string) => query<E>(element, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(element, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(element, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(element, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(element, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(element, text, selector),\n };\n}\n"],"mappings":";AAmBA,SAAgB,EAAmC,GAA4B,GAA4B;CACzG,OAAO,EAAK,cAAiB,CAAQ;AACvC;AAGA,SAAgB,EAAsC,GAA4B,GAAuB;CACvG,OAAO,MAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC;AACtD;AAGA,SAAgB,EAA2C,GAA4B,GAA0B;CAC/G,OAAO,EAAK,cAAiB,iBAAiB,EAAO,GAAG;AAC1D;AAGA,SAAgB,EAA8C,GAA4B,GAAqB;CAC7G,OAAO,MAAM,KAAK,EAAK,iBAAoB,iBAAiB,EAAO,GAAG,CAAC;AACzE;AAEA,SAAgB,EACd,GACA,GACA,GACU;CACV,KAAK,IAAM,KAAM,EAAK,iBAAoB,CAAQ,GAChD,IAAI,EAAG,aAAa,KAAK,MAAM,GAAM,OAAO;CAG9C,OAAO;AACT;AAEA,SAAgB,EACd,GACA,GACA,GACK;CACL,OAAO,MAAM,KAAK,EAAK,iBAAoB,CAAQ,CAAC,CAAC,CAAC,QAAQ,MAAO,EAAG,aAAa,KAAK,MAAM,CAAI;AACtG;AAOA,SAAgB,EAA2C,GAAe,GAA4B;CACpG,OAAO,EAAK,YAAY,cAAiB,CAAQ,KAAK;AACxD;AAGA,SAAgB,EAA8C,GAAe,GAAuB;CAClG,OAAO,MAAM,KAAK,EAAK,YAAY,iBAAoB,CAAQ,KAAK,CAAC,CAAC;AACxE;AAUA,SAAgB,EAAuC,GAAe,GAAwB;CAC5F,OAAO,EAAiB,GAAM,UAAU,EAAK,GAAG;AAClD;AAUA,SAAgB,EAAwC,GAAe,GAAwB;CAC7F,IAAM,IAAW,IAAW,UAAU,EAAS,MAAM;CAErD,OAAO,MAAM,KAAK,EAAK,iBAAoB,YAAY,GAAU,CAAC;AACpE;AAYA,SAAgB,EAAO,GAA8B;CACnD,OAAO;EACL,QAAqC,MAAqB,EAAS,GAAS,CAAQ;EACpF,WAAwC,MAAqB,EAAY,GAAS,CAAQ;EAC1F,mBAAgD,MAAmB,EAAoB,GAAS,CAAM;EACtG,iBAA8C,GAAc,IAAW,QACrE,EAAkB,GAAS,GAAM,CAAQ;EAC3C,gBAA6C,MAAmB,EAAiB,GAAS,CAAM;EAChG,cAA2C,GAAc,IAAW,QAAQ,EAAe,GAAS,GAAM,CAAQ;CACpH;AACF"}
package/dist/wait.cjs ADDED
@@ -0,0 +1,2 @@
1
+ const e=require("./errors.cjs");async function t(t,{interval:n=50,message:r,timeout:a=1e3}={}){let o=Date.now()+a,s;for(;;){try{let e=await t();if(e===void 0||e)return}catch(e){s=e}if(Date.now()>=o)break;await i(n)}let c=`waitFor timed out after ${a}ms`,l=r?`${r} (${c})`:c,u=s instanceof Error?s.message:s==null?void 0:String(s);throw new e.AssayTimeoutError(u?`${l}\n${u}`:l,{cause:s})}function n(t,n,r=1e3){return new Promise((i,a)=>{let o=e=>{clearTimeout(s),i(e)},s=setTimeout(()=>{t.removeEventListener(n,o),a(new e.AssayTimeoutError(`waitForEvent: "${n}" timed out after ${r}ms`))},r);t.addEventListener(n,o,{once:!0})})}var r=()=>new Promise(e=>{queueMicrotask(e)}),i=(e=0)=>new Promise(t=>{setTimeout(t,e)});exports.nextTick=r,exports.wait=i,exports.waitFor=t,exports.waitForEvent=n;
2
+ //# sourceMappingURL=wait.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wait.cjs","names":[],"sources":["../src/wait.ts"],"sourcesContent":["/**\n * Async waiting utilities for test environments.\n */\n\nimport { AssayTimeoutError } from './errors';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface WaitOptions {\n /** Maximum wait time in ms (default: 1000) */\n timeout?: number;\n /** Polling interval in ms (default: 50) */\n interval?: number;\n /** Message included in timeout error */\n message?: string;\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Poll until a callback returns truthy (or void) without throwing.\n * Supports both boolean conditions and `expect()` assertions.\n *\n * - Returns truthy → success\n * - Returns `undefined` (e.g. bare `expect()` call) → success\n * - Returns falsy value → retry\n * - Throws → retry\n *\n * Always rejects with `AssayTimeoutError` on timeout, regardless of which of the two retry\n * reasons above was last observed — `instanceof AssayTimeoutError` is a reliable way to catch\n * \"the condition never became true\" without caring whether the last attempt returned falsy or\n * threw. The original failure (an assertion's message, a thrown error) is preserved as `.cause`,\n * not merged into a mutated copy of it — `fn`'s thrown errors are caller-owned and never mutated.\n *\n * @example\n * await waitFor(() => query('.status')?.textContent === 'loaded');\n * await waitFor(() => expect(count).toBe(3));\n */\nexport async function waitFor(\n fn: () => unknown,\n { interval = 50, message, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n let lastError: unknown;\n\n // Single loop, one `fn()` call site — always tries once more before checking the deadline,\n // so a condition that becomes true exactly as the timeout elapses still succeeds.\n for (;;) {\n try {\n const result = await fn();\n\n if (result === undefined || result) return;\n } catch (e) {\n lastError = e;\n }\n\n if (Date.now() >= deadline) break;\n\n await wait(interval);\n }\n\n // The default timing summary always appears — a custom `message` adds context in front of it\n // rather than replacing it, so a debugging session never loses \"how long did this actually wait\".\n const timing = `waitFor timed out after ${timeout}ms`;\n const base = message ? `${message} (${timing})` : timing;\n const cause = lastError instanceof Error ? lastError.message : lastError != null ? String(lastError) : undefined;\n\n throw new AssayTimeoutError(cause ? `${base}\\n${cause}` : base, { cause: lastError });\n}\n\n/**\n * Resolve when the target element emits the given event.\n *\n * @example\n * const promise = waitForEvent(el, 'change');\n * fire.click(trigger);\n * const event = await promise;\n */\nexport function waitForEvent<T extends Event = Event>(element: Element, name: string, timeout = 1000): Promise<T> {\n return new Promise((resolve, reject) => {\n const onEvent = (e: Event): void => {\n clearTimeout(timer);\n resolve(e as T);\n };\n const timer = setTimeout(() => {\n element.removeEventListener(name, onEvent);\n reject(new AssayTimeoutError(`waitForEvent: \"${name}\" timed out after ${timeout}ms`));\n }, timeout);\n\n element.addEventListener(name, onEvent, { once: true });\n });\n}\n\n/**\n * Resolves after one microtask tick (`queueMicrotask`). Use to wait for reactivity (signal\n * effects, promise-chain continuations) to settle without moving into the macrotask queue —\n * cheaper and more precise than `wait(0)`, which yields to a full `setTimeout` turn instead.\n */\nexport const nextTick = (): Promise<void> =>\n new Promise((resolve) => {\n queueMicrotask(resolve);\n });\n\n/**\n * Resolves after `ms` milliseconds. Prefer `nextTick()` for reactive updates and `waitFor()`\n * for polling a condition — reach for this only when a real macrotask delay is what the code\n * under test actually depends on (e.g. a debounce timer).\n */\nexport const wait = (ms = 0): Promise<void> =>\n new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n"],"mappings":"gCAsCA,eAAsB,EACpB,EACA,CAAE,WAAW,GAAI,UAAS,UAAU,KAAsB,CAAC,EAC5C,CACf,IAAM,EAAW,KAAK,IAAI,EAAI,EAC1B,EAIJ,OAAS,CACP,GAAI,CACF,IAAM,EAAS,MAAM,EAAG,EAExB,GAAI,IAAW,IAAA,IAAa,EAAQ,MACtC,OAAS,EAAG,CACV,EAAY,CACd,CAEA,GAAI,KAAK,IAAI,GAAK,EAAU,MAE5B,MAAM,EAAK,CAAQ,CACrB,CAIA,IAAM,EAAS,2BAA2B,EAAQ,IAC5C,EAAO,EAAU,GAAG,EAAQ,IAAI,EAAO,GAAK,EAC5C,EAAQ,aAAqB,MAAQ,EAAU,QAAU,GAAa,KAA2B,IAAA,GAApB,OAAO,CAAS,EAEnG,MAAM,IAAI,EAAA,kBAAkB,EAAQ,GAAG,EAAK,IAAI,IAAU,EAAM,CAAE,MAAO,CAAU,CAAC,CACtF,CAUA,SAAgB,EAAsC,EAAkB,EAAc,EAAU,IAAkB,CAChH,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,IAAM,EAAW,GAAmB,CAClC,aAAa,CAAK,EAClB,EAAQ,CAAM,CAChB,EACM,EAAQ,eAAiB,CAC7B,EAAQ,oBAAoB,EAAM,CAAO,EACzC,EAAO,IAAI,EAAA,kBAAkB,kBAAkB,EAAK,oBAAoB,EAAQ,GAAG,CAAC,CACtF,EAAG,CAAO,EAEV,EAAQ,iBAAiB,EAAM,EAAS,CAAE,KAAM,EAAK,CAAC,CACxD,CAAC,CACH,CAOA,IAAa,MACX,IAAI,QAAS,GAAY,CACvB,eAAe,CAAO,CACxB,CAAC,EAOU,GAAQ,EAAK,IACxB,IAAI,QAAS,GAAY,CACvB,WAAW,EAAS,CAAE,CACxB,CAAC"}
package/dist/wait.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Async waiting utilities for test environments.
3
+ */
4
+ export interface WaitOptions {
5
+ /** Maximum wait time in ms (default: 1000) */
6
+ timeout?: number;
7
+ /** Polling interval in ms (default: 50) */
8
+ interval?: number;
9
+ /** Message included in timeout error */
10
+ message?: string;
11
+ }
12
+ /**
13
+ * Poll until a callback returns truthy (or void) without throwing.
14
+ * Supports both boolean conditions and `expect()` assertions.
15
+ *
16
+ * - Returns truthy → success
17
+ * - Returns `undefined` (e.g. bare `expect()` call) → success
18
+ * - Returns falsy value → retry
19
+ * - Throws → retry
20
+ *
21
+ * Always rejects with `AssayTimeoutError` on timeout, regardless of which of the two retry
22
+ * reasons above was last observed — `instanceof AssayTimeoutError` is a reliable way to catch
23
+ * "the condition never became true" without caring whether the last attempt returned falsy or
24
+ * threw. The original failure (an assertion's message, a thrown error) is preserved as `.cause`,
25
+ * not merged into a mutated copy of it — `fn`'s thrown errors are caller-owned and never mutated.
26
+ *
27
+ * @example
28
+ * await waitFor(() => query('.status')?.textContent === 'loaded');
29
+ * await waitFor(() => expect(count).toBe(3));
30
+ */
31
+ export declare function waitFor(fn: () => unknown, { interval, message, timeout }?: WaitOptions): Promise<void>;
32
+ /**
33
+ * Resolve when the target element emits the given event.
34
+ *
35
+ * @example
36
+ * const promise = waitForEvent(el, 'change');
37
+ * fire.click(trigger);
38
+ * const event = await promise;
39
+ */
40
+ export declare function waitForEvent<T extends Event = Event>(element: Element, name: string, timeout?: number): Promise<T>;
41
+ /**
42
+ * Resolves after one microtask tick (`queueMicrotask`). Use to wait for reactivity (signal
43
+ * effects, promise-chain continuations) to settle without moving into the macrotask queue —
44
+ * cheaper and more precise than `wait(0)`, which yields to a full `setTimeout` turn instead.
45
+ */
46
+ export declare const nextTick: () => Promise<void>;
47
+ /**
48
+ * Resolves after `ms` milliseconds. Prefer `nextTick()` for reactive updates and `waitFor()`
49
+ * for polling a condition — reach for this only when a real macrotask delay is what the code
50
+ * under test actually depends on (e.g. a debounce timer).
51
+ */
52
+ export declare const wait: (ms?: number) => Promise<void>;
53
+ //# sourceMappingURL=wait.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wait.d.ts","sourceRoot":"","sources":["../src/wait.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,OAAO,CAC3B,EAAE,EAAE,MAAM,OAAO,EACjB,EAAE,QAAa,EAAE,OAAO,EAAE,OAAc,EAAE,GAAE,WAAgB,GAC3D,OAAO,CAAC,IAAI,CAAC,CA2Bf;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,SAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAahH;AAED;;;;GAIG;AACH,eAAO,MAAM,QAAQ,QAAO,OAAO,CAAC,IAAI,CAGpC,CAAC;AAEL;;;;GAIG;AACH,eAAO,MAAM,IAAI,GAAI,WAAM,KAAG,OAAO,CAAC,IAAI,CAGtC,CAAC"}
package/dist/wait.js ADDED
@@ -0,0 +1,36 @@
1
+ import { AssayTimeoutError as e } from "./errors.js";
2
+ //#region src/wait.ts
3
+ async function t(t, { interval: n = 50, message: r, timeout: a = 1e3 } = {}) {
4
+ let o = Date.now() + a, s;
5
+ for (;;) {
6
+ try {
7
+ let e = await t();
8
+ if (e === void 0 || e) return;
9
+ } catch (e) {
10
+ s = e;
11
+ }
12
+ if (Date.now() >= o) break;
13
+ await i(n);
14
+ }
15
+ let c = `waitFor timed out after ${a}ms`, l = r ? `${r} (${c})` : c, u = s instanceof Error ? s.message : s == null ? void 0 : String(s);
16
+ throw new e(u ? `${l}\n${u}` : l, { cause: s });
17
+ }
18
+ function n(t, n, r = 1e3) {
19
+ return new Promise((i, a) => {
20
+ let o = (e) => {
21
+ clearTimeout(s), i(e);
22
+ }, s = setTimeout(() => {
23
+ t.removeEventListener(n, o), a(new e(`waitForEvent: "${n}" timed out after ${r}ms`));
24
+ }, r);
25
+ t.addEventListener(n, o, { once: !0 });
26
+ });
27
+ }
28
+ var r = () => new Promise((e) => {
29
+ queueMicrotask(e);
30
+ }), i = (e = 0) => new Promise((t) => {
31
+ setTimeout(t, e);
32
+ });
33
+ //#endregion
34
+ export { r as nextTick, i as wait, t as waitFor, n as waitForEvent };
35
+
36
+ //# sourceMappingURL=wait.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wait.js","names":[],"sources":["../src/wait.ts"],"sourcesContent":["/**\n * Async waiting utilities for test environments.\n */\n\nimport { AssayTimeoutError } from './errors';\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\nexport interface WaitOptions {\n /** Maximum wait time in ms (default: 1000) */\n timeout?: number;\n /** Polling interval in ms (default: 50) */\n interval?: number;\n /** Message included in timeout error */\n message?: string;\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Poll until a callback returns truthy (or void) without throwing.\n * Supports both boolean conditions and `expect()` assertions.\n *\n * - Returns truthy → success\n * - Returns `undefined` (e.g. bare `expect()` call) → success\n * - Returns falsy value → retry\n * - Throws → retry\n *\n * Always rejects with `AssayTimeoutError` on timeout, regardless of which of the two retry\n * reasons above was last observed — `instanceof AssayTimeoutError` is a reliable way to catch\n * \"the condition never became true\" without caring whether the last attempt returned falsy or\n * threw. The original failure (an assertion's message, a thrown error) is preserved as `.cause`,\n * not merged into a mutated copy of it — `fn`'s thrown errors are caller-owned and never mutated.\n *\n * @example\n * await waitFor(() => query('.status')?.textContent === 'loaded');\n * await waitFor(() => expect(count).toBe(3));\n */\nexport async function waitFor(\n fn: () => unknown,\n { interval = 50, message, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n let lastError: unknown;\n\n // Single loop, one `fn()` call site — always tries once more before checking the deadline,\n // so a condition that becomes true exactly as the timeout elapses still succeeds.\n for (;;) {\n try {\n const result = await fn();\n\n if (result === undefined || result) return;\n } catch (e) {\n lastError = e;\n }\n\n if (Date.now() >= deadline) break;\n\n await wait(interval);\n }\n\n // The default timing summary always appears — a custom `message` adds context in front of it\n // rather than replacing it, so a debugging session never loses \"how long did this actually wait\".\n const timing = `waitFor timed out after ${timeout}ms`;\n const base = message ? `${message} (${timing})` : timing;\n const cause = lastError instanceof Error ? lastError.message : lastError != null ? String(lastError) : undefined;\n\n throw new AssayTimeoutError(cause ? `${base}\\n${cause}` : base, { cause: lastError });\n}\n\n/**\n * Resolve when the target element emits the given event.\n *\n * @example\n * const promise = waitForEvent(el, 'change');\n * fire.click(trigger);\n * const event = await promise;\n */\nexport function waitForEvent<T extends Event = Event>(element: Element, name: string, timeout = 1000): Promise<T> {\n return new Promise((resolve, reject) => {\n const onEvent = (e: Event): void => {\n clearTimeout(timer);\n resolve(e as T);\n };\n const timer = setTimeout(() => {\n element.removeEventListener(name, onEvent);\n reject(new AssayTimeoutError(`waitForEvent: \"${name}\" timed out after ${timeout}ms`));\n }, timeout);\n\n element.addEventListener(name, onEvent, { once: true });\n });\n}\n\n/**\n * Resolves after one microtask tick (`queueMicrotask`). Use to wait for reactivity (signal\n * effects, promise-chain continuations) to settle without moving into the macrotask queue —\n * cheaper and more precise than `wait(0)`, which yields to a full `setTimeout` turn instead.\n */\nexport const nextTick = (): Promise<void> =>\n new Promise((resolve) => {\n queueMicrotask(resolve);\n });\n\n/**\n * Resolves after `ms` milliseconds. Prefer `nextTick()` for reactive updates and `waitFor()`\n * for polling a condition — reach for this only when a real macrotask delay is what the code\n * under test actually depends on (e.g. a debounce timer).\n */\nexport const wait = (ms = 0): Promise<void> =>\n new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n"],"mappings":";;AAsCA,eAAsB,EACpB,GACA,EAAE,cAAW,IAAI,YAAS,aAAU,QAAsB,CAAC,GAC5C;CACf,IAAM,IAAW,KAAK,IAAI,IAAI,GAC1B;CAIJ,SAAS;EACP,IAAI;GACF,IAAM,IAAS,MAAM,EAAG;GAExB,IAAI,MAAW,KAAA,KAAa,GAAQ;EACtC,SAAS,GAAG;GACV,IAAY;EACd;EAEA,IAAI,KAAK,IAAI,KAAK,GAAU;EAE5B,MAAM,EAAK,CAAQ;CACrB;CAIA,IAAM,IAAS,2BAA2B,EAAQ,KAC5C,IAAO,IAAU,GAAG,EAAQ,IAAI,EAAO,KAAK,GAC5C,IAAQ,aAAqB,QAAQ,EAAU,UAAU,KAAa,OAA2B,KAAA,IAApB,OAAO,CAAS;CAEnG,MAAM,IAAI,EAAkB,IAAQ,GAAG,EAAK,IAAI,MAAU,GAAM,EAAE,OAAO,EAAU,CAAC;AACtF;AAUA,SAAgB,EAAsC,GAAkB,GAAc,IAAU,KAAkB;CAChH,OAAO,IAAI,SAAS,GAAS,MAAW;EACtC,IAAM,KAAW,MAAmB;GAElC,AADA,aAAa,CAAK,GAClB,EAAQ,CAAM;EAChB,GACM,IAAQ,iBAAiB;GAE7B,AADA,EAAQ,oBAAoB,GAAM,CAAO,GACzC,EAAO,IAAI,EAAkB,kBAAkB,EAAK,oBAAoB,EAAQ,GAAG,CAAC;EACtF,GAAG,CAAO;EAEV,EAAQ,iBAAiB,GAAM,GAAS,EAAE,MAAM,GAAK,CAAC;CACxD,CAAC;AACH;AAOA,IAAa,UACX,IAAI,SAAS,MAAY;CACvB,eAAe,CAAO;AACxB,CAAC,GAOU,KAAQ,IAAK,MACxB,IAAI,SAAS,MAAY;CACvB,WAAW,GAAS,CAAE;AACxB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@vielzeug/assay",
3
+ "version": "1.0.0",
4
+ "description": "Framework-agnostic DOM testing primitives — scoped queries and synchronous event dispatchers",
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "main": "./dist/index.cjs",
10
+ "module": "./dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "source": "./src/index.ts",
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js",
17
+ "require": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "scripts": {
21
+ "build": "vite build && pnpm run build:bundle && pnpm run build:types",
22
+ "build:types": "tsc -p tsconfig.declarations.json",
23
+ "fix": "eslint --fix src",
24
+ "lint": "eslint src",
25
+ "prepublishOnly": "pnpm run build",
26
+ "preview": "vite preview",
27
+ "test": "vitest",
28
+ "build:bundle": "vite build --config vite.bundle.config.ts"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "registry": "https://registry.npmjs.org/"
33
+ },
34
+ "engines": {
35
+ "node": ">=22"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^26.1.0",
39
+ "jsdom": "^29.1.1",
40
+ "typescript": "^6.0.3",
41
+ "vite": "^8.1.3",
42
+ "vitest": "^4.1.10"
43
+ }
44
+ }