@vielzeug/assay 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -21
- package/dist/assay.cjs +1 -1
- package/dist/assay.cjs.map +1 -1
- package/dist/assay.iife.js +1 -1
- package/dist/assay.iife.js.map +1 -1
- package/dist/assay.js +1 -1
- package/dist/assay.js.map +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +4 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -2
- package/dist/errors.js.map +1 -1
- package/dist/events.cjs.map +1 -1
- package/dist/events.d.ts +18 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/live-region.cjs +1 -1
- package/dist/live-region.cjs.map +1 -1
- package/dist/live-region.d.ts +3 -4
- package/dist/live-region.d.ts.map +1 -1
- package/dist/live-region.js +35 -27
- package/dist/live-region.js.map +1 -1
- package/dist/query.cjs +1 -1
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +1 -1
- package/dist/query.js.map +1 -1
- package/dist/wait.cjs +1 -1
- package/dist/wait.cjs.map +1 -1
- package/dist/wait.d.ts +17 -13
- package/dist/wait.d.ts.map +1 -1
- package/dist/wait.js +64 -31
- package/dist/wait.js.map +1 -1
- package/package.json +11 -7
package/dist/assay.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assay.js","names":[],"sources":["../src/errors.ts","../src/events.ts","../src/wait.ts","../src/live-region.ts","../src/query.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\n/** Thrown when a required query has no match. */\nexport class AssayQueryError extends AssayError {}\n\n/** Thrown by `waitUntil`/`retry`/`waitForEvent` when the timeout elapses. */\nexport class AssayTimeoutError extends AssayError {}\n","/** Synchronous, exact DOM event dispatchers for test environments. */\n\n/** Dispatch an event instance without modifying it. */\nexport const dispatch = (target: EventTarget, event: Event): boolean => target.dispatchEvent(event);\n\nexport const fireBlur = (target: EventTarget, init: FocusEventInit = {}): boolean =>\n dispatch(target, new FocusEvent('blur', init));\n\nexport const fireChange = (target: EventTarget, init: EventInit = {}): boolean =>\n dispatch(target, new Event('change', { bubbles: true, ...init }));\n\nexport const fireClick = (target: EventTarget, init: MouseEventInit = {}): boolean =>\n dispatch(target, new MouseEvent('click', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireCustom = <T>(target: EventTarget, type: string, init: CustomEventInit<T> = {}): boolean =>\n dispatch(target, new CustomEvent(type, { bubbles: true, cancelable: true, composed: false, ...init }));\n\nexport const fireFocus = (target: EventTarget, init: FocusEventInit = {}): boolean =>\n dispatch(target, new FocusEvent('focus', init));\n\nexport const fireInput = (target: EventTarget, init: InputEventInit = {}): boolean =>\n dispatch(target, new InputEvent('input', { bubbles: true, ...init }));\n\nexport const fireKeyDown = (target: EventTarget, init: KeyboardEventInit = {}): boolean =>\n dispatch(target, new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireKeyUp = (target: EventTarget, init: KeyboardEventInit = {}): boolean =>\n dispatch(target, new KeyboardEvent('keyup', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireSubmit = (target: EventTarget, init: SubmitEventInit = {}): boolean =>\n dispatch(target, new SubmitEvent('submit', { bubbles: true, cancelable: true, ...init }));\n","import { AssayTimeoutError } from './errors';\n\nexport interface WaitOptions {\n /** Polling interval in ms (default: 50). */\n interval?: number;\n /** Cancel the pending wait. */\n signal?: AbortSignal;\n /** Maximum wait time in ms (default: 1000). */\n timeout?: number;\n}\n\nexport interface RetryOptions extends WaitOptions {\n /** Context included in the timeout error. */\n message?: string;\n}\n\nexport interface DelayOptions {\n /** Cancel the pending delay. */\n signal?: AbortSignal;\n}\n\nconst abortReason = (signal: AbortSignal): unknown =>\n signal.reason ?? new DOMException('The operation was aborted.', 'AbortError');\n\n/**\n * Resolve after `ms` milliseconds. Rejects with the abort reason when cancelled.\n */\nexport const delay = (ms = 0, { signal }: DelayOptions = {}): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortReason(signal));\n\n return;\n }\n\n const timer = setTimeout(done, ms);\n\n function done(): void {\n signal?.removeEventListener('abort', abort);\n resolve();\n }\n\n function abort(): void {\n clearTimeout(timer);\n reject(abortReason(signal!));\n }\n\n signal?.addEventListener('abort', abort, { once: true });\n });\n\n/** Poll until a synchronous or asynchronous predicate returns `true`. */\nexport async function waitUntil(\n predicate: () => boolean | Promise<boolean>,\n { interval = 50, signal, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n\n for (;;) {\n if (signal?.aborted) throw abortReason(signal);\n\n if (await predicate()) return;\n\n if (Date.now() >= deadline) break;\n\n await delay(interval, { signal });\n }\n\n throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n}\n\n/** Retry an assertion until it stops throwing. */\nexport async function retry(\n assertion: () => void | Promise<void>,\n { interval = 50, message, signal, timeout = 1000 }: RetryOptions = {},\n): Promise<void> {\n const deadline = Date.now() + timeout;\n let lastError: unknown;\n\n for (;;) {\n if (signal?.aborted) throw abortReason(signal);\n\n try {\n await assertion();\n\n return;\n } catch (error) {\n lastError = error;\n }\n\n if (Date.now() >= deadline) break;\n\n await delay(interval, { signal });\n }\n\n const timing = `retry timed out after ${timeout}ms`;\n const context = message ? `${message} (${timing})` : timing;\n const detail = lastError instanceof Error ? lastError.message : lastError == null ? '' : String(lastError);\n\n throw new AssayTimeoutError(detail ? `${context}\\n${detail}` : context, { cause: lastError });\n}\n\n/** Resolve with the next event of `type`, or reject when it times out or is aborted. */\nexport function waitForEvent<T extends Event = Event>(\n target: EventTarget,\n type: string,\n { signal, timeout = 1000 }: Omit<WaitOptions, 'interval'> = {},\n): Promise<T> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortReason(signal));\n\n return;\n }\n\n const dispose = () => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n target.removeEventListener(type, onEvent);\n };\n const onEvent = (e: Event): void => {\n dispose();\n resolve(e as T);\n };\n const timer = setTimeout(() => {\n dispose();\n reject(new AssayTimeoutError(`waitForEvent: \"${type}\" timed out after ${timeout}ms`));\n }, timeout);\n const onAbort = () => {\n dispose();\n reject(abortReason(signal!));\n };\n\n target.addEventListener(type, onEvent, { once: true });\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n\n/** Resolve after one microtask tick. */\nexport const nextTick = (): Promise<void> =>\n new Promise((resolve) => {\n queueMicrotask(resolve);\n });\n","import { AssayQueryError } from './errors';\nimport { retry, type WaitOptions } from './wait';\n\n/** Politeness levels for ARIA live regions. */\nexport type LiveRegionPoliteness = 'assertive' | 'off' | 'polite';\n\n/**\n * ARIA roles that carry an implicit `aria-live` value per the WAI-ARIA spec.\n * `role=\"status\"` → `polite`, `role=\"alert\"` → `assertive`. Elements with these\n * roles are live regions even without an explicit `aria-live` attribute.\n */\nconst IMPLICIT_ROLE_POLITENESS: Record<string, LiveRegionPoliteness> = {\n alert: 'assertive',\n status: 'polite',\n};\n\nexport interface LiveRegionQueryOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live` (or implicit role). Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match (e.g. `'status'`, `'alert'`). Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Resolves the effective politeness of an element: explicit `aria-live` wins,\n * otherwise falls back to the implicit value from `role=\"status\"`/`role=\"alert\"`.\n * Returns `null` when the element is not a live region.\n */\nfunction resolvePoliteness(el: HTMLElement): LiveRegionPoliteness | null {\n const explicit = el.getAttribute('aria-live');\n\n if (explicit) return explicit as LiveRegionPoliteness;\n\n const role = el.getAttribute('role');\n\n if (role && role in IMPLICIT_ROLE_POLITENESS) return IMPLICIT_ROLE_POLITENESS[role];\n\n return null;\n}\n\n/**\n * Queries the first live region matching the given politeness (and optional role)\n * within `root`. Returns `null` when none exists — safe to call before the region\n * has been lazily created.\n *\n * Matches both explicit `aria-live` attributes and implicit roles (`status` →\n * polite, `alert` → assertive) per the WAI-ARIA spec.\n *\n * @example\n * const region = queryLiveRegion({ politeness: 'polite' });\n * expect(region).not.toBeNull();\n */\nexport function queryLiveRegion(options: LiveRegionQueryOptions = {}): HTMLElement | null {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return null;\n\n const candidates = Array.from(root.querySelectorAll<HTMLElement>('[aria-live], [role=\"status\"], [role=\"alert\"]'));\n\n for (const el of candidates) {\n if (resolvePoliteness(el) !== politeness) continue;\n\n if (options.role !== undefined && el.getAttribute('role') !== options.role) continue;\n\n return el;\n }\n\n return null;\n}\n\n/**\n * Queries all live regions matching the given politeness (and optional role)\n * within `root`. Useful for asserting no duplicate regions were created.\n *\n * @example\n * expect(queryAllLiveRegions({ politeness: 'polite' })).toHaveLength(1);\n */\nexport function queryAllLiveRegions(options: LiveRegionQueryOptions = {}): HTMLElement[] {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return [];\n\n return Array.from(root.querySelectorAll<HTMLElement>('[aria-live], [role=\"status\"], [role=\"alert\"]')).filter((el) => {\n if (resolvePoliteness(el) !== politeness) return false;\n\n if (options.role !== undefined && el.getAttribute('role') !== options.role) return false;\n\n return true;\n });\n}\n\nexport interface WaitForLiveRegionOptions extends WaitOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live`. Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match. Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and contains\n * `text` (substring match). Throws `AssayTimeoutError` on timeout.\n *\n * Handles the clear-then-set announce pattern: the region may briefly be empty\n * before the message is written, so polling until the text appears is the\n * correct approach rather than a single snapshot assertion.\n *\n * @example\n * await waitForLiveRegion({ text: '3 results found' });\n * await waitForLiveRegion({ politeness: 'assertive', text: 'Session expired' });\n */\nexport async function waitForLiveRegion(text: string, options: WaitForLiveRegionOptions = {}): Promise<HTMLElement> {\n const politeness = options.politeness ?? 'polite';\n let matched: HTMLElement | null = null;\n\n await retry(\n () => {\n const region = queryLiveRegion({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n\n if (!region) throw new AssayQueryError(`No ${politeness} live region found.`);\n\n if (!region.textContent?.includes(text)) {\n throw new AssayQueryError(`Live region text \"${region.textContent}\" does not include \"${text}\".`);\n }\n\n matched = region;\n },\n {\n interval: options.interval,\n message: `waitForLiveRegion(\"${text}\")`,\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n\n // `retry` only resolves once the assertion stops throwing, so `matched` is set.\n return matched!;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and is empty\n * (cleared). Throws `AssayTimeoutError` on timeout.\n *\n * @example\n * await waitForLiveRegionCleared();\n */\nexport async function waitForLiveRegionCleared(options: WaitForLiveRegionOptions = {}): Promise<void> {\n const politeness = options.politeness ?? 'polite';\n\n await retry(\n () => {\n const region = queryLiveRegion({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n\n if (!region) throw new AssayQueryError(`No ${politeness} live region found.`);\n\n if (region.textContent !== '') {\n throw new AssayQueryError(`Live region not cleared: \"${region.textContent}\".`);\n }\n },\n {\n interval: options.interval,\n message: 'waitForLiveRegionCleared',\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n}\n","import { AssayQueryError } from './errors';\n\n/** DOM query helpers for test environments. */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any queryable DOM root — see {@link within}. */\nexport interface QueryScope {\n get<E extends Element = Element>(selector: string): E;\n getByTestId<E extends Element = Element>(testId: string): E;\n getByText<E extends Element = Element>(text: string, selector?: string): E;\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\ntype QueryRoot = ParentNode;\n\nconst describeRoot = (root: QueryRoot): string => {\n if (root instanceof Element) return root.outerHTML.slice(0, 500);\n\n if (root instanceof ShadowRoot) return `<shadow-root>${root.innerHTML.slice(0, 500)}</shadow-root>`;\n\n return root.nodeName;\n};\n\nconst requireMatch = <E extends Element>(element: E | null, description: string, root: QueryRoot): E => {\n if (element) return element;\n\n throw new AssayQueryError(`Unable to find ${description} within ${describeRoot(root)}.`);\n};\n\nconst query = <E extends Element = Element>(root: QueryRoot, selector: string): E | null =>\n root.querySelector<E>(selector);\n\nconst queryAll = <E extends Element = Element>(root: QueryRoot, selector: string): E[] => {\n return Array.from(root.querySelectorAll<E>(selector));\n};\n\nconst queryByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E | null =>\n queryAll<E>(root, '[data-testid]').find((element) => element.getAttribute('data-testid') === testId) ?? null;\n\nconst queryAllByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E[] =>\n queryAll<E>(root, '[data-testid]').filter((element) => element.getAttribute('data-testid') === testId);\n\nconst queryByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E | null => {\n for (const el of root.querySelectorAll<E>(selector)) {\n if (el.textContent?.trim() === text) return el;\n }\n\n return null;\n};\n\nconst queryAllByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E[] => {\n return Array.from(root.querySelectorAll<E>(selector)).filter((el) => el.textContent?.trim() === text);\n};\n\n/**\n * Queries the shadow root of a custom element for a matching CSS selector.\n * Returns `null` if the element has no shadow root or no match is found — safe to call on\n * both open-shadow custom elements and plain elements without checking `shadowRoot` first.\n */\nexport function queryInShadow<E extends Element = Element>(host: Element, selector: string): E | null {\n return host.shadowRoot ? query<E>(host.shadowRoot, selector) : null;\n}\n\n/** Queries all matching elements inside the shadow root of a custom element. */\nexport function queryAllInShadow<E extends Element = Element>(host: Element, selector: string): E[] {\n return host.shadowRoot ? queryAll<E>(host.shadowRoot, selector) : [];\n}\n\n/**\n * Queries a shadow DOM element by its CSS `part` attribute — shorthand for\n * `queryInShadow(host, '[part=\"name\"]')`.\n *\n * @example\n * const btn = queryPart(carousel, 'prev-btn');\n * expect(btn).not.toBeNull();\n */\nexport function queryPart<E extends Element = Element>(host: Element, part: string): E | null {\n return (\n queryAllInShadow<E>(host, '[part]').find((element) => element.getAttribute('part')?.split(/\\s+/).includes(part)) ??\n null\n );\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every child assigned to the\n * default slot when no name is given.\n *\n * @example\n * const slides = getSlotted(carousel);\n * expect(slides).toHaveLength(3);\n */\nexport function getSlotted<E extends Element = Element>(host: Element, slotName?: string): E[] {\n return Array.from(host.children).filter((element) =>\n slotName === undefined ? !element.getAttribute('slot') : element.getAttribute('slot') === slotName,\n ) as E[];\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to a DOM root — useful for light DOM and shadow roots.\n *\n * @example\n * const view = within(fixture.shadow!);\n * expect(view.get('.title').textContent).toBe('Hello');\n */\nexport function within(root: QueryRoot): QueryScope {\n return {\n get: <E extends Element = Element>(selector: string) =>\n requireMatch(query<E>(root, selector), `\"${selector}\"`, root),\n getByTestId: <E extends Element = Element>(testId: string) =>\n requireMatch(queryByTestId<E>(root, testId), `data-testid \"${testId}\"`, root),\n getByText: <E extends Element = Element>(text: string, selector = '*') =>\n requireMatch(queryByText<E>(root, text, selector), `text \"${text}\" matching \"${selector}\"`, root),\n query: <E extends Element = Element>(selector: string) => query<E>(root, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(root, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(root, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(root, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(root, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(root, text, selector),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"assay.js","names":[],"sources":["../src/errors.ts","../src/events.ts","../src/wait.ts","../src/live-region.ts","../src/query.ts"],"sourcesContent":["/** Base class for all Assay errors. Use `instanceof AssayError` to catch any Assay-originated error. */\nexport class AssayError extends Error {\n protected static readonly errorName: string = 'AssayError';\n\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = (new.target as typeof AssayError).errorName;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a required query has no match. */\nexport class AssayQueryError extends AssayError {\n protected static override readonly errorName = 'AssayQueryError';\n}\n\n/** Thrown when an Assay wait reaches its timeout. */\nexport class AssayTimeoutError extends AssayError {\n protected static override readonly errorName = 'AssayTimeoutError';\n}\n","/**\n * # Confidence boundary — read first\n *\n * `dispatch` is a **low-level synthetic-event dispatch API**. It fires a caller-\n * supplied DOM `Event` synchronously and returns the `dispatchEvent`\n * result. It does **not** model real user interaction: there is no focus/blur\n * sequencing, no input-event coordination, no keyboard layout, no pointer\n * hit-testing, and no trust flag (`event.isTrusted` is always `false`).\n *\n * Use it to drive event handlers in unit/integration tests where you control\n * the exact event shape. For **behavioral confidence** — that a real click\n * actually activates a control, that focus management works end-to-end, that\n * keyboard shortcuts fire under a real input device — write Playwright tests.\n * Assay deliberately stops at the dispatch boundary; anything that depends on\n * the browser's interaction semantics belongs in a browser-driven suite.\n */\n\n/** Dispatch a pre-built `Event` instance unchanged. Returns the `dispatchEvent` result. */\nexport const dispatch = (target: EventTarget, event: Event): boolean => target.dispatchEvent(event);\n\n/** Synthetic convenience dispatchers. They share the same confidence boundary as `dispatch()`. */\nexport const fireBlur = (target: EventTarget, init: FocusEventInit = {}): boolean =>\n dispatch(target, new FocusEvent('blur', init));\n\nexport const fireChange = (target: EventTarget, init: EventInit = {}): boolean =>\n dispatch(target, new Event('change', { bubbles: true, ...init }));\n\nexport const fireClick = (target: EventTarget, init: MouseEventInit = {}): boolean =>\n dispatch(target, new MouseEvent('click', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireCustom = <T>(target: EventTarget, type: string, init: CustomEventInit<T> = {}): boolean =>\n dispatch(target, new CustomEvent(type, { bubbles: true, cancelable: true, composed: false, ...init }));\n\nexport const fireFocus = (target: EventTarget, init: FocusEventInit = {}): boolean =>\n dispatch(target, new FocusEvent('focus', init));\n\nexport const fireInput = (target: EventTarget, init: InputEventInit = {}): boolean =>\n dispatch(target, new InputEvent('input', { bubbles: true, ...init }));\n\nexport const fireKeyDown = (target: EventTarget, init: KeyboardEventInit = {}): boolean =>\n dispatch(target, new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireKeyUp = (target: EventTarget, init: KeyboardEventInit = {}): boolean =>\n dispatch(target, new KeyboardEvent('keyup', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireSubmit = (target: EventTarget, init: SubmitEventInit = {}): boolean =>\n dispatch(target, new SubmitEvent('submit', { bubbles: true, cancelable: true, ...init }));\n","import { abortError } from '@vielzeug/arsenal';\n\nimport { AssayTimeoutError } from './errors';\n\nexport interface DelayOptions {\n signal?: AbortSignal;\n}\n\nexport interface WaitOptions {\n /** Polling interval in ms (default: 50). */\n interval?: number;\n /** Cancel the pending wait. */\n signal?: AbortSignal;\n /** Maximum wait time in ms (default: 1000). */\n timeout?: number;\n}\n\nexport interface EventuallyOptions extends WaitOptions {\n /** Context included in the timeout error. */\n message?: string;\n}\n\nconst DEADLINE = Symbol('assay.deadline');\nconst MAX_TIMER_MS = 2_147_483_647;\nconst now = (): number => performance.now();\n\nconst validateDuration = (name: string, value: number, allowZero: boolean): void => {\n if (!Number.isFinite(value) || value > MAX_TIMER_MS || (allowZero ? value < 0 : value <= 0)) {\n throw new RangeError(\n `${name} must be a ${allowZero ? 'non-negative' : 'positive'} finite number no greater than ${MAX_TIMER_MS}`,\n );\n }\n};\n\nconst validateWait = (interval: number, timeout: number): void => {\n validateDuration('interval', interval, false);\n validateDuration('timeout', timeout, true);\n};\n\nconst sleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal, 'The operation was aborted.'));\n return;\n }\n\n const timer = setTimeout(done, ms);\n\n function done(): void {\n signal?.removeEventListener('abort', abort);\n resolve();\n }\n\n function abort(): void {\n clearTimeout(timer);\n reject(abortError(signal, 'The operation was aborted.'));\n }\n\n signal?.addEventListener('abort', abort, { once: true });\n });\n\nconst runBounded = <T>(run: () => T | Promise<T>, deadline: number, signal?: AbortSignal): Promise<T> =>\n new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal, 'The operation was aborted.'));\n return;\n }\n\n let settled = false;\n const cleanup = (): boolean => {\n if (settled) return false;\n settled = true;\n clearTimeout(timer);\n signal?.removeEventListener('abort', abort);\n return true;\n };\n const resolveOnce = (value: T): void => {\n if (cleanup()) resolve(value);\n };\n const rejectOnce = (error: unknown): void => {\n if (cleanup()) reject(error);\n };\n const timer = setTimeout(() => rejectOnce(DEADLINE), Math.max(0, deadline - now()));\n const abort = () => rejectOnce(abortError(signal, 'The operation was aborted.'));\n\n signal?.addEventListener('abort', abort, { once: true });\n\n try {\n Promise.resolve(run()).then(resolveOnce, rejectOnce);\n } catch (error) {\n rejectOnce(error);\n }\n });\n\nexport const delay = (ms = 0, options: DelayOptions = {}): Promise<void> => {\n validateDuration('ms', ms, true);\n return sleep(ms, options.signal);\n};\n\nexport const nextTick = (): Promise<void> => new Promise((resolve) => queueMicrotask(resolve));\n\nexport async function waitUntil(\n predicate: () => boolean | Promise<boolean>,\n { interval = 50, signal, timeout = 1000 }: WaitOptions = {},\n): Promise<void> {\n validateWait(interval, timeout);\n const deadline = now() + timeout;\n let first = true;\n\n for (;;) {\n if (signal?.aborted) throw abortError(signal, 'The operation was aborted.');\n if (!first && now() >= deadline) throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n first = false;\n\n let matched: boolean;\n try {\n matched = await runBounded(predicate, deadline, signal);\n } catch (error) {\n if (error === DEADLINE) throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n throw error;\n }\n if (matched) return;\n\n const remaining = deadline - now();\n if (remaining <= 0) throw new AssayTimeoutError(`waitUntil timed out after ${timeout}ms`);\n await sleep(Math.min(interval, remaining), signal);\n }\n}\n\n/**\n * Retry an assertion until it stops throwing or the timeout elapses.\n *\n * Polls the assertion on a fixed interval and preserves the last failure as the\n * timeout cause. Pending asynchronous assertions remain bounded by the same\n * deadline. Use `waitUntil()` for boolean conditions, `delay()` for explicit\n * timer dependencies, and `nextTick()` for one queued microtask boundary.\n *\n * @example\n * await eventually(() => expect(region.textContent).toBe('Saved'));\n */\nexport async function eventually(\n assertion: () => void | Promise<void>,\n { interval = 50, message, signal, timeout = 1000 }: EventuallyOptions = {},\n): Promise<void> {\n validateWait(interval, timeout);\n const deadline = now() + timeout;\n let first = true;\n let lastError: unknown;\n\n for (;;) {\n if (signal?.aborted) throw abortError(signal, 'The operation was aborted.');\n if (!first && now() >= deadline) break;\n first = false;\n\n try {\n await runBounded(assertion, deadline, signal);\n return;\n } catch (error) {\n if (signal?.aborted) throw abortError(signal, 'The operation was aborted.');\n if (error === DEADLINE) break;\n lastError = error;\n }\n\n const remaining = deadline - now();\n if (remaining <= 0) break;\n await sleep(Math.min(interval, remaining), signal);\n }\n\n const timing = `eventually timed out after ${timeout}ms`;\n const context = message ? `${message} (${timing})` : timing;\n const detail = lastError instanceof Error ? lastError.message : lastError == null ? '' : String(lastError);\n\n throw new AssayTimeoutError(detail ? `${context}\\n${detail}` : context, { cause: lastError });\n}\n\n/** Resolve with the next event of `type`, or reject when it times out or is aborted. */\nexport function waitForEvent<T extends Event = Event>(\n target: EventTarget,\n type: string,\n { signal, timeout = 1000 }: Omit<WaitOptions, 'interval'> = {},\n): Promise<T> {\n validateDuration('timeout', timeout, true);\n\n return new Promise((resolve, reject) => {\n if (signal?.aborted) {\n reject(abortError(signal, 'The operation was aborted.'));\n return;\n }\n\n const dispose = () => {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onAbort);\n target.removeEventListener(type, onEvent);\n };\n const onEvent = (event: Event): void => {\n dispose();\n resolve(event as T);\n };\n const timer = setTimeout(() => {\n dispose();\n reject(new AssayTimeoutError(`waitForEvent: \"${type}\" timed out after ${timeout}ms`));\n }, timeout);\n const onAbort = () => {\n dispose();\n reject(abortError(signal, 'The operation was aborted.'));\n };\n\n target.addEventListener(type, onEvent, { once: true });\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","import { AssayQueryError } from './errors';\nimport { eventually, type WaitOptions } from './wait';\n\n/** Politeness levels for ARIA live regions. */\nexport type LiveRegionPoliteness = 'assertive' | 'off' | 'polite';\n\n/**\n * ARIA roles that carry an implicit `aria-live` value per the WAI-ARIA spec.\n * `alert` is assertive; `log` and `status` are polite; `marquee` and `timer` are\n * off. Elements with these roles are live regions without explicit `aria-live`.\n */\nconst IMPLICIT_ROLE_POLITENESS: Record<string, LiveRegionPoliteness> = {\n alert: 'assertive',\n log: 'polite',\n marquee: 'off',\n status: 'polite',\n timer: 'off',\n};\nconst LIVE_REGION_SELECTOR =\n '[aria-live], [role=\"alert\"], [role=\"log\"], [role=\"marquee\"], [role=\"status\"], [role=\"timer\"]';\n\nexport interface LiveRegionQueryOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live` (or implicit role). Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match (e.g. `'status'`, `'alert'`). Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Resolves the effective politeness of an element: explicit `aria-live` wins,\n * otherwise falls back to the implicit value from `role=\"status\"`/`role=\"alert\"`.\n * Returns `null` when the element is not a live region.\n */\nfunction resolvePoliteness(el: HTMLElement): LiveRegionPoliteness | null {\n const explicit = el.getAttribute('aria-live');\n\n if (explicit) return explicit as LiveRegionPoliteness;\n\n const role = el.getAttribute('role');\n\n if (role && role in IMPLICIT_ROLE_POLITENESS) return IMPLICIT_ROLE_POLITENESS[role];\n\n return null;\n}\n\nfunction candidates(root: ParentNode): HTMLElement[] {\n const descendants = Array.from(root.querySelectorAll<HTMLElement>(LIVE_REGION_SELECTOR));\n const rootElement = root as ParentNode & { matches?: (selector: string) => boolean };\n\n return rootElement.matches?.(LIVE_REGION_SELECTOR) ? [root as HTMLElement, ...descendants] : descendants;\n}\n\nfunction matchesOptions(element: HTMLElement, politeness: LiveRegionPoliteness, role?: string): boolean {\n return resolvePoliteness(element) === politeness && (role === undefined || element.getAttribute('role') === role);\n}\n\n/**\n * Queries the first live region matching the given politeness (and optional role)\n * within `root`. Returns `null` when none exists — safe to call before the region\n * has been lazily created.\n *\n * Matches explicit `aria-live` attributes and implicit live-region roles.\n *\n * @example\n * const region = queryLiveRegion({ politeness: 'polite' });\n * expect(region).not.toBeNull();\n */\nexport function queryLiveRegion(options: LiveRegionQueryOptions = {}): HTMLElement | null {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return null;\n\n return candidates(root).find((element) => matchesOptions(element, politeness, options.role)) ?? null;\n}\n\n/**\n * Queries all live regions matching the given politeness (and optional role)\n * within `root`. Useful for asserting no duplicate regions were created.\n *\n * @example\n * expect(queryAllLiveRegions({ politeness: 'polite' })).toHaveLength(1);\n */\nexport function queryAllLiveRegions(options: LiveRegionQueryOptions = {}): HTMLElement[] {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return [];\n\n return candidates(root).filter((element) => matchesOptions(element, politeness, options.role));\n}\n\nexport interface WaitForLiveRegionOptions extends WaitOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live`. Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match. Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and contains\n * `text` (substring match). Throws `AssayTimeoutError` on timeout.\n *\n * Handles the clear-then-set announce pattern: the region may briefly be empty\n * before the message is written, so polling until the text appears is the\n * correct approach rather than a single snapshot assertion.\n *\n * @example\n * await waitForLiveRegion('3 results found');\n * await waitForLiveRegion('Session expired', { politeness: 'assertive' });\n */\nexport async function waitForLiveRegion(text: string, options: WaitForLiveRegionOptions = {}): Promise<HTMLElement> {\n const politeness = options.politeness ?? 'polite';\n let matched: HTMLElement | null = null;\n\n await eventually(\n () => {\n const regions = queryAllLiveRegions({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n const region = regions.find((candidate) => candidate.textContent?.includes(text));\n\n if (region) {\n matched = region;\n return;\n }\n\n if (regions.length === 0) throw new AssayQueryError(`No ${politeness} live region found.`);\n throw new AssayQueryError(`No ${politeness} live region includes \"${text}\".`);\n },\n {\n interval: options.interval,\n message: `waitForLiveRegion(\"${text}\")`,\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n\n // `eventually` only resolves once the assertion stops throwing, so `matched` is set.\n return matched!;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and is empty\n * (cleared). Throws `AssayTimeoutError` on timeout.\n *\n * @example\n * await waitForLiveRegionCleared();\n */\nexport async function waitForLiveRegionCleared(options: WaitForLiveRegionOptions = {}): Promise<void> {\n const politeness = options.politeness ?? 'polite';\n\n await eventually(\n () => {\n const regions = queryAllLiveRegions({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n\n if (regions.some((region) => region.textContent === '')) return;\n if (regions.length === 0) throw new AssayQueryError(`No ${politeness} live region found.`);\n throw new AssayQueryError(`No ${politeness} live region is cleared.`);\n },\n {\n interval: options.interval,\n message: 'waitForLiveRegionCleared',\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n}\n","import { AssayQueryError } from './errors';\n\n/** DOM query helpers for test environments. */\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/** Scoped query helpers for any queryable DOM root — see {@link within}. */\nexport interface QueryScope {\n get<E extends Element = Element>(selector: string): E;\n getByTestId<E extends Element = Element>(testId: string): E;\n getByText<E extends Element = Element>(text: string, selector?: string): E;\n query<E extends Element = Element>(selector: string): E | null;\n queryAll<E extends Element = Element>(selector: string): E[];\n queryAllByTestId<E extends Element = Element>(testId: string): E[];\n queryAllByText<E extends Element = Element>(text: string, selector?: string): E[];\n queryByTestId<E extends Element = Element>(testId: string): E | null;\n queryByText<E extends Element = Element>(text: string, selector?: string): E | null;\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\ntype QueryRoot = ParentNode;\n\nconst describeRoot = (root: QueryRoot): string => {\n if (root instanceof Element) return root.outerHTML.slice(0, 500);\n\n if (root instanceof ShadowRoot) return `<shadow-root>${root.innerHTML.slice(0, 500)}</shadow-root>`;\n\n return root.nodeName;\n};\n\nconst requireMatch = <E extends Element>(element: E | null, description: string, root: QueryRoot): E => {\n if (element) return element;\n\n throw new AssayQueryError(`Unable to find ${description} within ${describeRoot(root)}.`);\n};\n\nconst query = <E extends Element = Element>(root: QueryRoot, selector: string): E | null =>\n root.querySelector<E>(selector);\n\nconst queryAll = <E extends Element = Element>(root: QueryRoot, selector: string): E[] => {\n return Array.from(root.querySelectorAll<E>(selector));\n};\n\nconst queryByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E | null =>\n queryAll<E>(root, '[data-testid]').find((element) => element.getAttribute('data-testid') === testId) ?? null;\n\nconst queryAllByTestId = <E extends Element = Element>(root: QueryRoot, testId: string): E[] =>\n queryAll<E>(root, '[data-testid]').filter((element) => element.getAttribute('data-testid') === testId);\n\nconst queryByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E | null => {\n for (const element of root.querySelectorAll<E>(selector)) {\n if (element.textContent?.trim() === text) return element;\n }\n\n return null;\n};\n\nconst queryAllByText = <E extends Element = Element>(root: QueryRoot, text: string, selector = '*'): E[] =>\n queryAll<E>(root, selector).filter((element) => element.textContent?.trim() === text);\n\n/**\n * Queries the shadow root of a custom element for a matching CSS selector.\n * Returns `null` if the element has no shadow root or no match is found — safe to call on\n * both open-shadow custom elements and plain elements without checking `shadowRoot` first.\n */\nexport function queryInShadow<E extends Element = Element>(host: Element, selector: string): E | null {\n return host.shadowRoot ? query<E>(host.shadowRoot, selector) : null;\n}\n\n/** Queries all matching elements inside the shadow root of a custom element. */\nexport function queryAllInShadow<E extends Element = Element>(host: Element, selector: string): E[] {\n return host.shadowRoot ? queryAll<E>(host.shadowRoot, selector) : [];\n}\n\n/**\n * Queries a shadow DOM element by its CSS `part` attribute — shorthand for\n * `queryInShadow(host, '[part=\"name\"]')`.\n *\n * @example\n * const btn = queryPart(carousel, 'prev-btn');\n * expect(btn).not.toBeNull();\n */\nexport function queryPart<E extends Element = Element>(host: Element, part: string): E | null {\n return (\n queryAllInShadow<E>(host, '[part]').find((element) => element.getAttribute('part')?.split(/\\s+/).includes(part)) ??\n null\n );\n}\n\n/**\n * Returns the light-DOM children assigned to a named slot, or every child assigned to the\n * default slot when no name is given.\n *\n * @example\n * const slides = getSlotted(carousel);\n * expect(slides).toHaveLength(3);\n */\nexport function getSlotted<E extends Element = Element>(host: Element, slotName?: string): E[] {\n return Array.from(host.children).filter((element) =>\n slotName === undefined ? !element.getAttribute('slot') : element.getAttribute('slot') === slotName,\n ) as E[];\n}\n\n// ─── API ─────────────────────────────────────────────────────────────────────\n\n/**\n * Create query helpers scoped to a DOM root — useful for light DOM and shadow roots.\n *\n * @example\n * const view = within(fixture.shadow!);\n * expect(view.get('.title').textContent).toBe('Hello');\n */\nexport function within(root: QueryRoot): QueryScope {\n return {\n get: <E extends Element = Element>(selector: string) =>\n requireMatch(query<E>(root, selector), `\"${selector}\"`, root),\n getByTestId: <E extends Element = Element>(testId: string) =>\n requireMatch(queryByTestId<E>(root, testId), `data-testid \"${testId}\"`, root),\n getByText: <E extends Element = Element>(text: string, selector = '*') =>\n requireMatch(queryByText<E>(root, text, selector), `text \"${text}\" matching \"${selector}\"`, root),\n query: <E extends Element = Element>(selector: string) => query<E>(root, selector),\n queryAll: <E extends Element = Element>(selector: string) => queryAll<E>(root, selector),\n queryAllByTestId: <E extends Element = Element>(testId: string) => queryAllByTestId<E>(root, testId),\n queryAllByText: <E extends Element = Element>(text: string, selector = '*') =>\n queryAllByText<E>(root, text, selector),\n queryByTestId: <E extends Element = Element>(testId: string) => queryByTestId<E>(root, testId),\n queryByText: <E extends Element = Element>(text: string, selector = '*') => queryByText<E>(root, text, selector),\n };\n}\n"],"mappings":"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"}
|
package/dist/errors.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.
|
|
1
|
+
var e=class extends Error{static errorName=`AssayError`;constructor(e,t){super(e,t),this.name=new.target.errorName,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{static errorName=`AssayQueryError`},n=class extends e{static errorName=`AssayTimeoutError`};exports.AssayError=e,exports.AssayQueryError=t,exports.AssayTimeoutError=n;
|
|
2
2
|
//# sourceMappingURL=errors.cjs.map
|
package/dist/errors.cjs.map
CHANGED
|
@@ -1 +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.
|
|
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 protected static readonly errorName: string = 'AssayError';\n\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = (new.target as typeof AssayError).errorName;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a required query has no match. */\nexport class AssayQueryError extends AssayError {\n protected static override readonly errorName = 'AssayQueryError';\n}\n\n/** Thrown when an Assay wait reaches its timeout. */\nexport class AssayTimeoutError extends AssayError {\n protected static override readonly errorName = 'AssayTimeoutError';\n}\n"],"mappings":"AACA,IAAa,EAAb,cAAgC,KAAM,CACpC,OAA0B,UAAoB,aAE9C,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAQ,WAAiC,UAC9C,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,EAAb,cAAqC,CAAW,CAC9C,OAAmC,UAAY,iBACjD,EAGa,EAAb,cAAuC,CAAW,CAChD,OAAmC,UAAY,mBACjD"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/** Base class for all Assay errors. Use `instanceof AssayError` to catch any Assay-originated error. */
|
|
2
2
|
export declare class AssayError extends Error {
|
|
3
|
+
protected static readonly errorName: string;
|
|
3
4
|
constructor(message: string, opts?: ErrorOptions);
|
|
4
5
|
}
|
|
5
6
|
/** Thrown when a required query has no match. */
|
|
6
7
|
export declare class AssayQueryError extends AssayError {
|
|
8
|
+
protected static readonly errorName = "AssayQueryError";
|
|
7
9
|
}
|
|
8
|
-
/** Thrown
|
|
10
|
+
/** Thrown when an Assay wait reaches its timeout. */
|
|
9
11
|
export declare class AssayTimeoutError extends AssayError {
|
|
12
|
+
protected static readonly errorName = "AssayTimeoutError";
|
|
10
13
|
}
|
|
11
14
|
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,qBAAa,UAAW,SAAQ,KAAK;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,qBAAa,UAAW,SAAQ,KAAK;IACnC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAgB;gBAE/C,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;CAKjD;AAED,iDAAiD;AACjD,qBAAa,eAAgB,SAAQ,UAAU;IAC7C,0BAAmC,SAAS,qBAAqB;CAClE;AAED,qDAAqD;AACrD,qBAAa,iBAAkB,SAAQ,UAAU;IAC/C,0BAAmC,SAAS,uBAAuB;CACpE"}
|
package/dist/errors.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
//#region src/errors.ts
|
|
2
2
|
var e = class extends Error {
|
|
3
|
+
static errorName = "AssayError";
|
|
3
4
|
constructor(e, t) {
|
|
4
|
-
super(e, t), this.name = new.target.
|
|
5
|
+
super(e, t), this.name = new.target.errorName, Object.setPrototypeOf(this, new.target.prototype);
|
|
5
6
|
}
|
|
6
|
-
}, t = class extends e {
|
|
7
|
+
}, t = class extends e {
|
|
8
|
+
static errorName = "AssayQueryError";
|
|
9
|
+
}, n = class extends e {
|
|
10
|
+
static errorName = "AssayTimeoutError";
|
|
11
|
+
};
|
|
7
12
|
//#endregion
|
|
8
13
|
export { e as AssayError, t as AssayQueryError, n as AssayTimeoutError };
|
|
9
14
|
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +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.
|
|
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 protected static readonly errorName: string = 'AssayError';\n\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = (new.target as typeof AssayError).errorName;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when a required query has no match. */\nexport class AssayQueryError extends AssayError {\n protected static override readonly errorName = 'AssayQueryError';\n}\n\n/** Thrown when an Assay wait reaches its timeout. */\nexport class AssayTimeoutError extends AssayError {\n protected static override readonly errorName = 'AssayTimeoutError';\n}\n"],"mappings":";AACA,IAAa,IAAb,cAAgC,MAAM;CACpC,OAA0B,YAAoB;CAE9C,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAQ,WAAiC,WAC9C,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAGa,IAAb,cAAqC,EAAW;CAC9C,OAAmC,YAAY;AACjD,GAGa,IAAb,cAAuC,EAAW;CAChD,OAAmC,YAAY;AACjD"}
|
package/dist/events.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.cjs","names":[],"sources":["../src/events.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"events.cjs","names":[],"sources":["../src/events.ts"],"sourcesContent":["/**\n * # Confidence boundary — read first\n *\n * `dispatch` is a **low-level synthetic-event dispatch API**. It fires a caller-\n * supplied DOM `Event` synchronously and returns the `dispatchEvent`\n * result. It does **not** model real user interaction: there is no focus/blur\n * sequencing, no input-event coordination, no keyboard layout, no pointer\n * hit-testing, and no trust flag (`event.isTrusted` is always `false`).\n *\n * Use it to drive event handlers in unit/integration tests where you control\n * the exact event shape. For **behavioral confidence** — that a real click\n * actually activates a control, that focus management works end-to-end, that\n * keyboard shortcuts fire under a real input device — write Playwright tests.\n * Assay deliberately stops at the dispatch boundary; anything that depends on\n * the browser's interaction semantics belongs in a browser-driven suite.\n */\n\n/** Dispatch a pre-built `Event` instance unchanged. Returns the `dispatchEvent` result. */\nexport const dispatch = (target: EventTarget, event: Event): boolean => target.dispatchEvent(event);\n\n/** Synthetic convenience dispatchers. They share the same confidence boundary as `dispatch()`. */\nexport const fireBlur = (target: EventTarget, init: FocusEventInit = {}): boolean =>\n dispatch(target, new FocusEvent('blur', init));\n\nexport const fireChange = (target: EventTarget, init: EventInit = {}): boolean =>\n dispatch(target, new Event('change', { bubbles: true, ...init }));\n\nexport const fireClick = (target: EventTarget, init: MouseEventInit = {}): boolean =>\n dispatch(target, new MouseEvent('click', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireCustom = <T>(target: EventTarget, type: string, init: CustomEventInit<T> = {}): boolean =>\n dispatch(target, new CustomEvent(type, { bubbles: true, cancelable: true, composed: false, ...init }));\n\nexport const fireFocus = (target: EventTarget, init: FocusEventInit = {}): boolean =>\n dispatch(target, new FocusEvent('focus', init));\n\nexport const fireInput = (target: EventTarget, init: InputEventInit = {}): boolean =>\n dispatch(target, new InputEvent('input', { bubbles: true, ...init }));\n\nexport const fireKeyDown = (target: EventTarget, init: KeyboardEventInit = {}): boolean =>\n dispatch(target, new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireKeyUp = (target: EventTarget, init: KeyboardEventInit = {}): boolean =>\n dispatch(target, new KeyboardEvent('keyup', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireSubmit = (target: EventTarget, init: SubmitEventInit = {}): boolean =>\n dispatch(target, new SubmitEvent('submit', { bubbles: true, cancelable: true, ...init }));\n"],"mappings":"AAkBA,IAAa,GAAY,EAAqB,IAA0B,EAAO,cAAc,CAAK,EAGrF,GAAY,EAAqB,EAAuB,CAAC,IACpE,EAAS,EAAQ,IAAI,WAAW,OAAQ,CAAI,CAAC,EAElC,GAAc,EAAqB,EAAkB,CAAC,IACjE,EAAS,EAAQ,IAAI,MAAM,SAAU,CAAE,QAAS,GAAM,GAAG,CAAK,CAAC,CAAC,EAErD,GAAa,EAAqB,EAAuB,CAAC,IACrE,EAAS,EAAQ,IAAI,WAAW,QAAS,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAE3E,GAAiB,EAAqB,EAAc,EAA2B,CAAC,IAC3F,EAAS,EAAQ,IAAI,YAAY,EAAM,CAAE,QAAS,GAAM,WAAY,GAAM,SAAU,GAAO,GAAG,CAAK,CAAC,CAAC,EAE1F,GAAa,EAAqB,EAAuB,CAAC,IACrE,EAAS,EAAQ,IAAI,WAAW,QAAS,CAAI,CAAC,EAEnC,GAAa,EAAqB,EAAuB,CAAC,IACrE,EAAS,EAAQ,IAAI,WAAW,QAAS,CAAE,QAAS,GAAM,GAAG,CAAK,CAAC,CAAC,EAEzD,GAAe,EAAqB,EAA0B,CAAC,IAC1E,EAAS,EAAQ,IAAI,cAAc,UAAW,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAEhF,GAAa,EAAqB,EAA0B,CAAC,IACxE,EAAS,EAAQ,IAAI,cAAc,QAAS,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC,EAE9E,GAAc,EAAqB,EAAwB,CAAC,IACvE,EAAS,EAAQ,IAAI,YAAY,SAAU,CAAE,QAAS,GAAM,WAAY,GAAM,GAAG,CAAK,CAAC,CAAC"}
|
package/dist/events.d.ts
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* # Confidence boundary — read first
|
|
3
|
+
*
|
|
4
|
+
* `dispatch` is a **low-level synthetic-event dispatch API**. It fires a caller-
|
|
5
|
+
* supplied DOM `Event` synchronously and returns the `dispatchEvent`
|
|
6
|
+
* result. It does **not** model real user interaction: there is no focus/blur
|
|
7
|
+
* sequencing, no input-event coordination, no keyboard layout, no pointer
|
|
8
|
+
* hit-testing, and no trust flag (`event.isTrusted` is always `false`).
|
|
9
|
+
*
|
|
10
|
+
* Use it to drive event handlers in unit/integration tests where you control
|
|
11
|
+
* the exact event shape. For **behavioral confidence** — that a real click
|
|
12
|
+
* actually activates a control, that focus management works end-to-end, that
|
|
13
|
+
* keyboard shortcuts fire under a real input device — write Playwright tests.
|
|
14
|
+
* Assay deliberately stops at the dispatch boundary; anything that depends on
|
|
15
|
+
* the browser's interaction semantics belongs in a browser-driven suite.
|
|
16
|
+
*/
|
|
17
|
+
/** Dispatch a pre-built `Event` instance unchanged. Returns the `dispatchEvent` result. */
|
|
3
18
|
export declare const dispatch: (target: EventTarget, event: Event) => boolean;
|
|
19
|
+
/** Synthetic convenience dispatchers. They share the same confidence boundary as `dispatch()`. */
|
|
4
20
|
export declare const fireBlur: (target: EventTarget, init?: FocusEventInit) => boolean;
|
|
5
21
|
export declare const fireChange: (target: EventTarget, init?: EventInit) => boolean;
|
|
6
22
|
export declare const fireClick: (target: EventTarget, init?: MouseEventInit) => boolean;
|
package/dist/events.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,2FAA2F;AAC3F,eAAO,MAAM,QAAQ,GAAI,QAAQ,WAAW,EAAE,OAAO,KAAK,KAAG,OAAsC,CAAC;AAEpG,kGAAkG;AAClG,eAAO,MAAM,QAAQ,GAAI,QAAQ,WAAW,EAAE,OAAM,cAAmB,KAAG,OAC1B,CAAC;AAEjD,eAAO,MAAM,UAAU,GAAI,QAAQ,WAAW,EAAE,OAAM,SAAc,KAAG,OACJ,CAAC;AAEpE,eAAO,MAAM,SAAS,GAAI,QAAQ,WAAW,EAAE,OAAM,cAAmB,KAAG,OACc,CAAC;AAE1F,eAAO,MAAM,UAAU,GAAI,CAAC,EAAE,QAAQ,WAAW,EAAE,MAAM,MAAM,EAAE,OAAM,eAAe,CAAC,CAAC,CAAM,KAAG,OACO,CAAC;AAEzG,eAAO,MAAM,SAAS,GAAI,QAAQ,WAAW,EAAE,OAAM,cAAmB,KAAG,OAC1B,CAAC;AAElD,eAAO,MAAM,SAAS,GAAI,QAAQ,WAAW,EAAE,OAAM,cAAmB,KAAG,OACJ,CAAC;AAExE,eAAO,MAAM,WAAW,GAAI,QAAQ,WAAW,EAAE,OAAM,iBAAsB,KAAG,OACc,CAAC;AAE/F,eAAO,MAAM,SAAS,GAAI,QAAQ,WAAW,EAAE,OAAM,iBAAsB,KAAG,OACc,CAAC;AAE7F,eAAO,MAAM,UAAU,GAAI,QAAQ,WAAW,EAAE,OAAM,eAAoB,KAAG,OACc,CAAC"}
|
package/dist/events.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","names":[],"sources":["../src/events.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../src/events.ts"],"sourcesContent":["/**\n * # Confidence boundary — read first\n *\n * `dispatch` is a **low-level synthetic-event dispatch API**. It fires a caller-\n * supplied DOM `Event` synchronously and returns the `dispatchEvent`\n * result. It does **not** model real user interaction: there is no focus/blur\n * sequencing, no input-event coordination, no keyboard layout, no pointer\n * hit-testing, and no trust flag (`event.isTrusted` is always `false`).\n *\n * Use it to drive event handlers in unit/integration tests where you control\n * the exact event shape. For **behavioral confidence** — that a real click\n * actually activates a control, that focus management works end-to-end, that\n * keyboard shortcuts fire under a real input device — write Playwright tests.\n * Assay deliberately stops at the dispatch boundary; anything that depends on\n * the browser's interaction semantics belongs in a browser-driven suite.\n */\n\n/** Dispatch a pre-built `Event` instance unchanged. Returns the `dispatchEvent` result. */\nexport const dispatch = (target: EventTarget, event: Event): boolean => target.dispatchEvent(event);\n\n/** Synthetic convenience dispatchers. They share the same confidence boundary as `dispatch()`. */\nexport const fireBlur = (target: EventTarget, init: FocusEventInit = {}): boolean =>\n dispatch(target, new FocusEvent('blur', init));\n\nexport const fireChange = (target: EventTarget, init: EventInit = {}): boolean =>\n dispatch(target, new Event('change', { bubbles: true, ...init }));\n\nexport const fireClick = (target: EventTarget, init: MouseEventInit = {}): boolean =>\n dispatch(target, new MouseEvent('click', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireCustom = <T>(target: EventTarget, type: string, init: CustomEventInit<T> = {}): boolean =>\n dispatch(target, new CustomEvent(type, { bubbles: true, cancelable: true, composed: false, ...init }));\n\nexport const fireFocus = (target: EventTarget, init: FocusEventInit = {}): boolean =>\n dispatch(target, new FocusEvent('focus', init));\n\nexport const fireInput = (target: EventTarget, init: InputEventInit = {}): boolean =>\n dispatch(target, new InputEvent('input', { bubbles: true, ...init }));\n\nexport const fireKeyDown = (target: EventTarget, init: KeyboardEventInit = {}): boolean =>\n dispatch(target, new KeyboardEvent('keydown', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireKeyUp = (target: EventTarget, init: KeyboardEventInit = {}): boolean =>\n dispatch(target, new KeyboardEvent('keyup', { bubbles: true, cancelable: true, ...init }));\n\nexport const fireSubmit = (target: EventTarget, init: SubmitEventInit = {}): boolean =>\n dispatch(target, new SubmitEvent('submit', { bubbles: true, cancelable: true, ...init }));\n"],"mappings":";AAkBA,IAAa,KAAY,GAAqB,MAA0B,EAAO,cAAc,CAAK,GAGrF,KAAY,GAAqB,IAAuB,CAAC,MACpE,EAAS,GAAQ,IAAI,WAAW,QAAQ,CAAI,CAAC,GAElC,KAAc,GAAqB,IAAkB,CAAC,MACjE,EAAS,GAAQ,IAAI,MAAM,UAAU;CAAE,SAAS;CAAM,GAAG;AAAK,CAAC,CAAC,GAErD,KAAa,GAAqB,IAAuB,CAAC,MACrE,EAAS,GAAQ,IAAI,WAAW,SAAS;CAAE,SAAS;CAAM,YAAY;CAAM,GAAG;AAAK,CAAC,CAAC,GAE3E,KAAiB,GAAqB,GAAc,IAA2B,CAAC,MAC3F,EAAS,GAAQ,IAAI,YAAY,GAAM;CAAE,SAAS;CAAM,YAAY;CAAM,UAAU;CAAO,GAAG;AAAK,CAAC,CAAC,GAE1F,KAAa,GAAqB,IAAuB,CAAC,MACrE,EAAS,GAAQ,IAAI,WAAW,SAAS,CAAI,CAAC,GAEnC,KAAa,GAAqB,IAAuB,CAAC,MACrE,EAAS,GAAQ,IAAI,WAAW,SAAS;CAAE,SAAS;CAAM,GAAG;AAAK,CAAC,CAAC,GAEzD,KAAe,GAAqB,IAA0B,CAAC,MAC1E,EAAS,GAAQ,IAAI,cAAc,WAAW;CAAE,SAAS;CAAM,YAAY;CAAM,GAAG;AAAK,CAAC,CAAC,GAEhF,KAAa,GAAqB,IAA0B,CAAC,MACxE,EAAS,GAAQ,IAAI,cAAc,SAAS;CAAE,SAAS;CAAM,YAAY;CAAM,GAAG;AAAK,CAAC,CAAC,GAE9E,KAAc,GAAqB,IAAwB,CAAC,MACvE,EAAS,GAAQ,IAAI,YAAY,UAAU;CAAE,SAAS;CAAM,YAAY;CAAM,GAAG;AAAK,CAAC,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./events.cjs"),n=require("./wait.cjs"),r=require("./live-region.cjs"),i=require("./query.cjs");exports.AssayError=e.AssayError,exports.AssayQueryError=e.AssayQueryError,exports.AssayTimeoutError=e.AssayTimeoutError,exports.delay=n.delay,exports.dispatch=t.dispatch,exports.fireBlur=t.fireBlur,exports.fireChange=t.fireChange,exports.fireClick=t.fireClick,exports.fireCustom=t.fireCustom,exports.fireFocus=t.fireFocus,exports.fireInput=t.fireInput,exports.fireKeyDown=t.fireKeyDown,exports.fireKeyUp=t.fireKeyUp,exports.fireSubmit=t.fireSubmit,exports.getSlotted=i.getSlotted,exports.nextTick=n.nextTick,exports.queryAllInShadow=i.queryAllInShadow,exports.queryAllLiveRegions=r.queryAllLiveRegions,exports.queryInShadow=i.queryInShadow,exports.queryLiveRegion=r.queryLiveRegion,exports.queryPart=i.queryPart,exports.
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./errors.cjs"),t=require("./events.cjs"),n=require("./wait.cjs"),r=require("./live-region.cjs"),i=require("./query.cjs");exports.AssayError=e.AssayError,exports.AssayQueryError=e.AssayQueryError,exports.AssayTimeoutError=e.AssayTimeoutError,exports.delay=n.delay,exports.dispatch=t.dispatch,exports.eventually=n.eventually,exports.fireBlur=t.fireBlur,exports.fireChange=t.fireChange,exports.fireClick=t.fireClick,exports.fireCustom=t.fireCustom,exports.fireFocus=t.fireFocus,exports.fireInput=t.fireInput,exports.fireKeyDown=t.fireKeyDown,exports.fireKeyUp=t.fireKeyUp,exports.fireSubmit=t.fireSubmit,exports.getSlotted=i.getSlotted,exports.nextTick=n.nextTick,exports.queryAllInShadow=i.queryAllInShadow,exports.queryAllLiveRegions=r.queryAllLiveRegions,exports.queryInShadow=i.queryInShadow,exports.queryLiveRegion=r.queryLiveRegion,exports.queryPart=i.queryPart,exports.waitForEvent=n.waitForEvent,exports.waitForLiveRegion=r.waitForLiveRegion,exports.waitForLiveRegionCleared=r.waitForLiveRegionCleared,exports.waitUntil=n.waitUntil,exports.within=i.within;
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export { AssayError, AssayQueryError, AssayTimeoutError } from './errors';
|
|
|
2
2
|
export { dispatch, fireBlur, fireChange, fireClick, fireCustom, fireFocus, fireInput, fireKeyDown, fireKeyUp, fireSubmit, } from './events';
|
|
3
3
|
export { type LiveRegionPoliteness, type LiveRegionQueryOptions, queryAllLiveRegions, queryLiveRegion, type WaitForLiveRegionOptions, waitForLiveRegion, waitForLiveRegionCleared, } from './live-region';
|
|
4
4
|
export { getSlotted, type QueryScope, queryAllInShadow, queryInShadow, queryPart, within } from './query';
|
|
5
|
-
export { type DelayOptions, delay,
|
|
5
|
+
export { type DelayOptions, delay, type EventuallyOptions, eventually, nextTick, type WaitOptions, waitForEvent, waitUntil, } from './wait';
|
|
6
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE1E,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,EACT,WAAW,EACX,SAAS,EACT,UAAU,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,mBAAmB,EACnB,eAAe,EACf,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC1G,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,EACL,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAE1E,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAS,EACT,UAAU,EACV,SAAS,EACT,SAAS,EACT,WAAW,EACX,SAAS,EACT,UAAU,GACX,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,mBAAmB,EACnB,eAAe,EACf,KAAK,wBAAwB,EAC7B,iBAAiB,EACjB,wBAAwB,GACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC1G,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,EACL,KAAK,iBAAiB,EACtB,UAAU,EACV,QAAQ,EACR,KAAK,WAAW,EAChB,YAAY,EACZ,SAAS,GACV,MAAM,QAAQ,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AssayError as e, AssayQueryError as t, AssayTimeoutError as n } from "./errors.js";
|
|
2
2
|
import { dispatch as r, fireBlur as i, fireChange as a, fireClick as o, fireCustom as s, fireFocus as c, fireInput as l, fireKeyDown as u, fireKeyUp as d, fireSubmit as f } from "./events.js";
|
|
3
|
-
import { delay as p,
|
|
3
|
+
import { delay as p, eventually as m, nextTick as h, waitForEvent as g, waitUntil as _ } from "./wait.js";
|
|
4
4
|
import { queryAllLiveRegions as v, queryLiveRegion as y, waitForLiveRegion as b, waitForLiveRegionCleared as x } from "./live-region.js";
|
|
5
5
|
import { getSlotted as S, queryAllInShadow as C, queryInShadow as w, queryPart as T, within as E } from "./query.js";
|
|
6
|
-
export { e as AssayError, t as AssayQueryError, n as AssayTimeoutError, p as delay, r as dispatch, i as fireBlur, a as fireChange, o as fireClick, s as fireCustom, c as fireFocus, l as fireInput, u as fireKeyDown, d as fireKeyUp, f as fireSubmit, S as getSlotted,
|
|
6
|
+
export { e as AssayError, t as AssayQueryError, n as AssayTimeoutError, p as delay, r as dispatch, m as eventually, i as fireBlur, a as fireChange, o as fireClick, s as fireCustom, c as fireFocus, l as fireInput, u as fireKeyDown, d as fireKeyUp, f as fireSubmit, S as getSlotted, h as nextTick, C as queryAllInShadow, v as queryAllLiveRegions, w as queryInShadow, y as queryLiveRegion, T as queryPart, g as waitForEvent, b as waitForLiveRegion, x as waitForLiveRegionCleared, _ as waitUntil, E as within };
|
package/dist/live-region.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs"),t=require("./wait.cjs");var n={alert:`assertive`,status:`polite`}
|
|
1
|
+
const e=require("./errors.cjs"),t=require("./wait.cjs");var n={alert:`assertive`,log:`polite`,marquee:`off`,status:`polite`,timer:`off`},r=`[aria-live], [role="alert"], [role="log"], [role="marquee"], [role="status"], [role="timer"]`;function i(e){let t=e.getAttribute(`aria-live`);if(t)return t;let r=e.getAttribute(`role`);return r&&r in n?n[r]:null}function a(e){let t=Array.from(e.querySelectorAll(r));return e.matches?.(r)?[e,...t]:t}function o(e,t,n){return i(e)===t&&(n===void 0||e.getAttribute(`role`)===n)}function s(e={}){let t=e.document??(typeof document<`u`?document:null),n=e.politeness??`polite`,r=e.root??t?.body??null;return r?a(r).find(t=>o(t,n,e.role))??null:null}function c(e={}){let t=e.document??(typeof document<`u`?document:null),n=e.politeness??`polite`,r=e.root??t?.body??null;return r?a(r).filter(t=>o(t,n,e.role)):[]}async function l(n,r={}){let i=r.politeness??`polite`,a=null;return await t.eventually(()=>{let t=c({document:r.document,politeness:i,role:r.role,root:r.root}),o=t.find(e=>e.textContent?.includes(n));if(o){a=o;return}throw t.length===0?new e.AssayQueryError(`No ${i} live region found.`):new e.AssayQueryError(`No ${i} live region includes "${n}".`)},{interval:r.interval,message:`waitForLiveRegion("${n}")`,signal:r.signal,timeout:r.timeout}),a}async function u(n={}){let r=n.politeness??`polite`;await t.eventually(()=>{let t=c({document:n.document,politeness:r,role:n.role,root:n.root});if(!t.some(e=>e.textContent===``))throw t.length===0?new e.AssayQueryError(`No ${r} live region found.`):new e.AssayQueryError(`No ${r} live region is cleared.`)},{interval:n.interval,message:`waitForLiveRegionCleared`,signal:n.signal,timeout:n.timeout})}exports.queryAllLiveRegions=c,exports.queryLiveRegion=s,exports.waitForLiveRegion=l,exports.waitForLiveRegionCleared=u;
|
|
2
2
|
//# sourceMappingURL=live-region.cjs.map
|
package/dist/live-region.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-region.cjs","names":[],"sources":["../src/live-region.ts"],"sourcesContent":["import { AssayQueryError } from './errors';\nimport {
|
|
1
|
+
{"version":3,"file":"live-region.cjs","names":[],"sources":["../src/live-region.ts"],"sourcesContent":["import { AssayQueryError } from './errors';\nimport { eventually, type WaitOptions } from './wait';\n\n/** Politeness levels for ARIA live regions. */\nexport type LiveRegionPoliteness = 'assertive' | 'off' | 'polite';\n\n/**\n * ARIA roles that carry an implicit `aria-live` value per the WAI-ARIA spec.\n * `alert` is assertive; `log` and `status` are polite; `marquee` and `timer` are\n * off. Elements with these roles are live regions without explicit `aria-live`.\n */\nconst IMPLICIT_ROLE_POLITENESS: Record<string, LiveRegionPoliteness> = {\n alert: 'assertive',\n log: 'polite',\n marquee: 'off',\n status: 'polite',\n timer: 'off',\n};\nconst LIVE_REGION_SELECTOR =\n '[aria-live], [role=\"alert\"], [role=\"log\"], [role=\"marquee\"], [role=\"status\"], [role=\"timer\"]';\n\nexport interface LiveRegionQueryOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live` (or implicit role). Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match (e.g. `'status'`, `'alert'`). Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Resolves the effective politeness of an element: explicit `aria-live` wins,\n * otherwise falls back to the implicit value from `role=\"status\"`/`role=\"alert\"`.\n * Returns `null` when the element is not a live region.\n */\nfunction resolvePoliteness(el: HTMLElement): LiveRegionPoliteness | null {\n const explicit = el.getAttribute('aria-live');\n\n if (explicit) return explicit as LiveRegionPoliteness;\n\n const role = el.getAttribute('role');\n\n if (role && role in IMPLICIT_ROLE_POLITENESS) return IMPLICIT_ROLE_POLITENESS[role];\n\n return null;\n}\n\nfunction candidates(root: ParentNode): HTMLElement[] {\n const descendants = Array.from(root.querySelectorAll<HTMLElement>(LIVE_REGION_SELECTOR));\n const rootElement = root as ParentNode & { matches?: (selector: string) => boolean };\n\n return rootElement.matches?.(LIVE_REGION_SELECTOR) ? [root as HTMLElement, ...descendants] : descendants;\n}\n\nfunction matchesOptions(element: HTMLElement, politeness: LiveRegionPoliteness, role?: string): boolean {\n return resolvePoliteness(element) === politeness && (role === undefined || element.getAttribute('role') === role);\n}\n\n/**\n * Queries the first live region matching the given politeness (and optional role)\n * within `root`. Returns `null` when none exists — safe to call before the region\n * has been lazily created.\n *\n * Matches explicit `aria-live` attributes and implicit live-region roles.\n *\n * @example\n * const region = queryLiveRegion({ politeness: 'polite' });\n * expect(region).not.toBeNull();\n */\nexport function queryLiveRegion(options: LiveRegionQueryOptions = {}): HTMLElement | null {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return null;\n\n return candidates(root).find((element) => matchesOptions(element, politeness, options.role)) ?? null;\n}\n\n/**\n * Queries all live regions matching the given politeness (and optional role)\n * within `root`. Useful for asserting no duplicate regions were created.\n *\n * @example\n * expect(queryAllLiveRegions({ politeness: 'polite' })).toHaveLength(1);\n */\nexport function queryAllLiveRegions(options: LiveRegionQueryOptions = {}): HTMLElement[] {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return [];\n\n return candidates(root).filter((element) => matchesOptions(element, politeness, options.role));\n}\n\nexport interface WaitForLiveRegionOptions extends WaitOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live`. Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match. Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and contains\n * `text` (substring match). Throws `AssayTimeoutError` on timeout.\n *\n * Handles the clear-then-set announce pattern: the region may briefly be empty\n * before the message is written, so polling until the text appears is the\n * correct approach rather than a single snapshot assertion.\n *\n * @example\n * await waitForLiveRegion('3 results found');\n * await waitForLiveRegion('Session expired', { politeness: 'assertive' });\n */\nexport async function waitForLiveRegion(text: string, options: WaitForLiveRegionOptions = {}): Promise<HTMLElement> {\n const politeness = options.politeness ?? 'polite';\n let matched: HTMLElement | null = null;\n\n await eventually(\n () => {\n const regions = queryAllLiveRegions({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n const region = regions.find((candidate) => candidate.textContent?.includes(text));\n\n if (region) {\n matched = region;\n return;\n }\n\n if (regions.length === 0) throw new AssayQueryError(`No ${politeness} live region found.`);\n throw new AssayQueryError(`No ${politeness} live region includes \"${text}\".`);\n },\n {\n interval: options.interval,\n message: `waitForLiveRegion(\"${text}\")`,\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n\n // `eventually` only resolves once the assertion stops throwing, so `matched` is set.\n return matched!;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and is empty\n * (cleared). Throws `AssayTimeoutError` on timeout.\n *\n * @example\n * await waitForLiveRegionCleared();\n */\nexport async function waitForLiveRegionCleared(options: WaitForLiveRegionOptions = {}): Promise<void> {\n const politeness = options.politeness ?? 'polite';\n\n await eventually(\n () => {\n const regions = queryAllLiveRegions({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n\n if (regions.some((region) => region.textContent === '')) return;\n if (regions.length === 0) throw new AssayQueryError(`No ${politeness} live region found.`);\n throw new AssayQueryError(`No ${politeness} live region is cleared.`);\n },\n {\n interval: options.interval,\n message: 'waitForLiveRegionCleared',\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n}\n"],"mappings":"wDAWA,IAAM,EAAiE,CACrE,MAAO,YACP,IAAK,SACL,QAAS,MACT,OAAQ,SACR,MAAO,KACT,EACM,EACJ,+FAkBF,SAAS,EAAkB,EAA8C,CACvE,IAAM,EAAW,EAAG,aAAa,WAAW,EAE5C,GAAI,EAAU,OAAO,EAErB,IAAM,EAAO,EAAG,aAAa,MAAM,EAInC,OAFI,GAAQ,KAAQ,EAAiC,EAAyB,GAEvE,IACT,CAEA,SAAS,EAAW,EAAiC,CACnD,IAAM,EAAc,MAAM,KAAK,EAAK,iBAA8B,CAAoB,CAAC,EAGvF,OAAO,EAAY,UAAU,CAAoB,EAAI,CAAC,EAAqB,GAAG,CAAW,EAAI,CAC/F,CAEA,SAAS,EAAe,EAAsB,EAAkC,EAAwB,CACtG,OAAO,EAAkB,CAAO,IAAM,IAAe,IAAS,IAAA,IAAa,EAAQ,aAAa,MAAM,IAAM,EAC9G,CAaA,SAAgB,EAAgB,EAAkC,CAAC,EAAuB,CACxF,IAAM,EAAM,EAAQ,WAAa,OAAO,SAAa,IAAc,SAAW,MACxE,EAAa,EAAQ,YAAc,SACnC,EAAO,EAAQ,MAAQ,GAAK,MAAQ,KAI1C,OAFK,EAEE,EAAW,CAAI,CAAC,CAAC,KAAM,GAAY,EAAe,EAAS,EAAY,EAAQ,IAAI,CAAC,GAAK,KAF9E,IAGpB,CASA,SAAgB,EAAoB,EAAkC,CAAC,EAAkB,CACvF,IAAM,EAAM,EAAQ,WAAa,OAAO,SAAa,IAAc,SAAW,MACxE,EAAa,EAAQ,YAAc,SACnC,EAAO,EAAQ,MAAQ,GAAK,MAAQ,KAI1C,OAFK,EAEE,EAAW,CAAI,CAAC,CAAC,OAAQ,GAAY,EAAe,EAAS,EAAY,EAAQ,IAAI,CAAC,EAF3E,CAAC,CAGrB,CAyBA,eAAsB,EAAkB,EAAc,EAAoC,CAAC,EAAyB,CAClH,IAAM,EAAa,EAAQ,YAAc,SACrC,EAA8B,KA6BlC,OA3BA,MAAM,EAAA,eACE,CACJ,IAAM,EAAU,EAAoB,CAClC,SAAU,EAAQ,SAClB,aACA,KAAM,EAAQ,KACd,KAAM,EAAQ,IAChB,CAAC,EACK,EAAS,EAAQ,KAAM,GAAc,EAAU,aAAa,SAAS,CAAI,CAAC,EAEhF,GAAI,EAAQ,CACV,EAAU,EACV,MACF,CAGA,MADI,EAAQ,SAAW,EAAS,IAAI,EAAA,gBAAgB,MAAM,EAAW,oBAAoB,EACnF,IAAI,EAAA,gBAAgB,MAAM,EAAW,yBAAyB,EAAK,GAAG,CAC9E,EACA,CACE,SAAU,EAAQ,SAClB,QAAS,sBAAsB,EAAK,IACpC,OAAQ,EAAQ,OAChB,QAAS,EAAQ,OACnB,CACF,EAGO,CACT,CASA,eAAsB,EAAyB,EAAoC,CAAC,EAAkB,CACpG,IAAM,EAAa,EAAQ,YAAc,SAEzC,MAAM,EAAA,eACE,CACJ,IAAM,EAAU,EAAoB,CAClC,SAAU,EAAQ,SAClB,aACA,KAAM,EAAQ,KACd,KAAM,EAAQ,IAChB,CAAC,EAEG,MAAQ,KAAM,GAAW,EAAO,cAAgB,EAAE,EAEtD,MADI,EAAQ,SAAW,EAAS,IAAI,EAAA,gBAAgB,MAAM,EAAW,oBAAoB,EACnF,IAAI,EAAA,gBAAgB,MAAM,EAAW,yBAAyB,CACtE,EACA,CACE,SAAU,EAAQ,SAClB,QAAS,2BACT,OAAQ,EAAQ,OAChB,QAAS,EAAQ,OACnB,CACF,CACF"}
|
package/dist/live-region.d.ts
CHANGED
|
@@ -16,8 +16,7 @@ export interface LiveRegionQueryOptions {
|
|
|
16
16
|
* within `root`. Returns `null` when none exists — safe to call before the region
|
|
17
17
|
* has been lazily created.
|
|
18
18
|
*
|
|
19
|
-
* Matches
|
|
20
|
-
* polite, `alert` → assertive) per the WAI-ARIA spec.
|
|
19
|
+
* Matches explicit `aria-live` attributes and implicit live-region roles.
|
|
21
20
|
*
|
|
22
21
|
* @example
|
|
23
22
|
* const region = queryLiveRegion({ politeness: 'polite' });
|
|
@@ -51,8 +50,8 @@ export interface WaitForLiveRegionOptions extends WaitOptions {
|
|
|
51
50
|
* correct approach rather than a single snapshot assertion.
|
|
52
51
|
*
|
|
53
52
|
* @example
|
|
54
|
-
* await waitForLiveRegion(
|
|
55
|
-
* await waitForLiveRegion({ politeness: 'assertive'
|
|
53
|
+
* await waitForLiveRegion('3 results found');
|
|
54
|
+
* await waitForLiveRegion('Session expired', { politeness: 'assertive' });
|
|
56
55
|
*/
|
|
57
56
|
export declare function waitForLiveRegion(text: string, options?: WaitForLiveRegionOptions): Promise<HTMLElement>;
|
|
58
57
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-region.d.ts","sourceRoot":"","sources":["../src/live-region.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"live-region.d.ts","sourceRoot":"","sources":["../src/live-region.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAEtD,+CAA+C;AAC/C,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,KAAK,GAAG,QAAQ,CAAC;AAiBlE,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,sFAAsF;IACtF,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AA8BD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,WAAW,GAAG,IAAI,CAQxF;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,sBAA2B,GAAG,WAAW,EAAE,CAQvF;AAED,MAAM,WAAW,wBAAyB,SAAQ,WAAW;IAC3D,iEAAiE;IACjE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,mEAAmE;IACnE,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,WAAW,CAAC,CAgClH;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBpG"}
|
package/dist/live-region.js
CHANGED
|
@@ -1,57 +1,65 @@
|
|
|
1
1
|
import { AssayQueryError as e } from "./errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { eventually as t } from "./wait.js";
|
|
3
3
|
//#region src/live-region.ts
|
|
4
4
|
var n = {
|
|
5
5
|
alert: "assertive",
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
log: "polite",
|
|
7
|
+
marquee: "off",
|
|
8
|
+
status: "polite",
|
|
9
|
+
timer: "off"
|
|
10
|
+
}, r = "[aria-live], [role=\"alert\"], [role=\"log\"], [role=\"marquee\"], [role=\"status\"], [role=\"timer\"]";
|
|
11
|
+
function i(e) {
|
|
9
12
|
let t = e.getAttribute("aria-live");
|
|
10
13
|
if (t) return t;
|
|
11
14
|
let r = e.getAttribute("role");
|
|
12
15
|
return r && r in n ? n[r] : null;
|
|
13
16
|
}
|
|
14
|
-
function
|
|
15
|
-
let t = e.
|
|
16
|
-
|
|
17
|
-
let a = Array.from(i.querySelectorAll("[aria-live], [role=\"status\"], [role=\"alert\"]"));
|
|
18
|
-
for (let t of a) if (r(t) === n && (e.role === void 0 || t.getAttribute("role") === e.role)) return t;
|
|
19
|
-
return null;
|
|
17
|
+
function a(e) {
|
|
18
|
+
let t = Array.from(e.querySelectorAll(r));
|
|
19
|
+
return e.matches?.(r) ? [e, ...t] : t;
|
|
20
20
|
}
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
return i ? Array.from(i.querySelectorAll("[aria-live], [role=\"status\"], [role=\"alert\"]")).filter((t) => !(r(t) !== n || e.role !== void 0 && t.getAttribute("role") !== e.role)) : [];
|
|
21
|
+
function o(e, t, n) {
|
|
22
|
+
return i(e) === t && (n === void 0 || e.getAttribute("role") === n);
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
let
|
|
24
|
+
function s(e = {}) {
|
|
25
|
+
let t = e.document ?? (typeof document < "u" ? document : null), n = e.politeness ?? "polite", r = e.root ?? t?.body ?? null;
|
|
26
|
+
return r ? a(r).find((t) => o(t, n, e.role)) ?? null : null;
|
|
27
|
+
}
|
|
28
|
+
function c(e = {}) {
|
|
29
|
+
let t = e.document ?? (typeof document < "u" ? document : null), n = e.politeness ?? "polite", r = e.root ?? t?.body ?? null;
|
|
30
|
+
return r ? a(r).filter((t) => o(t, n, e.role)) : [];
|
|
31
|
+
}
|
|
32
|
+
async function l(n, r = {}) {
|
|
33
|
+
let i = r.politeness ?? "polite", a = null;
|
|
27
34
|
return await t(() => {
|
|
28
|
-
let t =
|
|
35
|
+
let t = c({
|
|
29
36
|
document: r.document,
|
|
30
|
-
politeness:
|
|
37
|
+
politeness: i,
|
|
31
38
|
role: r.role,
|
|
32
39
|
root: r.root
|
|
33
|
-
});
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
}), o = t.find((e) => e.textContent?.includes(n));
|
|
41
|
+
if (o) {
|
|
42
|
+
a = o;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
throw t.length === 0 ? new e(`No ${i} live region found.`) : new e(`No ${i} live region includes "${n}".`);
|
|
37
46
|
}, {
|
|
38
47
|
interval: r.interval,
|
|
39
48
|
message: `waitForLiveRegion("${n}")`,
|
|
40
49
|
signal: r.signal,
|
|
41
50
|
timeout: r.timeout
|
|
42
|
-
}),
|
|
51
|
+
}), a;
|
|
43
52
|
}
|
|
44
|
-
async function
|
|
53
|
+
async function u(n = {}) {
|
|
45
54
|
let r = n.politeness ?? "polite";
|
|
46
55
|
await t(() => {
|
|
47
|
-
let t =
|
|
56
|
+
let t = c({
|
|
48
57
|
document: n.document,
|
|
49
58
|
politeness: r,
|
|
50
59
|
role: n.role,
|
|
51
60
|
root: n.root
|
|
52
61
|
});
|
|
53
|
-
if (!t) throw new e(`No ${r} live region found.`);
|
|
54
|
-
if (t.textContent !== "") throw new e(`Live region not cleared: "${t.textContent}".`);
|
|
62
|
+
if (!t.some((e) => e.textContent === "")) throw t.length === 0 ? new e(`No ${r} live region found.`) : new e(`No ${r} live region is cleared.`);
|
|
55
63
|
}, {
|
|
56
64
|
interval: n.interval,
|
|
57
65
|
message: "waitForLiveRegionCleared",
|
|
@@ -60,6 +68,6 @@ async function s(n = {}) {
|
|
|
60
68
|
});
|
|
61
69
|
}
|
|
62
70
|
//#endregion
|
|
63
|
-
export {
|
|
71
|
+
export { c as queryAllLiveRegions, s as queryLiveRegion, l as waitForLiveRegion, u as waitForLiveRegionCleared };
|
|
64
72
|
|
|
65
73
|
//# sourceMappingURL=live-region.js.map
|
package/dist/live-region.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-region.js","names":[],"sources":["../src/live-region.ts"],"sourcesContent":["import { AssayQueryError } from './errors';\nimport {
|
|
1
|
+
{"version":3,"file":"live-region.js","names":[],"sources":["../src/live-region.ts"],"sourcesContent":["import { AssayQueryError } from './errors';\nimport { eventually, type WaitOptions } from './wait';\n\n/** Politeness levels for ARIA live regions. */\nexport type LiveRegionPoliteness = 'assertive' | 'off' | 'polite';\n\n/**\n * ARIA roles that carry an implicit `aria-live` value per the WAI-ARIA spec.\n * `alert` is assertive; `log` and `status` are polite; `marquee` and `timer` are\n * off. Elements with these roles are live regions without explicit `aria-live`.\n */\nconst IMPLICIT_ROLE_POLITENESS: Record<string, LiveRegionPoliteness> = {\n alert: 'assertive',\n log: 'polite',\n marquee: 'off',\n status: 'polite',\n timer: 'off',\n};\nconst LIVE_REGION_SELECTOR =\n '[aria-live], [role=\"alert\"], [role=\"log\"], [role=\"marquee\"], [role=\"status\"], [role=\"timer\"]';\n\nexport interface LiveRegionQueryOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live` (or implicit role). Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match (e.g. `'status'`, `'alert'`). Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Resolves the effective politeness of an element: explicit `aria-live` wins,\n * otherwise falls back to the implicit value from `role=\"status\"`/`role=\"alert\"`.\n * Returns `null` when the element is not a live region.\n */\nfunction resolvePoliteness(el: HTMLElement): LiveRegionPoliteness | null {\n const explicit = el.getAttribute('aria-live');\n\n if (explicit) return explicit as LiveRegionPoliteness;\n\n const role = el.getAttribute('role');\n\n if (role && role in IMPLICIT_ROLE_POLITENESS) return IMPLICIT_ROLE_POLITENESS[role];\n\n return null;\n}\n\nfunction candidates(root: ParentNode): HTMLElement[] {\n const descendants = Array.from(root.querySelectorAll<HTMLElement>(LIVE_REGION_SELECTOR));\n const rootElement = root as ParentNode & { matches?: (selector: string) => boolean };\n\n return rootElement.matches?.(LIVE_REGION_SELECTOR) ? [root as HTMLElement, ...descendants] : descendants;\n}\n\nfunction matchesOptions(element: HTMLElement, politeness: LiveRegionPoliteness, role?: string): boolean {\n return resolvePoliteness(element) === politeness && (role === undefined || element.getAttribute('role') === role);\n}\n\n/**\n * Queries the first live region matching the given politeness (and optional role)\n * within `root`. Returns `null` when none exists — safe to call before the region\n * has been lazily created.\n *\n * Matches explicit `aria-live` attributes and implicit live-region roles.\n *\n * @example\n * const region = queryLiveRegion({ politeness: 'polite' });\n * expect(region).not.toBeNull();\n */\nexport function queryLiveRegion(options: LiveRegionQueryOptions = {}): HTMLElement | null {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return null;\n\n return candidates(root).find((element) => matchesOptions(element, politeness, options.role)) ?? null;\n}\n\n/**\n * Queries all live regions matching the given politeness (and optional role)\n * within `root`. Useful for asserting no duplicate regions were created.\n *\n * @example\n * expect(queryAllLiveRegions({ politeness: 'polite' })).toHaveLength(1);\n */\nexport function queryAllLiveRegions(options: LiveRegionQueryOptions = {}): HTMLElement[] {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return [];\n\n return candidates(root).filter((element) => matchesOptions(element, politeness, options.role));\n}\n\nexport interface WaitForLiveRegionOptions extends WaitOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live`. Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match. Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and contains\n * `text` (substring match). Throws `AssayTimeoutError` on timeout.\n *\n * Handles the clear-then-set announce pattern: the region may briefly be empty\n * before the message is written, so polling until the text appears is the\n * correct approach rather than a single snapshot assertion.\n *\n * @example\n * await waitForLiveRegion('3 results found');\n * await waitForLiveRegion('Session expired', { politeness: 'assertive' });\n */\nexport async function waitForLiveRegion(text: string, options: WaitForLiveRegionOptions = {}): Promise<HTMLElement> {\n const politeness = options.politeness ?? 'polite';\n let matched: HTMLElement | null = null;\n\n await eventually(\n () => {\n const regions = queryAllLiveRegions({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n const region = regions.find((candidate) => candidate.textContent?.includes(text));\n\n if (region) {\n matched = region;\n return;\n }\n\n if (regions.length === 0) throw new AssayQueryError(`No ${politeness} live region found.`);\n throw new AssayQueryError(`No ${politeness} live region includes \"${text}\".`);\n },\n {\n interval: options.interval,\n message: `waitForLiveRegion(\"${text}\")`,\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n\n // `eventually` only resolves once the assertion stops throwing, so `matched` is set.\n return matched!;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and is empty\n * (cleared). Throws `AssayTimeoutError` on timeout.\n *\n * @example\n * await waitForLiveRegionCleared();\n */\nexport async function waitForLiveRegionCleared(options: WaitForLiveRegionOptions = {}): Promise<void> {\n const politeness = options.politeness ?? 'polite';\n\n await eventually(\n () => {\n const regions = queryAllLiveRegions({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n\n if (regions.some((region) => region.textContent === '')) return;\n if (regions.length === 0) throw new AssayQueryError(`No ${politeness} live region found.`);\n throw new AssayQueryError(`No ${politeness} live region is cleared.`);\n },\n {\n interval: options.interval,\n message: 'waitForLiveRegionCleared',\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n}\n"],"mappings":";;;AAWA,IAAM,IAAiE;CACrE,OAAO;CACP,KAAK;CACL,SAAS;CACT,QAAQ;CACR,OAAO;AACT,GACM,IACJ;AAkBF,SAAS,EAAkB,GAA8C;CACvE,IAAM,IAAW,EAAG,aAAa,WAAW;CAE5C,IAAI,GAAU,OAAO;CAErB,IAAM,IAAO,EAAG,aAAa,MAAM;CAInC,OAFI,KAAQ,KAAQ,IAAiC,EAAyB,KAEvE;AACT;AAEA,SAAS,EAAW,GAAiC;CACnD,IAAM,IAAc,MAAM,KAAK,EAAK,iBAA8B,CAAoB,CAAC;CAGvF,OAAO,EAAY,UAAU,CAAoB,IAAI,CAAC,GAAqB,GAAG,CAAW,IAAI;AAC/F;AAEA,SAAS,EAAe,GAAsB,GAAkC,GAAwB;CACtG,OAAO,EAAkB,CAAO,MAAM,MAAe,MAAS,KAAA,KAAa,EAAQ,aAAa,MAAM,MAAM;AAC9G;AAaA,SAAgB,EAAgB,IAAkC,CAAC,GAAuB;CACxF,IAAM,IAAM,EAAQ,aAAa,OAAO,WAAa,MAAc,WAAW,OACxE,IAAa,EAAQ,cAAc,UACnC,IAAO,EAAQ,QAAQ,GAAK,QAAQ;CAI1C,OAFK,IAEE,EAAW,CAAI,CAAC,CAAC,MAAM,MAAY,EAAe,GAAS,GAAY,EAAQ,IAAI,CAAC,KAAK,OAF9E;AAGpB;AASA,SAAgB,EAAoB,IAAkC,CAAC,GAAkB;CACvF,IAAM,IAAM,EAAQ,aAAa,OAAO,WAAa,MAAc,WAAW,OACxE,IAAa,EAAQ,cAAc,UACnC,IAAO,EAAQ,QAAQ,GAAK,QAAQ;CAI1C,OAFK,IAEE,EAAW,CAAI,CAAC,CAAC,QAAQ,MAAY,EAAe,GAAS,GAAY,EAAQ,IAAI,CAAC,IAF3E,CAAC;AAGrB;AAyBA,eAAsB,EAAkB,GAAc,IAAoC,CAAC,GAAyB;CAClH,IAAM,IAAa,EAAQ,cAAc,UACrC,IAA8B;CA6BlC,OA3BA,MAAM,QACE;EACJ,IAAM,IAAU,EAAoB;GAClC,UAAU,EAAQ;GAClB;GACA,MAAM,EAAQ;GACd,MAAM,EAAQ;EAChB,CAAC,GACK,IAAS,EAAQ,MAAM,MAAc,EAAU,aAAa,SAAS,CAAI,CAAC;EAEhF,IAAI,GAAQ;GACV,IAAU;GACV;EACF;EAGA,MADI,EAAQ,WAAW,IAAS,IAAI,EAAgB,MAAM,EAAW,oBAAoB,IACnF,IAAI,EAAgB,MAAM,EAAW,yBAAyB,EAAK,GAAG;CAC9E,GACA;EACE,UAAU,EAAQ;EAClB,SAAS,sBAAsB,EAAK;EACpC,QAAQ,EAAQ;EAChB,SAAS,EAAQ;CACnB,CACF,GAGO;AACT;AASA,eAAsB,EAAyB,IAAoC,CAAC,GAAkB;CACpG,IAAM,IAAa,EAAQ,cAAc;CAEzC,MAAM,QACE;EACJ,IAAM,IAAU,EAAoB;GAClC,UAAU,EAAQ;GAClB;GACA,MAAM,EAAQ;GACd,MAAM,EAAQ;EAChB,CAAC;EAEG,OAAQ,MAAM,MAAW,EAAO,gBAAgB,EAAE,GAEtD,MADI,EAAQ,WAAW,IAAS,IAAI,EAAgB,MAAM,EAAW,oBAAoB,IACnF,IAAI,EAAgB,MAAM,EAAW,yBAAyB;CACtE,GACA;EACE,UAAU,EAAQ;EAClB,SAAS;EACT,QAAQ,EAAQ;EAChB,SAAS,EAAQ;CACnB,CACF;AACF"}
|
package/dist/query.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./errors.cjs");var t=e=>e instanceof Element?e.outerHTML.slice(0,500):e instanceof ShadowRoot?`<shadow-root>${e.innerHTML.slice(0,500)}</shadow-root>`:e.nodeName,n=(n,r,i)=>{if(n)return n;throw new e.AssayQueryError(`Unable to find ${r} within ${t(i)}.`)},r=(e,t)=>e.querySelector(t),i=(e,t)=>Array.from(e.querySelectorAll(t)),a=(e,t)=>i(e,`[data-testid]`).find(e=>e.getAttribute(`data-testid`)===t)??null,o=(e,t)=>i(e,`[data-testid]`).filter(e=>e.getAttribute(`data-testid`)===t),s=(e,t,n=`*`)=>{for(let r of e.querySelectorAll(n))if(r.textContent?.trim()===t)return r;return null},c=(e,t,n=`*`)=>
|
|
1
|
+
const e=require("./errors.cjs");var t=e=>e instanceof Element?e.outerHTML.slice(0,500):e instanceof ShadowRoot?`<shadow-root>${e.innerHTML.slice(0,500)}</shadow-root>`:e.nodeName,n=(n,r,i)=>{if(n)return n;throw new e.AssayQueryError(`Unable to find ${r} within ${t(i)}.`)},r=(e,t)=>e.querySelector(t),i=(e,t)=>Array.from(e.querySelectorAll(t)),a=(e,t)=>i(e,`[data-testid]`).find(e=>e.getAttribute(`data-testid`)===t)??null,o=(e,t)=>i(e,`[data-testid]`).filter(e=>e.getAttribute(`data-testid`)===t),s=(e,t,n=`*`)=>{for(let r of e.querySelectorAll(n))if(r.textContent?.trim()===t)return r;return null},c=(e,t,n=`*`)=>i(e,n).filter(e=>e.textContent?.trim()===t);function l(e,t){return e.shadowRoot?r(e.shadowRoot,t):null}function u(e,t){return e.shadowRoot?i(e.shadowRoot,t):[]}function d(e,t){return u(e,`[part]`).find(e=>e.getAttribute(`part`)?.split(/\s+/).includes(t))??null}function f(e,t){return Array.from(e.children).filter(e=>t===void 0?!e.getAttribute(`slot`):e.getAttribute(`slot`)===t)}function p(e){return{get:t=>n(r(e,t),`"${t}"`,e),getByTestId:t=>n(a(e,t),`data-testid "${t}"`,e),getByText:(t,r=`*`)=>n(s(e,t,r),`text "${t}" matching "${r}"`,e),query:t=>r(e,t),queryAll:t=>i(e,t),queryAllByTestId:t=>o(e,t),queryAllByText:(t,n=`*`)=>c(e,t,n),queryByTestId:t=>a(e,t),queryByText:(t,n=`*`)=>s(e,t,n)}}exports.getSlotted=f,exports.queryAllInShadow=u,exports.queryInShadow=l,exports.queryPart=d,exports.within=p;
|
|
2
2
|
//# sourceMappingURL=query.cjs.map
|