@orkestrel/test 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/constants.ts","../../../src/browser/helpers.ts","../../../src/browser/factories.ts"],"sourcesContent":["/**\n * The interactive ARIA roles a bare accessible name is searched across.\n *\n * @remarks\n * A person names a control, not a role, so the one-argument resolver searches every role a control\n * can compute. The two-argument form searches exactly the role it is given, which is how a name\n * shared by a tab and its panel is disambiguated.\n */\nexport const ACCESSIBLE_ROLES: readonly string[] = Object.freeze([\n\t'button',\n\t'checkbox',\n\t'combobox',\n\t'link',\n\t'listbox',\n\t'menuitem',\n\t'option',\n\t'radio',\n\t'searchbox',\n\t'slider',\n\t'spinbutton',\n\t'switch',\n\t'tab',\n\t'tabpanel',\n\t'textbox',\n\t'treeitem',\n])\n","import type { CaptureVariant } from './types.js'\nimport { page, userEvent } from 'vitest/browser'\nimport { ACCESSIBLE_ROLES } from './constants.js'\n\n/**\n * Determines whether a rectangle lies wholly outside the browser viewport.\n *\n * @param rectangle - The measured client rectangle to inspect.\n * @returns `true` when no part of the rectangle intersects the viewport.\n *\n * @example\n * ```ts\n * isOutsideViewport(element.getBoundingClientRect())\n * ```\n */\nexport function isOutsideViewport(rectangle: DOMRectReadOnly): boolean {\n\treturn (\n\t\trectangle.bottom <= 0 ||\n\t\trectangle.right <= 0 ||\n\t\trectangle.top >= window.innerHeight ||\n\t\trectangle.left >= window.innerWidth\n\t)\n}\n\n/**\n * Resolves one rendered, focus-reachable interactive element without requiring it to intersect the\n * viewport yet.\n *\n * @param first - The accessible name, or the exact ARIA role when `second` is present.\n * @param second - The accessible name when `first` supplies the role.\n * @returns The one rendered element carrying that name and optional role.\n * @throws When no matching element exists, every match is hidden or unreachable, or several\n * rendered matches make the name ambiguous.\n *\n * @remarks\n * This is the resolver the acting verbs use, so a click does not fail on a target the act itself\n * scrolls into view. Use {@link resolveAccessible} wherever the target must already be on screen.\n *\n * @example\n * ```ts\n * resolveRendered('tab', 'Drafts')\n * ```\n */\nexport function resolveRendered(first: string, second?: string): HTMLElement {\n\tconst name = second ?? first\n\tconst roles = second === undefined ? ACCESSIBLE_ROLES : [first]\n\tconst matches: HTMLElement[] = []\n\tfor (const role of roles) {\n\t\tfor (const element of page\n\t\t\t.getByRole(role, { name, exact: true, includeHidden: true })\n\t\t\t.elements()) {\n\t\t\tif (element instanceof HTMLElement && !matches.includes(element)) matches.push(element)\n\t\t}\n\t}\n\tif (matches.length === 0) {\n\t\tthrow new Error(`No interactive element has the accessible name \"${name}\"`)\n\t}\n\tconst reachable = matches.filter((element) => {\n\t\tconst rectangle = element.getBoundingClientRect()\n\t\treturn (\n\t\t\telement.isConnected &&\n\t\t\telement.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }) &&\n\t\t\trectangle.width > 0 &&\n\t\t\trectangle.height > 0 &&\n\t\t\telement.tabIndex >= 0 &&\n\t\t\t!element.matches(':disabled, [aria-disabled=\"true\"]') &&\n\t\t\telement.closest('[inert]') === null\n\t\t)\n\t})\n\tif (reachable.length === 0) {\n\t\tthrow new Error(`Interactive target \"${name}\" is not visible and focus-reachable`)\n\t}\n\tif (reachable.length > 1) {\n\t\tthrow new Error(`Interactive target \"${name}\" is ambiguous across ${reachable.length} elements`)\n\t}\n\tconst [target] = reachable\n\tif (target === undefined) throw new Error(`Interactive target \"${name}\" could not be resolved`)\n\treturn target\n}\n\n/**\n * Resolves one visible, focus-reachable interactive element by its exact accessible name. A\n * wholly-off-viewport target is scrolled into view before reachability is measured.\n *\n * @param name - The accessible name rendered for the target.\n * @returns The one reachable element carrying that name.\n * @throws When no matching element exists; every match is disconnected, hidden, zero-sized, still\n * outside the viewport after being scrolled into view, removed from sequential focus, disabled, or\n * inside an inert subtree; or several reachable matches make the name ambiguous.\n *\n * @example\n * ```ts\n * resolveAccessible('Save changes')\n * ```\n */\nexport function resolveAccessible(name: string): HTMLElement\n/**\n * Resolves one visible, focus-reachable interactive element by its exact ARIA role and accessible\n * name, disambiguating a bare name that answers for more than one rendered element. A\n * wholly-off-viewport target is scrolled into view before reachability is measured.\n *\n * @param role - The element's exact ARIA role.\n * @param name - The accessible name rendered for the target.\n * @returns The one reachable element carrying that role and name.\n * @throws When no matching element exists; every match is disconnected, hidden, zero-sized, still\n * outside the viewport after being scrolled into view, removed from sequential focus, disabled, or\n * inside an inert subtree; or several reachable matches make the role/name pair ambiguous.\n *\n * @example\n * ```ts\n * resolveAccessible('tab', 'Drafts')\n * ```\n */\nexport function resolveAccessible(role: string, name: string): HTMLElement\nexport function resolveAccessible(first: string, second?: string): HTMLElement {\n\tconst target = resolveRendered(first, second)\n\tlet rectangle = target.getBoundingClientRect()\n\tif (isOutsideViewport(rectangle)) {\n\t\ttarget.scrollIntoView({ block: 'nearest', behavior: 'instant' })\n\t\trectangle = target.getBoundingClientRect()\n\t}\n\tif (isOutsideViewport(rectangle)) {\n\t\tthrow new Error(`Interactive target \"${second ?? first}\" is unreachable after scrolling`)\n\t}\n\treturn target\n}\n\n/**\n * Clicks one visible, focus-reachable control by its accessible name through the browser provider.\n *\n * @param name - The target's exact accessible name.\n * @returns A promise resolving after trusted activation completes.\n *\n * @example\n * ```ts\n * await clickAccessible('Apply')\n * ```\n */\nexport async function clickAccessible(name: string): Promise<void>\n/**\n * Clicks one visible, focus-reachable control by its exact ARIA role and accessible name,\n * disambiguating a bare name that answers for more than one rendered element.\n *\n * @param role - The control's exact ARIA role.\n * @param name - The target's exact accessible name.\n * @returns A promise resolving after trusted activation completes.\n *\n * @example\n * ```ts\n * await clickAccessible('tab', 'Drafts')\n * ```\n */\nexport async function clickAccessible(role: string, name: string): Promise<void>\nexport async function clickAccessible(first: string, second?: string): Promise<void> {\n\tconst target = resolveRendered(first, second)\n\tawait userEvent.click(target)\n}\n\n/**\n * Clicks one human-reachable control by role and accessible-name text inside a named region.\n *\n * @param region - The containing region's exact accessible name.\n * @param role - The control's exact ARIA role.\n * @param name - The rendered accessible-name text that identifies the control in that region.\n * @returns A promise resolving after trusted activation completes.\n * @throws When the named control is absent, unreachable, or ambiguous inside the region.\n *\n * @remarks\n * Use this form when repeated short verbs such as `Add`, or a line whose status completes its\n * accessible name, need the same region context a person uses to disambiguate them.\n *\n * @example\n * ```ts\n * await clickAccessibleWithin('Ledger', 'button', 'Monthly income')\n * ```\n */\nexport async function clickAccessibleWithin(\n\tregion: string,\n\trole: string,\n\tname: string,\n): Promise<void> {\n\tconst matches = page\n\t\t.getByRole('region', { name: region, exact: true })\n\t\t.getByRole(role, { name, exact: false, includeHidden: true })\n\t\t.elements()\n\tconst reachable = matches.filter((element) => {\n\t\tif (!(element instanceof HTMLElement)) return false\n\t\tconst rectangle = element.getBoundingClientRect()\n\t\treturn (\n\t\t\telement.isConnected &&\n\t\t\telement.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }) &&\n\t\t\trectangle.width > 0 &&\n\t\t\trectangle.height > 0 &&\n\t\t\telement.tabIndex >= 0 &&\n\t\t\t!element.matches(':disabled, [aria-disabled=\"true\"]') &&\n\t\t\telement.closest('[inert]') === null\n\t\t)\n\t})\n\tif (reachable.length === 0) {\n\t\tthrow new Error(`Interactive target \"${name}\" is not reachable inside \"${region}\"`)\n\t}\n\tif (reachable.length > 1) {\n\t\tthrow new Error(\n\t\t\t`Interactive target \"${name}\" is ambiguous across ${reachable.length} elements inside \"${region}\"`,\n\t\t)\n\t}\n\tconst [target] = reachable\n\tif (!(target instanceof HTMLElement)) {\n\t\tthrow new Error(`Interactive target \"${name}\" could not be resolved inside \"${region}\"`)\n\t}\n\tawait userEvent.click(target)\n}\n\n/**\n * Opens or closes one native details disclosure by its rendered summary.\n *\n * @param name - The summary text a person reads.\n * @returns A promise resolving after trusted activation completes.\n * @throws When no visible, focus-reachable native summary has that rendered name, or several do.\n *\n * @remarks\n * Chromium exposes `<summary>` as a native disclosure rather than through an ARIA role accepted by\n * `getByRole`, so this resolver names the platform element and its rendered text directly.\n *\n * @example\n * ```ts\n * await clickDisclosure('Advanced')\n * ```\n */\nexport async function clickDisclosure(name: string): Promise<void> {\n\tconst matches = [...document.querySelectorAll('summary')].filter(\n\t\t(element) => element.innerText.replaceAll(/\\s+/g, ' ').trim() === name,\n\t)\n\tconst reachable = matches.filter((element) => {\n\t\tconst rectangle = element.getBoundingClientRect()\n\t\treturn (\n\t\t\telement.isConnected &&\n\t\t\telement.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }) &&\n\t\t\trectangle.width > 0 &&\n\t\t\trectangle.height > 0 &&\n\t\t\telement.tabIndex >= 0 &&\n\t\t\telement.closest('[inert]') === null\n\t\t)\n\t})\n\tif (reachable.length === 0) {\n\t\tthrow new Error(`Native disclosure \"${name}\" is not visible and focus-reachable`)\n\t}\n\tif (reachable.length > 1) {\n\t\tthrow new Error(`Native disclosure \"${name}\" is ambiguous across ${reachable.length} elements`)\n\t}\n\tconst [target] = reachable\n\tif (target === undefined) throw new Error(`Native disclosure \"${name}\" could not be resolved`)\n\tawait userEvent.click(target)\n}\n\n/**\n * Replaces a named field's value through focus, select-all, deletion, and real keystrokes.\n *\n * @param name - The field's exact accessible name.\n * @param text - The text to type.\n * @returns A promise resolving after every keystroke completes.\n *\n * @example\n * ```ts\n * await typeAccessible('Runs', '3')\n * ```\n */\nexport async function typeAccessible(name: string, text: string): Promise<void> {\n\tawait userEvent.click(resolveRendered(name))\n\tawait userEvent.keyboard('{Control>}a{/Control}{Backspace}')\n\tif (text === '') return\n\tawait userEvent.keyboard(text.replaceAll('{', '{{').replaceAll('[', '[['))\n}\n\n/**\n * Replaces a named field's value in one operation, for text too long to type key by key.\n *\n * @param name - The field's exact accessible name.\n * @param text - The text to place in the field.\n * @returns A promise resolving after the browser commits the value.\n *\n * @remarks\n * The provider drives the real element, so the field publishes the same input event a person's\n * typing publishes. Use {@link typeAccessible} wherever the keystrokes themselves are the subject.\n *\n * @example\n * ```ts\n * await fillAccessible('Payload', '{\"status\":\"ready\"}')\n * ```\n */\nexport async function fillAccessible(name: string, text: string): Promise<void> {\n\tawait userEvent.fill(resolveRendered(name), text)\n}\n\n/**\n * Presses a browser-keyboard sequence using Vitest's installed user-event syntax.\n *\n * @param keys - The keys or key descriptors to press.\n * @returns A promise resolving after the sequence completes.\n *\n * @example\n * ```ts\n * await pressKeys('{ArrowRight}{Enter}')\n * ```\n */\nexport async function pressKeys(keys: string): Promise<void> {\n\tawait userEvent.keyboard(keys)\n}\n\n/**\n * Reaches a named control only through natural forward Tab traversal from the current focus.\n *\n * @param name - The target's exact accessible name.\n * @returns The target after the browser moves focus to it.\n * @throws When one complete traversal cannot reach the target.\n *\n * @example\n * ```ts\n * await traverseAccessible('Evaluate')\n * ```\n */\nexport async function traverseAccessible(name: string): Promise<HTMLElement> {\n\tresolveRendered(name)\n\t// Two facts shape the loop. A Tab pressed before the page has real input focus moves nothing,\n\t// so a step counts only when focus actually lands somewhere; the traversal is over when focus\n\t// revisits an element, because that is one full cycle of the tab order. And the target is\n\t// re-resolved on every step, because a framework may replace the node between resolution and\n\t// focus arrival: the person's target is the role and name, never one node.\n\tconst cap =\n\t\tdocument.querySelectorAll<HTMLElement>('a[href], button, input, select, textarea, [tabindex]')\n\t\t\t.length *\n\t\t\t3 +\n\t\t10\n\tconst visited = new Set<Element>()\n\tconst trail: string[] = []\n\tfor (let attempt = 0; attempt < cap; attempt += 1) {\n\t\tawait userEvent.tab()\n\t\tconst focused = document.activeElement\n\t\tif (!(focused instanceof HTMLElement) || focused === document.body) continue\n\t\tlet current: HTMLElement | undefined\n\t\ttry {\n\t\t\tcurrent = resolveRendered(name)\n\t\t} catch {\n\t\t\tcontinue\n\t\t}\n\t\tif (focused === current) return current\n\t\tif (visited.has(focused)) break\n\t\tvisited.add(focused)\n\t\ttrail.push(`${focused.tagName}:${focused.innerText.slice(0, 20)}`)\n\t}\n\tthrow new Error(\n\t\t`Interactive target \"${name}\" is not reachable through forward Tab traversal: ${trail.join(' > ')}`,\n\t)\n}\n\n/**\n * Reads the normalized visible text of one named region, dialog, table, tab panel, or alert.\n *\n * @param name - The region's exact accessible name.\n * @returns The text a screen reader can perceive in the visible region, including descendant\n * visually-hidden content.\n * @throws When the named region is absent, hidden, or ambiguous.\n *\n * @example\n * ```ts\n * readPerception('Run')\n * ```\n */\nexport function readPerception(name: string): string {\n\tconst matches: HTMLElement[] = []\n\tfor (const role of ['alert', 'alertdialog', 'dialog', 'region', 'status', 'table', 'tabpanel']) {\n\t\tfor (const element of page\n\t\t\t.getByRole(role, { name, exact: true, includeHidden: true })\n\t\t\t.elements()) {\n\t\t\tif (element instanceof HTMLElement && !matches.includes(element)) matches.push(element)\n\t\t}\n\t}\n\tconst visible = matches.filter((element) => {\n\t\tconst rectangle = element.getBoundingClientRect()\n\t\treturn (\n\t\t\telement.isConnected &&\n\t\t\telement.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }) &&\n\t\t\trectangle.width > 0 &&\n\t\t\trectangle.height > 0\n\t\t)\n\t})\n\tif (visible.length === 0) throw new Error(`Named region \"${name}\" is not visible`)\n\tif (visible.length > 1) {\n\t\tthrow new Error(`Named region \"${name}\" is ambiguous across ${visible.length} elements`)\n\t}\n\tconst [region] = visible\n\tif (region === undefined) throw new Error(`Named region \"${name}\" could not be resolved`)\n\treturn region.innerText.replaceAll(/\\s+/g, ' ').trim()\n}\n\n/**\n * Reads the normalized visible text of the whole page.\n *\n * @returns Every rendered word in the document body, its whitespace runs collapsed and trimmed.\n *\n * @remarks\n * This is the reader for a sentence that spans two regions and for a vocabulary sweep over the\n * words an interface uses. Reach for {@link readPerception} wherever one named region is the\n * subject, because that one throws when the region is missing and this one returns whatever is\n * there.\n *\n * @example\n * ```ts\n * readPage().includes('No cases yet')\n * ```\n */\nexport function readPage(): string {\n\treturn document.body.innerText.replaceAll(/\\s+/g, ' ').trim()\n}\n\n/**\n * Reads the rendered text of the element that currently holds focus.\n *\n * @returns The focused HTML element's trimmed rendered text, including an empty string, or\n * `undefined` when focus rests on a non-HTML element. When nothing holds focus, the browser\n * reports the document body as active, so the whole page's rendered text returns.\n *\n * @example\n * ```ts\n * await traverseAccessible('Evaluate')\n * readFocus() // 'Evaluate'\n * ```\n */\nexport function readFocus(): string | undefined {\n\tconst focused = document.activeElement\n\treturn focused instanceof HTMLElement ? focused.innerText.trim() : undefined\n}\n\n/**\n * Reads the value a resolved control renders.\n *\n * @param role - The control's exact ARIA role.\n * @param name - The control's exact accessible name.\n * @returns The control's current value.\n * @throws When the target does not resolve, or resolves to an element that carries no value.\n *\n * @remarks\n * A control's value is a rendered fact a person can read, not internal state, so it is read from\n * the resolved element rather than from the component that produced it.\n *\n * @example\n * ```ts\n * readValue('spinbutton', 'Runs') // '3'\n * ```\n */\nexport function readValue(role: string, name: string): string {\n\tconst control = resolveAccessible(role, name)\n\tif (\n\t\t!(control instanceof HTMLInputElement) &&\n\t\t!(control instanceof HTMLTextAreaElement) &&\n\t\t!(control instanceof HTMLSelectElement)\n\t) {\n\t\tthrow new Error(`Interactive target \"${name}\" does not carry a value`)\n\t}\n\treturn control.value\n}\n\n/**\n * Waits for one animation frame to settle pending browser paint work.\n *\n * @returns A promise resolving after one `requestAnimationFrame`.\n *\n * @example\n * ```ts\n * await waitForFrame()\n * ```\n */\nexport function waitForFrame(): Promise<void> {\n\treturn new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))\n}\n\n/**\n * Renders trusted fixture markup into a container attached to the document.\n *\n * @param markup - The fixture markup to render.\n * @returns The attached container.\n *\n * @example\n * ```ts\n * const container = render('<button type=\"button\">Save</button>')\n * container.remove()\n * ```\n */\nexport function render(markup: string): HTMLDivElement {\n\tconst container = document.createElement('div')\n\tcontainer.innerHTML = markup\n\tdocument.body.append(container)\n\treturn container\n}\n\n/**\n * Measures the WCAG 2.x contrast ratio between an element's computed text and background colors.\n *\n * @param element - The element whose rendered text contrast to measure.\n * @returns The relative-luminance contrast ratio.\n * @throws When the browser does not expose parseable computed colors.\n *\n * @remarks\n * A transparent or translucent background resolves through the element's ancestors: every painted\n * layer from the element up to the first opaque one composites top-over-bottom onto that opaque\n * base, so a 3% surface tint reads as a tint over what shows through it rather than as a\n * full-strength paint. A translucent foreground then resolves against that effective background\n * before luminance is measured.\n *\n * Every element from the target upwards must be reachable, and at least one of them must paint:\n * the measurement throws rather than assuming a white canvas when nothing in the chain declares a\n * background color. The element itself must expose a computed foreground color — a detached\n * element exposes none, and the measurement throws rather than guessing one.\n *\n * @example\n * ```ts\n * const container = render('<p style=\"background: #000; color: #fff\">Ready</p>')\n * contrast(requireValue(container.firstElementChild)) // 21\n * ```\n */\nexport function contrast(element: Element): number {\n\tconst foreground = getComputedStyle(element).color.match(/\\d+(?:\\.\\d+)?/g)\n\tif (foreground === null || foreground.length < 3) {\n\t\tthrow new Error('Computed foreground color is unavailable')\n\t}\n\tconst layers: number[][] = []\n\tlet current: Element | null = element\n\tlet opaque = false\n\twhile (current !== null) {\n\t\tconst channels = getComputedStyle(current).backgroundColor.match(/\\d+(?:\\.\\d+)?/g)\n\t\tif (channels !== null && channels.length >= 3) {\n\t\t\tconst layerAlpha = channels[3] === undefined ? 1 : Number(channels[3])\n\t\t\tif (layerAlpha > 0) {\n\t\t\t\tlayers.push([...channels.slice(0, 3).map(Number), layerAlpha])\n\t\t\t}\n\t\t\tif (layerAlpha >= 1) {\n\t\t\t\topaque = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tcurrent = current.parentElement\n\t}\n\tif (layers.length === 0) throw new Error('Computed background color is unavailable')\n\tconst base = layers[layers.length - 1]\n\tif (base === undefined) throw new Error('Computed background color is unavailable')\n\tlet composed = base.slice(0, 3).map((channel) => channel / 255)\n\tif (!opaque) composed = [1, 1, 1]\n\tconst start = opaque ? layers.length - 2 : layers.length - 1\n\tfor (let index = start; index >= 0; index -= 1) {\n\t\tconst layer = layers[index]\n\t\tif (layer === undefined) continue\n\t\tconst layerAlpha = layer[3] ?? 1\n\t\tcomposed = composed.map((channel, position) => {\n\t\t\tconst top = (layer[position] ?? 0) / 255\n\t\t\treturn top * layerAlpha + channel * (1 - layerAlpha)\n\t\t})\n\t}\n\n\tconst alpha = foreground[3] === undefined ? 1 : Number(foreground[3])\n\tconst backgroundChannels = composed\n\tconst foregroundChannels = foreground.slice(0, 3).map((channel, index) => {\n\t\tconst behind = backgroundChannels[index]\n\t\tif (behind === undefined) throw new Error('Computed background channel is unavailable')\n\t\treturn (Number(channel) / 255) * alpha + behind * (1 - alpha)\n\t})\n\tconst foregroundLinear = foregroundChannels.map((channel) =>\n\t\tchannel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4,\n\t)\n\tconst backgroundLinear = backgroundChannels.map((channel) =>\n\t\tchannel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4,\n\t)\n\tconst foregroundLuminance =\n\t\t0.2126 * (foregroundLinear[0] ?? 0) +\n\t\t0.7152 * (foregroundLinear[1] ?? 0) +\n\t\t0.0722 * (foregroundLinear[2] ?? 0)\n\tconst backgroundLuminance =\n\t\t0.2126 * (backgroundLinear[0] ?? 0) +\n\t\t0.7152 * (backgroundLinear[1] ?? 0) +\n\t\t0.0722 * (backgroundLinear[2] ?? 0)\n\tconst lighter = Math.max(foregroundLuminance, backgroundLuminance)\n\tconst darker = Math.min(foregroundLuminance, backgroundLuminance)\n\treturn (lighter + 0.05) / (darker + 0.05)\n}\n\n/**\n * Collects every class token the stylesheets loaded into this document actually define.\n *\n * @returns The set of class names reachable in the shipped cascade.\n *\n * @remarks\n * The set is what an authored-class conformance check measures against, so a class no loaded\n * stylesheet defines — an invented utility, a misspelled framework name — is absent from it.\n *\n * @example\n * ```ts\n * readCascade().has('card')\n * ```\n */\nexport function readCascade(): ReadonlySet<string> {\n\tconst known = new Set<string>()\n\tconst rules: CSSRule[] = []\n\tfor (const sheet of document.styleSheets) rules.push(...sheet.cssRules)\n\twhile (rules.length > 0) {\n\t\tconst rule = rules.pop()\n\t\tif (rule instanceof CSSGroupingRule) rules.push(...rule.cssRules)\n\t\tif (!(rule instanceof CSSStyleRule)) continue\n\t\tfor (const match of rule.selectorText.matchAll(/\\.([a-zA-Z][\\w-]*)/g)) {\n\t\t\tknown.add(String(match[1]))\n\t\t}\n\t}\n\treturn known\n}\n\n/**\n * Reads the normalized visible text of every element a selector matches, in document order.\n *\n * @param root - The subtree to search.\n * @param selector - The CSS selector naming the rows.\n * @returns One line per matched element, its text runs collapsed and single-space joined.\n *\n * @remarks\n * The line is built from the row's text nodes rather than from `textContent`, because adjacent\n * inline elements carry no whitespace between them in compiled template output and would otherwise\n * read as one run-together word.\n *\n * @example\n * ```ts\n * readRows(container, 'li')\n * ```\n */\nexport function readRows(root: ParentNode, selector: string): readonly string[] {\n\tconst rows: string[] = []\n\tfor (const row of root.querySelectorAll(selector)) {\n\t\tconst parts: string[] = []\n\t\tconst walker = document.createTreeWalker(row, NodeFilter.SHOW_TEXT)\n\t\twhile (walker.nextNode() !== null) {\n\t\t\tconst text = (walker.currentNode.textContent ?? '').replaceAll(/\\s+/g, ' ').trim()\n\t\t\tif (text !== '') parts.push(text)\n\t\t}\n\t\trows.push(parts.join(' '))\n\t}\n\treturn rows\n}\n\n/**\n * Reads one resolved CSS property from a real browser element.\n *\n * @param element - The element whose resolved style to inspect.\n * @param property - The CSS property name.\n * @returns The browser's resolved property value.\n *\n * @example\n * ```ts\n * style(button, 'padding-left')\n * ```\n */\nexport function style(element: Element, property: string): string {\n\treturn getComputedStyle(element).getPropertyValue(property)\n}\n\n/**\n * Expands a capture registry across every variant into the filenames a complete portfolio holds.\n *\n * @param states - The registered state names.\n * @param variants - The variants the portfolio is rendered in.\n * @returns One `<state>--<variant>.png` name per pair, each state's variants together, in registry\n * order.\n *\n * @remarks\n * The expansion is the portfolio's own definition of complete, so a duplicate in it is a registry\n * defect a proof reads directly rather than a collision discovered on disk.\n *\n * @example\n * ```ts\n * expandCaptures(['start'], [{ name: 'dark-390', width: 390, height: 844 }])\n * // ['start--dark-390.png']\n * ```\n */\nexport function expandCaptures(\n\tstates: readonly string[],\n\tvariants: readonly CaptureVariant[],\n): readonly string[] {\n\tconst files: string[] = []\n\tfor (const state of states) {\n\t\tfor (const variant of variants) files.push(`${state}--${variant.name}.png`)\n\t}\n\treturn files\n}\n","import type { PortfolioInterface, PortfolioOptions } from './types.js'\nimport { page } from 'vitest/browser'\nimport { expandCaptures } from './helpers.js'\n\n/**\n * Creates the capture portfolio one run places its screenshots through.\n *\n * @param options - The state registry, the variant matrix, the variant this run renders, the\n * directory it writes into, and whether it writes at all.\n * @returns The portfolio: its registry expansion, what it has placed, and `place`.\n * @throws When no registered variant carries the name `variant` names.\n *\n * @remarks\n * A disabled portfolio is the ordinary run. `place` then resizes nothing, writes nothing, and\n * records nothing, so a journey calls it unconditionally and a suite with the flag unset pays for\n * none of it. The portfolio refuses an unregistered variant at creation. An enabled run refuses an\n * unregistered state name and a second placement of one state.\n *\n * @example\n * ```ts\n * const portfolio = createPortfolio({\n * \tstates: ['start-empty'],\n * \tvariants: [{ name: 'dark-390', width: 390, height: 844 }],\n * \tvariant: 'dark-390',\n * \tdirectory: '../../tmp/capture/states',\n * })\n * await portfolio.place('start-empty')\n * ```\n */\nexport function createPortfolio(options: PortfolioOptions): PortfolioInterface {\n\tconst selected = options.variants.find((candidate) => candidate.name === options.variant)\n\tif (selected === undefined) {\n\t\tthrow new Error(`Capture variant \"${options.variant}\" is not registered`)\n\t}\n\tconst registry = [...options.states]\n\tconst files = expandCaptures(registry, options.variants)\n\tconst enabled = options.enabled ?? false\n\tconst placed: string[] = []\n\tconst paths: string[] = []\n\treturn {\n\t\tvariant: options.variant,\n\t\tfiles,\n\t\tget states() {\n\t\t\treturn [...placed]\n\t\t},\n\t\tget paths() {\n\t\t\treturn [...paths]\n\t\t},\n\t\tasync place(state) {\n\t\t\tif (!enabled) return undefined\n\t\t\tif (!registry.includes(state)) {\n\t\t\t\tthrow new Error(`Capture state \"${state}\" is not registered`)\n\t\t\t}\n\t\t\tif (placed.includes(state)) {\n\t\t\t\tthrow new Error(`Capture state \"${state}\" is already placed`)\n\t\t\t}\n\t\t\tconst file = `${state}--${options.variant}.png`\n\t\t\tselected.apply?.()\n\t\t\tif (window.innerWidth !== selected.width || window.innerHeight !== selected.height) {\n\t\t\t\tawait page.viewport(selected.width, selected.height)\n\t\t\t}\n\t\t\tconst written = await page.screenshot({ path: `${options.directory}/${file}` })\n\t\t\tplaced.push(state)\n\t\t\tpaths.push(written)\n\t\t\treturn written\n\t\t},\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/constants.ts","../../../src/browser/helpers.ts","../../../src/browser/factories.ts"],"sourcesContent":["import type { Color } from './types.js'\n\n/**\n * The interactive ARIA roles a bare accessible name is searched across.\n *\n * @remarks\n * A person names a control, not a role, so the one-argument resolver searches every role a control\n * can compute. The two-argument form searches exactly the role it is given, which is how a name\n * shared by a tab and its panel is disambiguated.\n */\nexport const ACCESSIBLE_ROLES: readonly string[] = Object.freeze([\n\t'button',\n\t'checkbox',\n\t'combobox',\n\t'link',\n\t'listbox',\n\t'menuitem',\n\t'option',\n\t'radio',\n\t'searchbox',\n\t'slider',\n\t'spinbutton',\n\t'switch',\n\t'tab',\n\t'tabpanel',\n\t'textbox',\n\t'treeitem',\n])\n\n/**\n * The page a browser paints an unstyled document onto.\n *\n * @remarks\n * This is the floor a backdrop walk ends on wherever the caller wants the browser's own canvas\n * assumed. `readBackdrop` takes its floor as an argument rather than reaching for this one, so a\n * measurement over a surface the canvas never shows through names the color it actually sits on.\n */\nexport const CANVAS_COLOR: Color = Object.freeze([255, 255, 255, 1])\n\n/**\n * The attribute marking the runner's tester pane, and the rule that sizes it, while a frame is\n * staged.\n *\n * @remarks\n * `stagePane` writes it onto the pane and onto the stylesheet it appends, and `releasePane` finds\n * both by it. Nothing else reads it, so a document carrying it after a capture returned is a pane\n * that was never released.\n */\nexport const CAPTURE_PANE = 'data-capture-pane'\n\n/**\n * The roles whose accessible name is the text a reader can see inside them.\n *\n * @remarks\n * `readName` reads an element in this list from its own rendered text, after every `aria-hidden`\n * descendant is dropped, and falls through to `title` for every other role.\n */\nexport const CONTENT_ROLES: readonly string[] = Object.freeze([\n\t'button',\n\t'cell',\n\t'columnheader',\n\t'heading',\n\t'link',\n\t'listitem',\n\t'option',\n\t'row',\n\t'rowheader',\n\t'tab',\n])\n\n/**\n * The role each `input` type carries.\n *\n * @remarks\n * Membership is the contract. The map answers for `button`, `checkbox`, `email`, `number`,\n * `password`, `radio`, `range`, `reset`, `search`, `submit`, `tel`, `text`, and `url`. A type the\n * map omits — `color`, `date`, `file`, `hidden`, and the rest — exposes no role of its own, so\n * `readRole` returns `undefined` for it and `describeTree` writes no line for it.\n */\nexport const FIELD_ROLES: Readonly<Record<string, string>> = Object.freeze({\n\tbutton: 'button',\n\tcheckbox: 'checkbox',\n\temail: 'textbox',\n\tnumber: 'spinbutton',\n\tpassword: 'textbox',\n\tradio: 'radio',\n\trange: 'slider',\n\treset: 'button',\n\tsearch: 'searchbox',\n\tsubmit: 'button',\n\ttel: 'textbox',\n\ttext: 'textbox',\n\turl: 'textbox',\n})\n\n/**\n * What sequential keyboard navigation can reach, before disabled and unrendered elements go.\n *\n * @remarks\n * `describeFocus` queries this selector and then drops what a browser drops: an element the\n * accessibility tree does not present, a disabled control, and one removed from the sequence by\n * `tabindex=\"-1\"`. `traverseAccessible` counts the same population to bound its walk, so this is\n * the one list either one reads.\n */\nexport const FOCUSABLE_SELECTOR =\n\t'a[href], area[href], button, input, select, summary, textarea, [tabindex]'\n\n/**\n * The role a `th` carries for the header axis its `scope` names.\n *\n * @remarks\n * A header cell heads a column or a row, and this map answers for the `col` and `row` scopes that\n * say which. A `th` declaring no scope keeps {@link IMPLICIT_ROLES}' `columnheader` rather than the\n * ARIA computation that infers the axis from the table's shape.\n */\nexport const HEADER_ROLES: Readonly<Record<string, string>> = Object.freeze({\n\tcol: 'columnheader',\n\trow: 'rowheader',\n})\n\n/**\n * The role each listed tag carries in the accessibility tree when it declares none of its own.\n *\n * @remarks\n * Membership is the contract. The map answers for the sectioning elements `ARTICLE`, `ASIDE`,\n * `FOOTER`, `HEADER`, `MAIN`, `NAV`, `SEARCH`, and `SECTION`; the headings `H1` through `H6`; the\n * grouping and list elements `FIELDSET`, `FORM`, `HR`, `LI`, `OL`, and `UL`; the table elements\n * `TABLE`, `TBODY`, `THEAD`, `TR`, `TD`, and `TH`; and the widgets `BUTTON`, `DIALOG`, `IMG`,\n * `OPTION`, `OUTPUT`, `PROGRESS`, `SUMMARY`, and `TEXTAREA`.\n *\n * A tag the map omits carries no implicit role, so `readRole` returns `undefined` for it,\n * `describeTree` writes no line for it, and the walk continues straight into its children at the\n * depth the omitted element sat at. `A`, `INPUT`, and `SELECT` are absent deliberately: each takes\n * its role from an attribute rather than from its tag, and `readRole` answers for them from their\n * own anatomy.\n *\n * `SECTION` maps to `region`, which `readRole` withholds from an unnamed one, because an unnamed\n * section is not a landmark. `TH` maps to `columnheader`, which {@link HEADER_ROLES} replaces when\n * the cell declares a `scope`.\n */\nexport const IMPLICIT_ROLES: Readonly<Record<string, string>> = Object.freeze({\n\tARTICLE: 'article',\n\tASIDE: 'complementary',\n\tBUTTON: 'button',\n\tDIALOG: 'dialog',\n\tFIELDSET: 'group',\n\tFOOTER: 'contentinfo',\n\tFORM: 'form',\n\tH1: 'heading',\n\tH2: 'heading',\n\tH3: 'heading',\n\tH4: 'heading',\n\tH5: 'heading',\n\tH6: 'heading',\n\tHEADER: 'banner',\n\tHR: 'separator',\n\tIMG: 'img',\n\tLI: 'listitem',\n\tMAIN: 'main',\n\tNAV: 'navigation',\n\tOL: 'list',\n\tOPTION: 'option',\n\tOUTPUT: 'status',\n\tPROGRESS: 'progressbar',\n\tSEARCH: 'search',\n\tSECTION: 'region',\n\tSUMMARY: 'button',\n\tTABLE: 'table',\n\tTBODY: 'rowgroup',\n\tTD: 'cell',\n\tTEXTAREA: 'textbox',\n\tTH: 'columnheader',\n\tTHEAD: 'rowgroup',\n\tTR: 'row',\n\tUL: 'list',\n})\n","import type { CaptureVariant, Color, FrameOptions } from './types.js'\nimport { commands, page, userEvent } from 'vitest/browser'\nimport {\n\tACCESSIBLE_ROLES,\n\tCANVAS_COLOR,\n\tCAPTURE_PANE,\n\tCONTENT_ROLES,\n\tFIELD_ROLES,\n\tFOCUSABLE_SELECTOR,\n\tHEADER_ROLES,\n\tIMPLICIT_ROLES,\n} from './constants.js'\n\n/**\n * Determines whether a rectangle lies wholly outside the browser viewport.\n *\n * @param rectangle - The measured client rectangle to inspect.\n * @returns `true` when no part of the rectangle intersects the viewport.\n *\n * @example\n * ```ts\n * isOutsideViewport(element.getBoundingClientRect())\n * ```\n */\nexport function isOutsideViewport(rectangle: DOMRectReadOnly): boolean {\n\treturn (\n\t\trectangle.bottom <= 0 ||\n\t\trectangle.right <= 0 ||\n\t\trectangle.top >= window.innerHeight ||\n\t\trectangle.left >= window.innerWidth\n\t)\n}\n\n/**\n * Determines whether a person can click one element where it currently sits.\n *\n * @param element - The element to judge.\n * @returns `true` when the element is connected, visible, laid out with a non-zero box, in the\n * sequential focus order, neither disabled nor marked `aria-disabled=\"true\"`, and outside every\n * `[inert]` subtree; `false` otherwise.\n *\n * @remarks\n * This is the one reachability filter the layer applies. `resolveRendered`, `clickAccessibleWithin`,\n * and `clickDisclosure` each narrow their own candidates and then keep the ones this accepts, so a\n * journey meets one rule rather than three near-copies of it.\n *\n * It measures geometry, which is what separates it from {@link isRendered}. A control clipped to a\n * zero-size rectangle is announced and is not clickable, so `isRendered` accepts it and this\n * refuses it. Nothing here asks about the viewport: `resolveAccessible` scrolls a wholly\n * off-viewport target into view and measures that separately with {@link isOutsideViewport}.\n *\n * @example\n * ```ts\n * isReachable(requireValue(container.querySelector('button')))\n * ```\n */\nexport function isReachable(element: Element): boolean {\n\tif (!(element instanceof HTMLElement) && !(element instanceof SVGElement)) return false\n\tconst rectangle = element.getBoundingClientRect()\n\treturn (\n\t\telement.isConnected &&\n\t\telement.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }) &&\n\t\trectangle.width > 0 &&\n\t\trectangle.height > 0 &&\n\t\telement.tabIndex >= 0 &&\n\t\t!element.matches(':disabled, [aria-disabled=\"true\"]') &&\n\t\telement.closest('[inert]') === null\n\t)\n}\n\n/**\n * Determines whether the accessibility tree presents one element at all.\n *\n * @param element - The element to judge.\n * @returns `false` when the element is hidden from assistive technology, from sight, or from both;\n * `true` otherwise.\n *\n * @remarks\n * A control clipped to a zero-size rectangle is still announced, which is the whole point of that\n * idiom, so nothing here reads geometry: only the removals a browser honours — `aria-hidden`\n * anywhere above it, the `hidden` attribute, a hidden input, and a `display` or `visibility` that\n * takes it off the page. {@link isReachable} is the clickable half of the pair and does read\n * geometry.\n *\n * The last two are asked about the element's ancestors as well as itself, which reading a computed\n * `display` cannot do: the computed value of a child of a `display: none` container is the child's\n * own, so a control inside a closed drawer reports itself as laid out. `checkVisibility` answers\n * for the box tree, and `visibility` inherits, so between them an ancestor cannot hide a control\n * from a reader and leave it standing in a description.\n *\n * @example\n * ```ts\n * isRendered(requireValue(container.querySelector('[aria-hidden=\"true\"] button'))) // false\n * ```\n */\nexport function isRendered(element: Element): boolean {\n\tif (element.closest('[aria-hidden=\"true\"]') !== null) return false\n\tif (element instanceof HTMLElement && element.hidden) return false\n\tif (element instanceof HTMLInputElement && element.type === 'hidden') return false\n\tif (!element.checkVisibility()) return false\n\treturn getComputedStyle(element).visibility !== 'hidden'\n}\n\n/**\n * Resolves one rendered, focus-reachable interactive element without requiring it to intersect the\n * viewport yet.\n *\n * @param first - The accessible name, or the exact ARIA role when `second` is present.\n * @param second - The accessible name when `first` supplies the role.\n * @returns The one rendered element carrying that name and optional role.\n * @throws When no matching element exists, every match is hidden or unreachable, or several\n * rendered matches make the name ambiguous.\n *\n * @remarks\n * This is the resolver the acting verbs use, so a click does not fail on a target the act itself\n * scrolls into view. Use {@link resolveAccessible} wherever the target must already be on screen.\n *\n * @example\n * ```ts\n * resolveRendered('tab', 'Drafts')\n * ```\n */\nexport function resolveRendered(first: string, second?: string): HTMLElement {\n\tconst name = second ?? first\n\tconst roles = second === undefined ? ACCESSIBLE_ROLES : [first]\n\tconst matches: HTMLElement[] = []\n\tfor (const role of roles) {\n\t\tfor (const element of page\n\t\t\t.getByRole(role, { name, exact: true, includeHidden: true })\n\t\t\t.elements()) {\n\t\t\tif (element instanceof HTMLElement && !matches.includes(element)) matches.push(element)\n\t\t}\n\t}\n\tif (matches.length === 0) {\n\t\tthrow new Error(`No interactive element has the accessible name \"${name}\"`)\n\t}\n\tconst reachable = matches.filter((element) => isReachable(element))\n\tif (reachable.length === 0) {\n\t\tthrow new Error(`Interactive target \"${name}\" is not visible and focus-reachable`)\n\t}\n\tif (reachable.length > 1) {\n\t\tthrow new Error(`Interactive target \"${name}\" is ambiguous across ${reachable.length} elements`)\n\t}\n\tconst [target] = reachable\n\tif (target === undefined) throw new Error(`Interactive target \"${name}\" could not be resolved`)\n\treturn target\n}\n\n/**\n * Resolves one visible, focus-reachable interactive element by its exact accessible name. A\n * wholly-off-viewport target is scrolled into view before reachability is measured.\n *\n * @param name - The accessible name rendered for the target.\n * @returns The one reachable element carrying that name.\n * @throws When no matching element exists; every match is disconnected, hidden, zero-sized, still\n * outside the viewport after being scrolled into view, removed from sequential focus, disabled, or\n * inside an inert subtree; or several reachable matches make the name ambiguous.\n *\n * @example\n * ```ts\n * resolveAccessible('Save changes')\n * ```\n */\nexport function resolveAccessible(name: string): HTMLElement\n/**\n * Resolves one visible, focus-reachable interactive element by its exact ARIA role and accessible\n * name, disambiguating a bare name that answers for more than one rendered element. A\n * wholly-off-viewport target is scrolled into view before reachability is measured.\n *\n * @param role - The element's exact ARIA role.\n * @param name - The accessible name rendered for the target.\n * @returns The one reachable element carrying that role and name.\n * @throws When no matching element exists; every match is disconnected, hidden, zero-sized, still\n * outside the viewport after being scrolled into view, removed from sequential focus, disabled, or\n * inside an inert subtree; or several reachable matches make the role/name pair ambiguous.\n *\n * @example\n * ```ts\n * resolveAccessible('tab', 'Drafts')\n * ```\n */\nexport function resolveAccessible(role: string, name: string): HTMLElement\nexport function resolveAccessible(first: string, second?: string): HTMLElement {\n\tconst target = resolveRendered(first, second)\n\tlet rectangle = target.getBoundingClientRect()\n\tif (isOutsideViewport(rectangle)) {\n\t\ttarget.scrollIntoView({ block: 'nearest', behavior: 'instant' })\n\t\trectangle = target.getBoundingClientRect()\n\t}\n\tif (isOutsideViewport(rectangle)) {\n\t\tthrow new Error(`Interactive target \"${second ?? first}\" is unreachable after scrolling`)\n\t}\n\treturn target\n}\n\n/**\n * Clicks one visible, focus-reachable control by its accessible name through the browser provider.\n *\n * @param name - The target's exact accessible name.\n * @returns A promise resolving after trusted activation completes.\n *\n * @example\n * ```ts\n * await clickAccessible('Apply')\n * ```\n */\nexport async function clickAccessible(name: string): Promise<void>\n/**\n * Clicks one visible, focus-reachable control by its exact ARIA role and accessible name,\n * disambiguating a bare name that answers for more than one rendered element.\n *\n * @param role - The control's exact ARIA role.\n * @param name - The target's exact accessible name.\n * @returns A promise resolving after trusted activation completes.\n *\n * @example\n * ```ts\n * await clickAccessible('tab', 'Drafts')\n * ```\n */\nexport async function clickAccessible(role: string, name: string): Promise<void>\nexport async function clickAccessible(first: string, second?: string): Promise<void> {\n\tconst target = resolveRendered(first, second)\n\tawait userEvent.click(target)\n}\n\n/**\n * Clicks one human-reachable control by role and accessible-name text inside a named region.\n *\n * @param region - The containing region's exact accessible name.\n * @param role - The control's exact ARIA role.\n * @param name - The rendered accessible-name text that identifies the control in that region.\n * @returns A promise resolving after trusted activation completes.\n * @throws When the named control is absent, unreachable, or ambiguous inside the region.\n *\n * @remarks\n * Use this form when repeated short verbs such as `Add`, or a line whose status completes its\n * accessible name, need the same region context a person uses to disambiguate them.\n *\n * @example\n * ```ts\n * await clickAccessibleWithin('Ledger', 'button', 'Monthly income')\n * ```\n */\nexport async function clickAccessibleWithin(\n\tregion: string,\n\trole: string,\n\tname: string,\n): Promise<void> {\n\tconst matches = page\n\t\t.getByRole('region', { name: region, exact: true })\n\t\t.getByRole(role, { name, exact: false, includeHidden: true })\n\t\t.elements()\n\tconst reachable = matches.filter(\n\t\t(element) => element instanceof HTMLElement && isReachable(element),\n\t)\n\tif (reachable.length === 0) {\n\t\tthrow new Error(`Interactive target \"${name}\" is not reachable inside \"${region}\"`)\n\t}\n\tif (reachable.length > 1) {\n\t\tthrow new Error(\n\t\t\t`Interactive target \"${name}\" is ambiguous across ${reachable.length} elements inside \"${region}\"`,\n\t\t)\n\t}\n\tconst [target] = reachable\n\tif (!(target instanceof HTMLElement)) {\n\t\tthrow new Error(`Interactive target \"${name}\" could not be resolved inside \"${region}\"`)\n\t}\n\tawait userEvent.click(target)\n}\n\n/**\n * Opens or closes one native details disclosure by its rendered summary.\n *\n * @param name - The summary text a person reads.\n * @returns A promise resolving after trusted activation completes.\n * @throws When no native summary with that rendered name passes {@link isReachable}, or several do.\n *\n * @remarks\n * Chromium exposes `<summary>` as a native disclosure rather than through an ARIA role accepted by\n * `getByRole`, so this resolver names the platform element and its rendered text directly.\n *\n * It applies the same {@link isReachable} filter the other acting verbs apply, so a summary marked\n * `aria-disabled=\"true\"` is refused here exactly as a button marked that way is refused there.\n *\n * @example\n * ```ts\n * await clickDisclosure('Advanced')\n * ```\n */\nexport async function clickDisclosure(name: string): Promise<void> {\n\tconst matches = [...document.querySelectorAll('summary')].filter(\n\t\t(element) => element.innerText.replaceAll(/\\s+/g, ' ').trim() === name,\n\t)\n\tconst reachable = matches.filter((element) => isReachable(element))\n\tif (reachable.length === 0) {\n\t\tthrow new Error(`Native disclosure \"${name}\" is not visible and focus-reachable`)\n\t}\n\tif (reachable.length > 1) {\n\t\tthrow new Error(`Native disclosure \"${name}\" is ambiguous across ${reachable.length} elements`)\n\t}\n\tconst [target] = reachable\n\tif (target === undefined) throw new Error(`Native disclosure \"${name}\" could not be resolved`)\n\tawait userEvent.click(target)\n}\n\n/**\n * Replaces a named field's value through focus, select-all, deletion, and real keystrokes.\n *\n * @param name - The field's exact accessible name.\n * @param text - The text to type.\n * @returns A promise resolving after every keystroke completes.\n *\n * @example\n * ```ts\n * await typeAccessible('Runs', '3')\n * ```\n */\nexport async function typeAccessible(name: string, text: string): Promise<void> {\n\tawait userEvent.click(resolveRendered(name))\n\tawait userEvent.keyboard('{Control>}a{/Control}{Backspace}')\n\tif (text === '') return\n\tawait userEvent.keyboard(text.replaceAll('{', '{{').replaceAll('[', '[['))\n}\n\n/**\n * Replaces a named field's value in one operation, for text too long to type key by key.\n *\n * @param name - The field's exact accessible name.\n * @param text - The text to place in the field.\n * @returns A promise resolving after the browser commits the value.\n *\n * @remarks\n * The provider drives the real element, so the field publishes the same input event a person's\n * typing publishes. Use {@link typeAccessible} wherever the keystrokes themselves are the subject.\n *\n * @example\n * ```ts\n * await fillAccessible('Payload', '{\"status\":\"ready\"}')\n * ```\n */\nexport async function fillAccessible(name: string, text: string): Promise<void> {\n\tawait userEvent.fill(resolveRendered(name), text)\n}\n\n/**\n * Presses a browser-keyboard sequence using Vitest's installed user-event syntax.\n *\n * @param keys - The keys or key descriptors to press.\n * @returns A promise resolving after the sequence completes.\n *\n * @example\n * ```ts\n * await pressKeys('{ArrowRight}{Enter}')\n * ```\n */\nexport async function pressKeys(keys: string): Promise<void> {\n\tawait userEvent.keyboard(keys)\n}\n\n/**\n * Reaches a named control only through natural forward Tab traversal from the current focus.\n *\n * @param name - The target's exact accessible name.\n * @returns The target after the browser moves focus to it.\n * @throws When one complete traversal cannot reach the target.\n *\n * @example\n * ```ts\n * await traverseAccessible('Evaluate')\n * ```\n */\nexport async function traverseAccessible(name: string): Promise<HTMLElement> {\n\tresolveRendered(name)\n\t// Two facts shape the loop. A Tab pressed before the page has real input focus moves nothing,\n\t// so a step counts only when focus actually lands somewhere; the traversal is over when focus\n\t// revisits an element, because that is one full cycle of the tab order. And the target is\n\t// re-resolved on every step, because a framework may replace the node between resolution and\n\t// focus arrival: the person's target is the role and name, never one node.\n\t// The bound is counted off `FOCUSABLE_SELECTOR`, the one population this environment reads\n\t// sequential navigation from, so a tag the selector gains is a tag this traversal budgets for.\n\tconst cap = document.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR).length * 3 + 10\n\tconst visited = new Set<Element>()\n\tconst trail: string[] = []\n\tfor (let attempt = 0; attempt < cap; attempt += 1) {\n\t\tawait userEvent.tab()\n\t\tconst focused = document.activeElement\n\t\tif (!(focused instanceof HTMLElement) || focused === document.body) continue\n\t\tlet current: HTMLElement | undefined\n\t\ttry {\n\t\t\tcurrent = resolveRendered(name)\n\t\t} catch {\n\t\t\tcontinue\n\t\t}\n\t\tif (focused === current) return current\n\t\tif (visited.has(focused)) break\n\t\tvisited.add(focused)\n\t\ttrail.push(`${focused.tagName}:${focused.innerText.slice(0, 20)}`)\n\t}\n\tthrow new Error(\n\t\t`Interactive target \"${name}\" is not reachable through forward Tab traversal: ${trail.join(' > ')}`,\n\t)\n}\n\n/**\n * Reads the normalized visible text of one named region, dialog, table, tab panel, or alert.\n *\n * @param name - The region's exact accessible name.\n * @returns The text a screen reader can perceive in the visible region, including descendant\n * visually-hidden content.\n * @throws When the named region is absent, hidden, or ambiguous.\n *\n * @example\n * ```ts\n * readPerception('Run')\n * ```\n */\nexport function readPerception(name: string): string {\n\tconst matches: HTMLElement[] = []\n\tfor (const role of ['alert', 'alertdialog', 'dialog', 'region', 'status', 'table', 'tabpanel']) {\n\t\tfor (const element of page\n\t\t\t.getByRole(role, { name, exact: true, includeHidden: true })\n\t\t\t.elements()) {\n\t\t\tif (element instanceof HTMLElement && !matches.includes(element)) matches.push(element)\n\t\t}\n\t}\n\tconst visible = matches.filter((element) => {\n\t\tconst rectangle = element.getBoundingClientRect()\n\t\treturn (\n\t\t\telement.isConnected &&\n\t\t\telement.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true }) &&\n\t\t\trectangle.width > 0 &&\n\t\t\trectangle.height > 0\n\t\t)\n\t})\n\tif (visible.length === 0) throw new Error(`Named region \"${name}\" is not visible`)\n\tif (visible.length > 1) {\n\t\tthrow new Error(`Named region \"${name}\" is ambiguous across ${visible.length} elements`)\n\t}\n\tconst [region] = visible\n\tif (region === undefined) throw new Error(`Named region \"${name}\" could not be resolved`)\n\treturn region.innerText.replaceAll(/\\s+/g, ' ').trim()\n}\n\n/**\n * Reads the normalized visible text of the whole page.\n *\n * @returns Every rendered word in the document body, its whitespace runs collapsed and trimmed.\n *\n * @remarks\n * This is the reader for a sentence that spans two regions and for a vocabulary sweep over the\n * words an interface uses. Reach for {@link readPerception} wherever one named region is the\n * subject, because that one throws when the region is missing and this one returns whatever is\n * there.\n *\n * @example\n * ```ts\n * readPage().includes('No cases yet')\n * ```\n */\nexport function readPage(): string {\n\treturn document.body.innerText.replaceAll(/\\s+/g, ' ').trim()\n}\n\n/**\n * Reads the rendered text of the element that currently holds focus.\n *\n * @returns The focused HTML element's trimmed rendered text, including an empty string, or\n * `undefined` when focus rests on a non-HTML element. When nothing holds focus, the browser\n * reports the document body as active, so the whole page's rendered text returns.\n *\n * @example\n * ```ts\n * await traverseAccessible('Evaluate')\n * readFocus() // 'Evaluate'\n * ```\n */\nexport function readFocus(): string | undefined {\n\tconst focused = document.activeElement\n\treturn focused instanceof HTMLElement ? focused.innerText.trim() : undefined\n}\n\n/**\n * Reads the value a resolved control renders.\n *\n * @param role - The control's exact ARIA role.\n * @param name - The control's exact accessible name.\n * @returns The control's current value.\n * @throws When the target does not resolve, or resolves to an element that carries no value.\n *\n * @remarks\n * A control's value is a rendered fact a person can read, not internal state, so it is read from\n * the resolved element rather than from the component that produced it.\n *\n * @example\n * ```ts\n * readValue('spinbutton', 'Runs') // '3'\n * ```\n */\nexport function readValue(role: string, name: string): string {\n\tconst control = resolveAccessible(role, name)\n\tif (\n\t\t!(control instanceof HTMLInputElement) &&\n\t\t!(control instanceof HTMLTextAreaElement) &&\n\t\t!(control instanceof HTMLSelectElement)\n\t) {\n\t\tthrow new Error(`Interactive target \"${name}\" does not carry a value`)\n\t}\n\treturn control.value\n}\n\n/**\n * Reads one element's rendered text the way a name computation reads it.\n *\n * @param element - The element whose announced words are wanted.\n * @returns The text with every `aria-hidden` descendant dropped and whitespace runs collapsed.\n *\n * @remarks\n * A glyph marked `aria-hidden` contributes nothing to a name, so a control captioned by an icon\n * plus a word reads as the word alone — which is what a reader hears, and what a verdict citing a\n * description has to compare against the copy a template writes. Reach for `readRows` wherever the\n * subject is what the page paints rather than what it announces: that one keeps the glyph.\n *\n * @example\n * ```ts\n * readText(requireValue(container.querySelector('button'))) // 'Save'\n * ```\n */\nexport function readText(element: Element): string {\n\tconst parts: string[] = []\n\tconst walker = element.ownerDocument.createTreeWalker(element, NodeFilter.SHOW_TEXT)\n\tfor (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {\n\t\tconst owner = node.parentElement\n\t\tif (owner === null || owner.closest('[aria-hidden=\"true\"]') !== null) continue\n\t\tparts.push(node.textContent ?? '')\n\t}\n\treturn parts.join(' ').replaceAll(/\\s+/g, ' ').trim()\n}\n\n/**\n * Reads the role one element carries in the accessibility tree.\n *\n * @param element - The element to classify.\n * @returns The declared role, the implicit one, or `undefined` when the element carries none.\n *\n * @remarks\n * A declared `role` wins outright, and its first token is the answer when several are listed.\n * Otherwise the element's own anatomy decides: an anchor is a link only while it holds an `href`,\n * an `input` takes the role {@link FIELD_ROLES} gives its type, a `select` is a combobox until it\n * offers several rows at once, a `section` is a region only once something names it, and a `th`\n * heads whichever axis its `scope` names. Every other tag answers from {@link IMPLICIT_ROLES},\n * whose membership is the contract for what this can answer at all.\n *\n * @example\n * ```ts\n * readRole(requireValue(container.querySelector('a[href]'))) // 'link'\n * ```\n */\nexport function readRole(element: Element): string | undefined {\n\tconst declared = element.getAttribute('role')?.trim()\n\tif (declared !== undefined && declared.length > 0) return declared.split(/\\s+/)[0]\n\tif (element instanceof HTMLAnchorElement) return element.href.length > 0 ? 'link' : undefined\n\tif (element instanceof HTMLInputElement) return FIELD_ROLES[element.type]\n\tif (element instanceof HTMLSelectElement) {\n\t\treturn element.multiple || element.size > 1 ? 'listbox' : 'combobox'\n\t}\n\tconst implicit = IMPLICIT_ROLES[element.tagName]\n\tconst scope = element.tagName === 'TH' ? element.getAttribute('scope')?.trim() : undefined\n\tif (scope !== undefined) return HEADER_ROLES[scope] ?? implicit\n\tif (\n\t\timplicit === 'region' &&\n\t\t!element.hasAttribute('aria-label') &&\n\t\t!element.hasAttribute('aria-labelledby')\n\t) {\n\t\treturn undefined\n\t}\n\treturn implicit\n}\n\n/**\n * Reads the accessible name one element is announced under.\n *\n * @param element - The element to name.\n * @returns The computed name, or an empty string when the element carries none.\n *\n * @remarks\n * The order is the one a browser follows: `aria-labelledby`, then `aria-label`, then a form\n * control's own labels, then an image's `alt`, then the text inside a role {@link CONTENT_ROLES}\n * names, then `title`. A submit, reset, or button input is named by its value, because it renders\n * no text to read. An `aria-labelledby` naming several ids joins their texts in the order the\n * attribute lists them, and an id nothing answers for is skipped rather than fatal.\n *\n * Each step answers only when it has something to say, so a step that carries nothing hands the\n * element to the next one. An image whose `alt` is absent or blank is the case that shows it:\n * `<img title=\"Chart\">` is named `Chart` rather than the empty string its own `alt` step would\n * have returned, and an image carrying both keeps answering `alt`.\n *\n * @example\n * ```ts\n * readName(requireValue(container.querySelector('button'))) // 'Save changes'\n * ```\n */\nexport function readName(element: Element): string {\n\tconst referenced = element.getAttribute('aria-labelledby')\n\tif (referenced !== null) {\n\t\tconst named = referenced\n\t\t\t.split(/\\s+/)\n\t\t\t.map((id) => element.ownerDocument.getElementById(id))\n\t\t\t.filter((node) => node !== null)\n\t\t\t.map((node) => readText(node))\n\t\t\t.filter((text) => text.length > 0)\n\t\tif (named.length > 0) return named.join(' ')\n\t}\n\tconst labelled = element.getAttribute('aria-label')?.trim()\n\tif (labelled !== undefined && labelled.length > 0) return labelled\n\tif (\n\t\telement instanceof HTMLInputElement ||\n\t\telement instanceof HTMLSelectElement ||\n\t\telement instanceof HTMLTextAreaElement\n\t) {\n\t\tconst labels = [...(element.labels ?? [])]\n\t\t\t.map((label) => readText(label))\n\t\t\t.filter((text) => text.length > 0)\n\t\tif (labels.length > 0) return labels.join(' ')\n\t\tif (element instanceof HTMLInputElement && element.value.length > 0) {\n\t\t\tif (FIELD_ROLES[element.type] === 'button') return element.value\n\t\t}\n\t}\n\tif (element instanceof HTMLImageElement) {\n\t\tconst alternative = element.alt.trim()\n\t\tif (alternative.length > 0) return alternative\n\t}\n\tconst role = readRole(element)\n\tif (role !== undefined && CONTENT_ROLES.includes(role)) {\n\t\tconst text = readText(element)\n\t\tif (text.length > 0) return text\n\t}\n\treturn element.getAttribute('title')?.trim() ?? ''\n}\n\n/**\n * Reads the states one element is announced in.\n *\n * @param element - The element to read.\n * @returns Every state the element declares, in one fixed order.\n *\n * @remarks\n * A state a reader is told about is one this records: what is unavailable, disclosed, pressed,\n * current, refused, chosen, announcing itself, demanded, uneditable, described, or busy. The order\n * is fixed, so two descriptions of the same surface are comparable line for line.\n *\n * A native disclosure states its expansion on the parent `details` element's own `open` rather than\n * on an ARIA attribute, so a summary that declares no `aria-expanded` is read from the platform's\n * one copy of that fact.\n *\n * @example\n * ```ts\n * readStates(requireValue(container.querySelector('summary'))) // ['collapsed']\n * ```\n */\nexport function readStates(element: Element): readonly string[] {\n\tconst states: string[] = []\n\tif (element.matches(':disabled') || element.getAttribute('aria-disabled') === 'true') {\n\t\tstates.push('disabled')\n\t}\n\tconst expanded = element.getAttribute('aria-expanded')\n\tif (expanded === 'true') states.push('expanded')\n\tif (expanded === 'false') states.push('collapsed')\n\tif (\n\t\texpanded === null &&\n\t\telement.tagName === 'SUMMARY' &&\n\t\telement.parentElement instanceof HTMLDetailsElement\n\t) {\n\t\tstates.push(element.parentElement.open ? 'expanded' : 'collapsed')\n\t}\n\tconst pressed = element.getAttribute('aria-pressed')\n\tif (pressed !== null) states.push(`pressed=${pressed}`)\n\tconst current = element.getAttribute('aria-current')\n\tif (current !== null && current !== 'false') states.push('current')\n\tif (element.getAttribute('aria-invalid') === 'true') states.push('invalid')\n\tconst checked =\n\t\telement instanceof HTMLInputElement\n\t\t\t? element.checked\n\t\t\t: element.getAttribute('aria-checked') === 'true'\n\tif (checked) states.push('checked')\n\tconst selected = element.getAttribute('aria-selected')\n\tif (selected !== null) states.push(`selected=${selected}`)\n\tconst live = element.getAttribute('aria-live')\n\tif (live !== null) states.push(`live=${live}`)\n\tif (element.matches(':required')) states.push('required')\n\tif (element instanceof HTMLInputElement && element.readOnly) states.push('readonly')\n\tif (element.hasAttribute('aria-describedby')) states.push('described')\n\tif (element.getAttribute('aria-busy') === 'true') states.push('busy')\n\treturn Object.freeze(states)\n}\n\n/**\n * Describes the accessible tree one rendered element presents.\n *\n * @param element - The host to walk, which is described first when it carries a role of its own.\n * @returns One indented line per element carrying a role, naming its role, its name, and its\n * states, in document order; an empty string when nothing in the subtree carries one.\n *\n * @remarks\n * The walk is over the real rendered DOM, so what it reports is the tree the shipped markup and the\n * shipped cascade produce together — a landmark lost to a hidden ancestor is missing here exactly as\n * it is missing for a reader. An element {@link isRendered} refuses is dropped with its whole\n * subtree.\n *\n * Depth follows the roles rather than the elements, so the indentation reads as the structure a\n * screen reader announces instead of as the markup's nesting. An element {@link readRole} answers\n * `undefined` for writes no line and adds no depth, so its children sit where it sat. That is how\n * a wrapper `div` disappears, and it is also how an element {@link IMPLICIT_ROLES} does not answer\n * for disappears — visibly, because its roled children stay at the depth it occupied.\n *\n * @example\n * ```ts\n * describeTree(container)\n * // main \"Board\"\n * // heading \"Totals\"\n * ```\n */\nexport function describeTree(element: Element): string {\n\tconst lines: string[] = []\n\tconst pending: Array<{ readonly node: Element; readonly depth: number }> = [\n\t\t{ node: element, depth: 0 },\n\t]\n\twhile (pending.length > 0) {\n\t\tconst entry = pending.pop()\n\t\tif (entry === undefined) break\n\t\tif (!isRendered(entry.node)) continue\n\t\tconst role = readRole(entry.node)\n\t\tlet depth = entry.depth\n\t\tif (role !== undefined) {\n\t\t\tconst name = readName(entry.node)\n\t\t\tconst states = readStates(entry.node)\n\t\t\tlines.push(\n\t\t\t\t`${' '.repeat(depth)}${role}${name.length > 0 ? ` \"${name}\"` : ''}${\n\t\t\t\t\tstates.length > 0 ? ` [${states.join(', ')}]` : ''\n\t\t\t\t}`,\n\t\t\t)\n\t\t\tdepth += 1\n\t\t}\n\t\tfor (let index = entry.node.children.length - 1; index >= 0; index -= 1) {\n\t\t\tconst child = entry.node.children[index]\n\t\t\tif (child !== undefined) pending.push({ node: child, depth })\n\t\t}\n\t}\n\treturn lines.join('\\n')\n}\n\n/**\n * Describes the order sequential keyboard navigation visits one element's controls in.\n *\n * @param element - The host to walk; its own controls are described, and it is not itself one.\n * @returns One numbered line per reachable control, naming its role and its name.\n *\n * @remarks\n * A positive `tabindex` is honoured, because a browser honours it: those controls come first in\n * ascending order and everything else follows in document order. A control removed from the\n * sequence by `tabindex=\"-1\"`, by being disabled, or by not being rendered at all is absent here,\n * which is the fact a focus-order verdict is about. A control {@link readRole} answers `undefined`\n * for is named by its lowercased tag, so it is still counted rather than silently dropped.\n *\n * @example\n * ```ts\n * describeFocus(container)\n * // 1. button \"Save\"\n * // 2. link \"Cancel\"\n * ```\n */\nexport function describeFocus(element: Element): string {\n\treturn [...element.querySelectorAll(FOCUSABLE_SELECTOR)]\n\t\t.filter(\n\t\t\t(node) =>\n\t\t\t\tisRendered(node) && !node.matches(':disabled') && node.getAttribute('tabindex') !== '-1',\n\t\t)\n\t\t.sort((first, second) => {\n\t\t\tconst left = Number.parseInt(first.getAttribute('tabindex') ?? '0', 10)\n\t\t\tconst right = Number.parseInt(second.getAttribute('tabindex') ?? '0', 10)\n\t\t\tif (left > 0 && right > 0) return left - right\n\t\t\tif (left > 0) return -1\n\t\t\tif (right > 0) return 1\n\t\t\treturn 0\n\t\t})\n\t\t.map((node, index) => {\n\t\t\tconst role = readRole(node) ?? node.tagName.toLowerCase()\n\t\t\tconst name = readName(node)\n\t\t\treturn `${String(index + 1)}. ${role}${name.length > 0 ? ` \"${name}\"` : ''}`\n\t\t})\n\t\t.join('\\n')\n}\n\n/**\n * Waits for one animation frame to settle pending browser paint work.\n *\n * @returns A promise resolving after one `requestAnimationFrame`.\n *\n * @example\n * ```ts\n * await waitForFrame()\n * ```\n */\nexport function waitForFrame(): Promise<void> {\n\treturn new Promise<void>((resolve) => requestAnimationFrame(() => resolve()))\n}\n\n/**\n * Renders trusted fixture markup into a container attached to the document.\n *\n * @param markup - The fixture markup to render.\n * @returns The attached container.\n *\n * @example\n * ```ts\n * const container = render('<button type=\"button\">Save</button>')\n * container.remove()\n * ```\n */\nexport function render(markup: string): HTMLDivElement {\n\tconst container = document.createElement('div')\n\tcontainer.innerHTML = markup\n\tdocument.body.append(container)\n\treturn container\n}\n\n/**\n * Clears both browser storage surfaces.\n *\n * @remarks\n * A browser test file shares one page, so a key written by one test is read by the next one that\n * looks for it. Call this from an `afterEach` hook, which runs after a failed test as well as a\n * passing one, rather than at the end of each test that happens to write a key.\n *\n * @example\n * ```ts\n * afterEach(clearStorage)\n * ```\n */\nexport function clearStorage(): void {\n\tlocalStorage.clear()\n\tsessionStorage.clear()\n}\n\n/**\n * Parses one computed CSS color value into straight sRGB channels.\n *\n * @param value - A computed `rgb()`, `rgba()`, or `color(srgb …)` value.\n * @returns The color's channels, or `undefined` when the value names no color this reader speaks.\n *\n * @remarks\n * A computed color resolves to `rgb()` or `rgba()` for every legacy source, and a `color-mix()`\n * declaration resolves to `color(srgb r g b [/ a])` with channels on the 0–1 scale. Both forms are\n * read here and nothing else is: a keyword, a hex triple, an empty string from a detached element,\n * and a color space the cascade never hands back all return `undefined`. Absence is the answer\n * rather than a transparent color, so a caller decides what an unreadable value means instead of\n * measuring a black it never saw.\n *\n * @example\n * ```ts\n * parseColor('rgba(255, 255, 255, 0.5)') // [255, 255, 255, 0.5]\n * parseColor('rebeccapurple') // undefined\n * ```\n */\nexport function parseColor(value: string): Color | undefined {\n\tconst modern =\n\t\t/^color\\(srgb\\s+(?<red>[\\d.]+)\\s+(?<green>[\\d.]+)\\s+(?<blue>[\\d.]+)(?:\\s*\\/\\s*(?<alpha>[\\d.]+))?\\)$/u.exec(\n\t\t\tvalue,\n\t\t)\n\tconst legacy = /^rgba?\\((?<channels>[^)]*)\\)$/u.exec(value)\n\tconst parts =\n\t\tmodern?.groups === undefined\n\t\t\t? (legacy?.groups?.channels ?? '')\n\t\t\t\t\t.split(/[\\s,/]+/u)\n\t\t\t\t\t.filter((part) => part.length > 0)\n\t\t\t\t\t.map((part) => Number.parseFloat(part))\n\t\t\t: [\n\t\t\t\t\tNumber.parseFloat(modern.groups.red ?? '') * 255,\n\t\t\t\t\tNumber.parseFloat(modern.groups.green ?? '') * 255,\n\t\t\t\t\tNumber.parseFloat(modern.groups.blue ?? '') * 255,\n\t\t\t\t\tmodern.groups.alpha === undefined ? 1 : Number.parseFloat(modern.groups.alpha),\n\t\t\t\t]\n\tconst [red, green, blue, alpha = 1] = parts\n\tif (red === undefined || green === undefined || blue === undefined) return undefined\n\tif (![red, green, blue, alpha].every((channel) => Number.isFinite(channel))) return undefined\n\treturn Object.freeze([red, green, blue, alpha])\n}\n\n/**\n * Composites one color over another.\n *\n * @param front - The color painted on top.\n * @param back - The color already on the surface.\n * @returns The opaque result a reader sees, its alpha always `1`.\n *\n * @example\n * ```ts\n * blendColor([255, 255, 255, 0.5], [0, 0, 0, 1]) // [127.5, 127.5, 127.5, 1]\n * ```\n */\nexport function blendColor(front: Color, back: Color): Color {\n\tconst [red, green, blue, alpha] = front\n\tconst [under, over, beneath] = back\n\treturn Object.freeze([\n\t\tred * alpha + under * (1 - alpha),\n\t\tgreen * alpha + over * (1 - alpha),\n\t\tblue * alpha + beneath * (1 - alpha),\n\t\t1,\n\t])\n}\n\n/**\n * Measures one opaque color's WCAG relative luminance.\n *\n * @param color - The color to weigh. Its alpha is ignored, so composite before calling.\n * @returns The relative luminance, from `0` for black to `1` for white.\n *\n * @example\n * ```ts\n * measureLuminance([255, 255, 255, 1]) // 1\n * ```\n */\nexport function measureLuminance(color: Color): number {\n\tconst [red, green, blue] = color\n\tconst [first = 0, second = 0, third = 0] = [red, green, blue].map((channel) => {\n\t\tconst part = channel / 255\n\t\treturn part <= 0.040_45 ? part / 12.92 : ((part + 0.055) / 1.055) ** 2.4\n\t})\n\treturn 0.2126 * first + 0.7152 * second + 0.0722 * third\n}\n\n/**\n * Measures the WCAG 2.x contrast ratio between two opaque colors.\n *\n * @param front - The foreground color, already composited.\n * @param back - The opaque backdrop.\n * @returns The ratio, from `1` for two identical colors to `21` for black against white.\n *\n * @remarks\n * The ratio is symmetric: the brighter of the two luminances is always the numerator, so swapping\n * the arguments returns the same number.\n *\n * @example\n * ```ts\n * measureContrast([0, 0, 0, 1], [255, 255, 255, 1]) // 21\n * ```\n */\nexport function measureContrast(front: Color, back: Color): number {\n\tconst bright = Math.max(measureLuminance(front), measureLuminance(back))\n\tconst dark = Math.min(measureLuminance(front), measureLuminance(back))\n\treturn (bright + 0.05) / (dark + 0.05)\n}\n\n/**\n * Collects the painted layers standing between one element and the surface it sits on.\n *\n * @param element - The element to walk up from.\n * @returns Every layer the walk paints, the element's own first and the deepest last.\n *\n * @remarks\n * A surface token paints one ancestor while every element between it and the text paints nothing,\n * so a backdrop is found by walking up rather than by reading the element's own `background-color`,\n * which is almost always transparent. A fully transparent layer paints nothing and is left out, and\n * the walk stops at the first fully opaque layer, because nothing above that layer is visible.\n *\n * The stack is what tells a resolved backdrop from an assumed one: the walk reached an opaque\n * surface exactly when its last layer's alpha is `1`. {@link contrast} refuses on that reading,\n * which no comparison of composited colors can replace — 64 half-transparent layers composite to\n * the same channels over opposite floors, because the floor's remaining share falls below the last\n * bit a channel carries.\n *\n * @example\n * ```ts\n * readLayers(requireValue(container.querySelector('p')))\n * ```\n */\nexport function readLayers(element: Element): readonly Color[] {\n\tconst layers: Color[] = []\n\tfor (let node: Element | null = element; node !== null; node = node.parentElement) {\n\t\tconst layer = parseColor(getComputedStyle(node).backgroundColor)\n\t\tif (layer === undefined || layer[3] === 0) continue\n\t\tlayers.push(layer)\n\t\tif (layer[3] >= 1) break\n\t}\n\treturn Object.freeze(layers)\n}\n\n/**\n * Resolves the opaque color standing behind one element.\n *\n * @param element - The element whose backdrop to resolve.\n * @param floor - The opaque color the walk ends on when nothing above it paints.\n * @returns The composited color a reader sees behind the element.\n *\n * @remarks\n * The layers {@link readLayers} collects composite top-over-bottom onto the floor, so a 3% surface\n * tint reads as a tint over what shows through it rather than as a full-strength paint.\n *\n * The floor is required, because this leaf never guesses what a document sits on. Pass\n * {@link CANVAS_COLOR} for the page a browser paints behind an unstyled document, or the color of\n * the surface a fragment is really rendered into. When no layer paints, the floor is returned by\n * identity.\n *\n * The composite alone never says whether the floor is part of the answer. A caller that must know\n * reads the stack instead.\n *\n * @example\n * ```ts\n * readBackdrop(requireValue(container.querySelector('p')), CANVAS_COLOR)\n * ```\n */\nexport function readBackdrop(element: Element, floor: Color): Color {\n\treturn readLayers(element).reduceRight((back, front) => blendColor(front, back), floor)\n}\n\n/**\n * Measures the WCAG 2.x contrast ratio between an element's computed text and background colors.\n *\n * @param element - The element whose rendered text contrast to measure.\n * @param floor - The opaque color the backdrop walk ends on. Omit it to refuse a stack the floor\n * would show through instead of assuming one.\n * @returns The relative-luminance contrast ratio.\n * @throws Thrown when the element exposes no computed foreground color, and — with `floor` omitted\n * — when the walk from the element upwards reaches no opaque layer.\n *\n * @remarks\n * A transparent or translucent background resolves through the element's ancestors: every painted\n * layer from the element up to the first opaque one composites top-over-bottom onto that opaque\n * base, so a 3% surface tint reads as a tint over what shows through it rather than as a\n * full-strength paint. A translucent foreground then resolves against that effective background\n * before luminance is measured.\n *\n * With `floor` omitted, the walk from the target upwards must reach a fully opaque layer: the\n * measurement throws rather than assuming a white canvas wherever that canvas would still be part\n * of the answer. The refusal reads the alpha of the deepest layer {@link readLayers} collected, so\n * a chain that declares no background color at all, a chain painting only translucent layers, and a\n * chain deep enough for its composite to round to the canvas's own channels are refused alike,\n * because the number any of them produces is as much a report of the assumption as of the page.\n * Supply a floor wherever the caller knows what the stack sits on — a fragment mounted into a\n * painted host, or a document whose canvas is {@link CANVAS_COLOR} — and the composite is taken\n * over it rather than refused.\n *\n * The element itself must expose a computed foreground color either way. A detached element exposes\n * none, and the measurement throws rather than guessing one.\n *\n * @example\n * ```ts\n * const container = render('<p style=\"background: #000; color: #fff\">Ready</p>')\n * contrast(requireValue(container.firstElementChild)) // 21\n * contrast(requireValue(container.firstElementChild), CANVAS_COLOR) // 21, and never refuses\n * ```\n */\nexport function contrast(element: Element, floor?: Color): number {\n\tconst foreground = parseColor(getComputedStyle(element).color)\n\tif (foreground === undefined) throw new Error('Computed foreground color is unavailable')\n\tconst layers = readLayers(element)\n\tconst deepest = layers.at(-1)\n\t// The walk reached a real surface exactly when its deepest layer is fully opaque. An empty stack\n\t// paints nothing, and a stack ending translucent leaves the floor showing through whatever the\n\t// composite reports: 64 half-transparent layers round to identical channels over opposite floors,\n\t// so comparing two composited readings admits the stack this refusal exists for.\n\tif (floor === undefined && (deepest === undefined || deepest[3] < 1)) {\n\t\tthrow new Error('Computed background color is unavailable')\n\t}\n\tconst backdrop = layers.reduceRight(\n\t\t(back, front) => blendColor(front, back),\n\t\tfloor ?? CANVAS_COLOR,\n\t)\n\treturn measureContrast(blendColor(foreground, backdrop), backdrop)\n}\n\n/**\n * Measures the contrast the focus chrome painted on one control reaches against its own backdrop.\n *\n * @param control - The control that holds the focus.\n * @param worn - The element the control's focus chrome is painted onto. Default: `control`.\n * @returns The strongest ratio the painted focus chrome reaches, or `undefined` when the control is\n * not showing `:focus-visible` or the cascade paints no chrome of its own.\n *\n * @remarks\n * This reads and never acts. Focus arrives through the published verbs — `traverseAccessible`,\n * `pressKeys`, a real click — and this measures what the browser painted once it landed. A control\n * that is not matching `:focus-visible` when the call is made reports nothing, because no\n * measurement taken then would be about focus.\n *\n * Some controls are two elements: one that takes the focus and one a reader can see. A hidden radio\n * beside the label that carries every pixel of its chrome is the case `worn` exists for, so a\n * measurement is not taken on a rectangle nobody is looking at. The focus state is still read off\n * `control`, because that is what holds it.\n *\n * The backdrop is the surface behind the element the chrome is worn on, resolved from that element's\n * parent through {@link readBackdrop} onto {@link CANVAS_COLOR}. A control whose ancestry paints\n * nothing is therefore measured against the browser's own canvas, which is what a reader looking at\n * an unstyled document sees.\n *\n * Only chrome the cascade paints is measured — an `outline` with a real style and width, and the\n * first color in a `box-shadow`. A control left the browser's own `outline-style: auto` ring reports\n * `undefined`, because that ring's two tones are guaranteed against any backdrop and its computed\n * color names neither. A focus style that only changes the control's own fill reports `undefined`\n * too: the resting fill is gone by the time focus is on the control, and this never moves focus to\n * go and read it.\n *\n * @example\n * ```ts\n * await traverseAccessible('Evaluate')\n * readRing(resolveRendered('Evaluate')) // the ratio the painted ring reaches\n * ```\n */\nexport function readRing(control: Element, worn?: Element): number | undefined {\n\tif (!control.matches(':focus-visible')) return undefined\n\tconst target = worn ?? control\n\tconst declared = getComputedStyle(target)\n\tconst backdrop = readBackdrop(target.parentElement ?? target, CANVAS_COLOR)\n\tconst outline =\n\t\tdeclared.outlineStyle === 'none' ||\n\t\tdeclared.outlineStyle === 'auto' ||\n\t\tNumber.parseFloat(declared.outlineWidth) === 0\n\t\t\t? undefined\n\t\t\t: parseColor(declared.outlineColor)\n\tconst shadow = parseColor(/(?:rgba?|color)\\([^)]*\\)/u.exec(declared.boxShadow)?.[0] ?? '')\n\tconst ratios: number[] = []\n\tfor (const painted of [outline, shadow]) {\n\t\tif (painted === undefined) continue\n\t\tratios.push(measureContrast(blendColor(painted, backdrop), backdrop))\n\t}\n\treturn ratios.length === 0 ? undefined : Math.max(...ratios)\n}\n\n/**\n * Collects every class token the stylesheets loaded into this document actually define.\n *\n * @returns The set of class names reachable in the shipped cascade.\n *\n * @remarks\n * The set is what an authored-class conformance check measures against, so a class no loaded\n * stylesheet defines — an invented utility, a misspelled framework name — is absent from it.\n *\n * @example\n * ```ts\n * readCascade().has('card')\n * ```\n */\nexport function readCascade(): ReadonlySet<string> {\n\tconst known = new Set<string>()\n\tconst rules: CSSRule[] = []\n\tfor (const sheet of document.styleSheets) rules.push(...sheet.cssRules)\n\twhile (rules.length > 0) {\n\t\tconst rule = rules.pop()\n\t\tif (rule instanceof CSSGroupingRule) rules.push(...rule.cssRules)\n\t\tif (!(rule instanceof CSSStyleRule)) continue\n\t\tfor (const match of rule.selectorText.matchAll(/\\.([a-zA-Z][\\w-]*)/g)) {\n\t\t\tknown.add(String(match[1]))\n\t\t}\n\t}\n\treturn known\n}\n\n/**\n * Reads the normalized visible text of every element a selector matches, in document order.\n *\n * @param root - The subtree to search.\n * @param selector - The CSS selector naming the rows.\n * @returns One line per matched element, its text runs collapsed and single-space joined.\n *\n * @remarks\n * The line is built from the row's text nodes rather than from `textContent`, because adjacent\n * inline elements carry no whitespace between them in compiled template output and would otherwise\n * read as one run-together word.\n *\n * @example\n * ```ts\n * readRows(container, 'li')\n * ```\n */\nexport function readRows(root: ParentNode, selector: string): readonly string[] {\n\tconst rows: string[] = []\n\tfor (const row of root.querySelectorAll(selector)) {\n\t\tconst parts: string[] = []\n\t\tconst walker = document.createTreeWalker(row, NodeFilter.SHOW_TEXT)\n\t\twhile (walker.nextNode() !== null) {\n\t\t\tconst text = (walker.currentNode.textContent ?? '').replaceAll(/\\s+/g, ' ').trim()\n\t\t\tif (text !== '') parts.push(text)\n\t\t}\n\t\trows.push(parts.join(' '))\n\t}\n\treturn rows\n}\n\n/**\n * Collects every element carrying a component class rendered outside the container it belongs to.\n *\n * @param root - The subtree to sweep.\n * @param child - The component class whose anatomy requires a container, such as `list-group-item`.\n * @param parent - The container class that child class must render inside, such as `list-group`.\n * @returns The markup of every element carrying `child` with no `parent` above it, in document\n * order; an empty list when every one of them is nested correctly.\n *\n * @remarks\n * A component keeps its padding, borders, and radii on the container, so a child class rendered\n * outside one is an unstyled box wearing a component's name, and the interface has to hand-roll the\n * chrome back. The search for the container starts at the element's parent, so an element can never\n * answer the invariant by carrying both classes itself.\n *\n * The class names are arguments, so the check belongs to no framework: name the pair your own\n * cascade defines.\n *\n * @example\n * ```ts\n * extractOrphans(container, 'list-group-item', 'list-group') // []\n * ```\n */\nexport function extractOrphans(root: ParentNode, child: string, parent: string): readonly string[] {\n\treturn [...root.querySelectorAll(`.${child}`)]\n\t\t.filter((node) => (node.parentElement?.closest(`.${parent}`) ?? null) === null)\n\t\t.map((node) => node.outerHTML)\n}\n\n/**\n * Reads one resolved CSS property from a real browser element.\n *\n * @param element - The element whose resolved style to inspect.\n * @param property - The CSS property name.\n * @returns The browser's resolved property value.\n *\n * @example\n * ```ts\n * style(button, 'padding-left')\n * ```\n */\nexport function style(element: Element, property: string): string {\n\treturn getComputedStyle(element).getPropertyValue(property)\n}\n\n/**\n * Sets the tester's viewport and renders the runner's pane at the size that viewport claims.\n *\n * @param width - The viewport width in CSS pixels.\n * @param height - The viewport height in CSS pixels.\n * @returns A promise resolving after the resized pane has been painted.\n * @throws Thrown when the tester sits inside no pane a capture can size, and when the staged pane\n * does not render at the viewport it was given.\n *\n * @remarks\n * This depends on the runner's own tester layout, and that dependency is contract rather than an\n * accident: `vitest@4.1.11` lays its tester out inside a smaller page, fits it by scaling the pane\n * the tester sits in, and clips whatever overflows that pane. Layout inside the tester is\n * unaffected — the tester reports the viewport it was given and every breakpoint answers to it —\n * but a screenshot is taken off the page the runner painted, so a frame shot through that scale is\n * a thumbnail of the surface and a frame shot after only unscaling it is a sliver. The tester is\n * therefore unscaled and lifted to the window's own origin for the shot. The `iframe[data-vitest]`\n * selector and the `--tester-transform`, `--tester-margin-left`, `--viewport-width`, and\n * `--viewport-height` custom properties are the runner's, so a Vitest release that renames any of\n * them reddens the size check below rather than writing a wrong frame.\n *\n * Hand the pane straight back with {@link releasePane}. A tester pinned at a viewport taller than\n * the window puts its lower half beyond what a pointer can reach, so an ordinary press then fails\n * as a control outside the viewport, in a test that took no picture at all.\n *\n * The rule is declared rather than written inline, because the runner writes its own scale onto the\n * pane as inline custom properties and rewrites them whenever the tester resizes. A declared rule\n * marked important outranks an inline value and survives every rewrite. It finds the pane by the\n * tester it contains as well as by {@link CAPTURE_PANE}, because a re-render between the staging and\n * the shot replaces the node and takes any attribute of ours with it.\n *\n * The wait is two frames rather than a delay: the first carries the resize into layout and the\n * second is the paint a screenshot reads.\n *\n * @example\n * ```ts\n * await stagePane(390, 844)\n * ```\n */\nexport async function stagePane(width: number, height: number): Promise<void> {\n\tawait page.viewport(width, height)\n\tconst frame = window.frameElement\n\tconst pane = frame?.parentElement\n\tconst owner = pane?.ownerDocument\n\tif (frame === null || pane === null || pane === undefined || owner === undefined) {\n\t\tthrow new Error('Tester pane is unavailable for a capture')\n\t}\n\tpane.setAttribute(CAPTURE_PANE, '')\n\tif (owner.querySelector(`style[${CAPTURE_PANE}]`) === null) {\n\t\tconst rule = owner.createElement('style')\n\t\trule.setAttribute(CAPTURE_PANE, '')\n\t\trule.textContent = [\n\t\t\t`[${CAPTURE_PANE}],:has(>iframe[data-vitest])`,\n\t\t\t'{--tester-transform:none !important;--tester-margin-left:0px !important}',\n\t\t\t'iframe[data-vitest]',\n\t\t\t'{position:fixed !important;left:0 !important;top:0 !important;right:auto !important;',\n\t\t\t'bottom:auto !important;width:var(--viewport-width) !important;',\n\t\t\t'height:var(--viewport-height) !important;z-index:2147483647 !important}',\n\t\t].join('')\n\t\towner.head.append(rule)\n\t}\n\tawait waitForFrame()\n\tawait waitForFrame()\n\tconst box = frame.getBoundingClientRect()\n\tif (Math.round(box.width) !== width || Math.round(box.height) !== height) {\n\t\tthrow new Error(\n\t\t\t`Tester pane rendered ${String(Math.round(box.width))}x${String(Math.round(box.height))} for a ${String(width)}x${String(height)} viewport`,\n\t\t)\n\t}\n}\n\n/**\n * Hands the tester pane back to the runner's own layout.\n *\n * @remarks\n * A staged pane is the runner's fitting scale suppressed, so a pane left staged outlives the capture\n * that needed it and every later act in the file happens on a surface the runner is no longer\n * fitting to its window. What that costs is not a wrong picture: it is a control whose page\n * coordinates fall outside the pane, which the runner's own layout then intercepts, so an ordinary\n * press fails with the voice of a control that is covered. Calling this on an unstaged pane does\n * nothing.\n *\n * @example\n * ```ts\n * releasePane()\n * ```\n */\nexport function releasePane(): void {\n\tconst pane = window.frameElement?.parentElement\n\tpane?.removeAttribute(CAPTURE_PANE)\n\tpane?.ownerDocument.querySelector(`style[${CAPTURE_PANE}]`)?.remove()\n}\n\n/**\n * Shoots one frame at one viewport size and proves the file on disk holds this run's bytes.\n *\n * @param options - The path to write, the viewport to shoot at, and the element to shoot.\n * @returns The absolute path of the written frame, after it has been read back and matched.\n * @throws Thrown when the pane cannot be staged, when the provider wrote the frame somewhere else,\n * and when the bytes on disk are not the ones this shot produced.\n *\n * @remarks\n * The path a screenshot call returns is the path it meant to write, so it is not evidence a file\n * exists. The file is read back through the runner's built-in `readFile` command and compared with\n * the shot itself, which is what separates a frame this run wrote from one an earlier run left\n * behind. The provider resolves `options.path` against the calling test file and returns an absolute\n * path, so the two are compared by the segments that survive resolving `.` and `..` lexically — the\n * refusal is what a provider resolving that path against a different base would trip.\n *\n * Omit `options.element` to shoot the whole page. The pane is staged for the frame and released\n * before this returns, on the failing path as well as the passing one.\n *\n * @example\n * ```ts\n * await captureFrame({ path: '../../tmp/capture/start.png', width: 390, height: 844 })\n * ```\n */\nexport async function captureFrame(options: FrameOptions): Promise<string> {\n\ttry {\n\t\tawait stagePane(options.width, options.height)\n\t\tconst shot =\n\t\t\toptions.element === undefined\n\t\t\t\t? await page.screenshot({ path: options.path, base64: true })\n\t\t\t\t: await page.screenshot({ element: options.element, path: options.path, base64: true })\n\t\tconst segments: string[] = []\n\t\tfor (const segment of options.path.replaceAll('\\\\', '/').split('/')) {\n\t\t\tif (segment === '' || segment === '.') continue\n\t\t\tif (segment === '..') segments.pop()\n\t\t\telse segments.push(segment)\n\t\t}\n\t\tif (!shot.path.replaceAll('\\\\', '/').endsWith(segments.join('/'))) {\n\t\t\tthrow new Error(\n\t\t\t\t`Capture frame was written to ${shot.path} where ${options.path} was asked for`,\n\t\t\t)\n\t\t}\n\t\tif ((await commands.readFile(shot.path, 'base64')) !== shot.base64) {\n\t\t\tthrow new Error(`Capture frame at ${options.path} is not the one this run shot`)\n\t\t}\n\t\treturn shot.path\n\t} finally {\n\t\treleasePane()\n\t}\n}\n\n/**\n * Expands a capture registry across every variant into the filenames a complete portfolio holds.\n *\n * @param states - The registered state names.\n * @param variants - The variants the portfolio is rendered in.\n * @returns One `<state>--<variant>.png` name per pair, each state's variants together, in registry\n * order.\n *\n * @remarks\n * The expansion is the portfolio's own definition of complete, so a duplicate in it is a registry\n * defect a proof reads directly rather than a collision discovered on disk.\n *\n * @example\n * ```ts\n * expandCaptures(['start'], [{ name: 'dark-390', width: 390, height: 844 }])\n * // ['start--dark-390.png']\n * ```\n */\nexport function expandCaptures(\n\tstates: readonly string[],\n\tvariants: readonly CaptureVariant[],\n): readonly string[] {\n\tconst files: string[] = []\n\tfor (const state of states) {\n\t\tfor (const variant of variants) files.push(`${state}--${variant.name}.png`)\n\t}\n\treturn files\n}\n","import type {\n\tJournalInterface,\n\tJournalStep,\n\tPortfolioInterface,\n\tPortfolioOptions,\n} from './types.js'\nimport { captureFrame, expandCaptures } from './helpers.js'\n\n/**\n * Creates the capture portfolio one run places its screenshots through.\n *\n * @param options - The state registry, the variant matrix, the variant this run renders, the\n * directory it writes into, and whether it writes at all.\n * @returns The portfolio: its registry expansion, what it has placed, and `place`.\n * @throws When no registered variant carries the name `variant` names.\n *\n * @remarks\n * A disabled portfolio is the ordinary run. `place` then resizes nothing, writes nothing, and\n * records nothing, so a journey calls it unconditionally and a suite with the flag unset pays for\n * none of it. The portfolio refuses an unregistered variant at creation. An enabled run refuses an\n * unregistered state name and a second placement of one state.\n *\n * An enabled `place` writes through `captureFrame`, so a placed state carries that helper's staged\n * pane and its byte readback: a path is recorded only after the file on disk has been proved to hold\n * this run's own frame.\n *\n * @example\n * ```ts\n * const portfolio = createPortfolio({\n * \tstates: ['start-empty'],\n * \tvariants: [{ name: 'dark-390', width: 390, height: 844 }],\n * \tvariant: 'dark-390',\n * \tdirectory: '../../tmp/capture/states',\n * })\n * await portfolio.place('start-empty')\n * ```\n */\nexport function createPortfolio(options: PortfolioOptions): PortfolioInterface {\n\tconst selected = options.variants.find((candidate) => candidate.name === options.variant)\n\tif (selected === undefined) {\n\t\tthrow new Error(`Capture variant \"${options.variant}\" is not registered`)\n\t}\n\tconst registry = [...options.states]\n\tconst files = expandCaptures(registry, options.variants)\n\tconst enabled = options.enabled ?? false\n\tconst placed: string[] = []\n\tconst paths: string[] = []\n\treturn {\n\t\tvariant: options.variant,\n\t\tfiles,\n\t\tget states() {\n\t\t\treturn [...placed]\n\t\t},\n\t\tget paths() {\n\t\t\treturn [...paths]\n\t\t},\n\t\tasync place(state, element) {\n\t\t\tif (!enabled) return undefined\n\t\t\tif (!registry.includes(state)) {\n\t\t\t\tthrow new Error(`Capture state \"${state}\" is not registered`)\n\t\t\t}\n\t\t\tif (placed.includes(state)) {\n\t\t\t\tthrow new Error(`Capture state \"${state}\" is already placed`)\n\t\t\t}\n\t\t\tconst file = `${state}--${options.variant}.png`\n\t\t\tselected.apply?.()\n\t\t\tconst written = await captureFrame({\n\t\t\t\tpath: `${options.directory}/${file}`,\n\t\t\t\twidth: selected.width,\n\t\t\t\theight: selected.height,\n\t\t\t\telement,\n\t\t\t})\n\t\t\tplaced.push(state)\n\t\t\tpaths.push(written)\n\t\t\treturn written\n\t\t},\n\t}\n}\n\n/**\n * Creates the journal one scenario records its steps and the page's own output into.\n *\n * @returns A journal that records nothing until it is started.\n *\n * @remarks\n * The console is recorded rather than replaced: every intercepted call is forwarded to the channel\n * that was there when the journal started, so a run under a journal prints exactly what it printed\n * without one. `stop` puts those same function references back by identity.\n *\n * Uncaught errors and unhandled rejections are recorded too, through listeners the journal drops\n * when it stops. `steps` and `output` hand out snapshots, so a list read mid-scenario stays what it\n * was. Each journal owns its own recording, so a file that needs one per scenario creates one per\n * scenario.\n *\n * @example\n * ```ts\n * const journal = createJournal()\n * journal.start()\n * journal.record('click', 'Evaluate', 'alerts=0')\n * journal.stop()\n * journal.steps // [{ action: 'click', trigger: 'Evaluate', result: 'alerts=0' }]\n * ```\n */\nexport function createJournal(): JournalInterface {\n\tconst steps: JournalStep[] = []\n\tconst output: string[] = []\n\t// The channels the page was writing to when the journal started. Their presence is what \"started\"\n\t// means, so no second flag can disagree with it. The listeners are dropped through one signal,\n\t// which is why no handler reference has to be kept to take them off again.\n\tlet intercepted: Pick<Console, 'debug' | 'error' | 'info' | 'log' | 'warn'> | undefined\n\tlet listeners: AbortController | undefined\n\treturn {\n\t\tget steps() {\n\t\t\treturn [...steps]\n\t\t},\n\t\tget output() {\n\t\t\treturn [...output]\n\t\t},\n\t\tstart() {\n\t\t\tsteps.length = 0\n\t\t\toutput.length = 0\n\t\t\tif (intercepted !== undefined) return\n\t\t\tconst forwarded = {\n\t\t\t\tdebug: console.debug,\n\t\t\t\terror: console.error,\n\t\t\t\tinfo: console.info,\n\t\t\t\tlog: console.log,\n\t\t\t\twarn: console.warn,\n\t\t\t}\n\t\t\tintercepted = forwarded\n\t\t\tfor (const channel of ['debug', 'error', 'info', 'log', 'warn'] as const) {\n\t\t\t\tconsole[channel] = (...data: unknown[]) => {\n\t\t\t\t\toutput.push(`${channel}: ${data.map((value) => String(value)).join(' ')}`)\n\t\t\t\t\tforwarded[channel](...data)\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst dropped = new AbortController()\n\t\t\tlisteners = dropped\n\t\t\twindow.addEventListener(\n\t\t\t\t'error',\n\t\t\t\t(event) => {\n\t\t\t\t\toutput.push(`error: ${event.message}`)\n\t\t\t\t},\n\t\t\t\t{ signal: dropped.signal },\n\t\t\t)\n\t\t\twindow.addEventListener(\n\t\t\t\t'unhandledrejection',\n\t\t\t\t(event) => {\n\t\t\t\t\toutput.push(`rejection: ${String(event.reason)}`)\n\t\t\t\t},\n\t\t\t\t{ signal: dropped.signal },\n\t\t\t)\n\t\t},\n\t\tstop() {\n\t\t\tif (intercepted === undefined) return\n\t\t\tObject.assign(console, intercepted)\n\t\t\tintercepted = undefined\n\t\t\tlisteners?.abort()\n\t\t\tlisteners = undefined\n\t\t},\n\t\trecord(action, trigger, result) {\n\t\t\tif (intercepted === undefined) return\n\t\t\tsteps.push(Object.freeze({ action, trigger, result }))\n\t\t},\n\t}\n}\n"],"mappings":"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"}
@@ -10,6 +10,158 @@ function waitForDelay(ms = 0) {
10
10
  return new Promise((resolve) => setTimeout(resolve, ms));
11
11
  }
12
12
  /**
13
+ * Waits until a condition holds within an elapsed-time budget.
14
+ *
15
+ * @param description - The condition described in a timeout error.
16
+ * @param condition - The synchronous or asynchronous condition to read.
17
+ * @param options - The time bounds and abort signal.
18
+ * @returns A promise that resolves when the condition first returns `true`.
19
+ * @throws The condition's thrown value, the abort reason, or an `Error` when a bound is invalid or
20
+ * the condition does not hold within the budget.
21
+ * @remarks The first read is immediate. Default budget: `1000` milliseconds. Default interval: `10`
22
+ * milliseconds.
23
+ */
24
+ async function waitForCondition(description, condition, options) {
25
+ const budget = options?.budget ?? 1e3;
26
+ const interval = options?.interval ?? 10;
27
+ if (!Number.isFinite(budget) || budget < 0) throw new Error("Wait budget must be finite and non-negative");
28
+ if (!Number.isFinite(interval) || interval < 0) throw new Error("Wait interval must be finite and non-negative");
29
+ const start = performance.now();
30
+ while (true) {
31
+ options?.signal?.throwIfAborted();
32
+ const held = await condition();
33
+ options?.signal?.throwIfAborted();
34
+ if (held) return;
35
+ const elapsed = performance.now() - start;
36
+ if (elapsed >= budget) throw new Error(`Condition "${description}" did not hold within ${budget}ms (waited ${elapsed}ms)`);
37
+ await waitForDelay(interval);
38
+ }
39
+ }
40
+ /**
41
+ * Repeats a producer until one produced value satisfies a predicate.
42
+ *
43
+ * @typeParam T - The produced value type.
44
+ * @param description - The operation described in an exhaustion error.
45
+ * @param produce - The synchronous or asynchronous operation to repeat.
46
+ * @param satisfied - The predicate that accepts a produced value.
47
+ * @param options - The time, attempt, and abort bounds.
48
+ * @returns The first produced value the predicate accepts.
49
+ * @throws The predicate's thrown value, the abort reason, or an `Error` when a bound is invalid or
50
+ * the retry exhausts its budget or attempts.
51
+ * @remarks A producer throw counts as an unsatisfied attempt. The last producer error becomes the
52
+ * exhaustion error's `cause`. Default budget: `1000` milliseconds. Default interval: `10`
53
+ * milliseconds.
54
+ */
55
+ async function retryUntil(description, produce, satisfied, options) {
56
+ const budget = options?.budget ?? 1e3;
57
+ const interval = options?.interval ?? 10;
58
+ const attempts = options?.attempts;
59
+ if (!Number.isFinite(budget) || budget < 0) throw new Error("Retry budget must be finite and non-negative");
60
+ if (!Number.isFinite(interval) || interval < 0) throw new Error("Retry interval must be finite and non-negative");
61
+ if (attempts !== void 0 && (!Number.isInteger(attempts) || attempts < 1)) throw new Error("Retry attempts must be a positive integer");
62
+ const start = performance.now();
63
+ let count = 0;
64
+ let cause;
65
+ while (true) {
66
+ options?.signal?.throwIfAborted();
67
+ if (count > 0) {
68
+ const elapsed = performance.now() - start;
69
+ if (elapsed >= budget) throw new Error(`Retry "${description}" did not succeed within ${budget}ms (waited ${elapsed}ms)`, { cause });
70
+ }
71
+ let produced;
72
+ try {
73
+ produced = {
74
+ success: true,
75
+ value: await produce()
76
+ };
77
+ } catch (error) {
78
+ produced = {
79
+ success: false,
80
+ error
81
+ };
82
+ }
83
+ count += 1;
84
+ options?.signal?.throwIfAborted();
85
+ if (produced.success) {
86
+ if (satisfied(produced.value)) return produced.value;
87
+ } else cause = produced.error;
88
+ const elapsed = performance.now() - start;
89
+ if (elapsed >= budget) throw new Error(`Retry "${description}" did not succeed within ${budget}ms (waited ${elapsed}ms)`, { cause });
90
+ if (attempts !== void 0 && count >= attempts) throw new Error(`Retry "${description}" did not succeed within ${attempts} attempts`, { cause });
91
+ await waitForDelay(Math.min(interval, budget - elapsed));
92
+ }
93
+ }
94
+ /**
95
+ * Waits for the first delivery from an event subscription.
96
+ *
97
+ * @typeParam TArgs - The delivered argument tuple.
98
+ * @param subscribe - The function that installs the event listener and may return its cleanup.
99
+ * @param description - The event described in a timeout error.
100
+ * @param options - The time bounds and abort signal.
101
+ * @returns The first delivered argument tuple.
102
+ * @throws The subscription's thrown value, the abort reason, or an `Error` when a bound is invalid
103
+ * or the event is not delivered within the budget.
104
+ * @remarks Default budget: `1000` milliseconds. The interval is validated for consistency with the
105
+ * wait family but is not used because this helper parks on the event.
106
+ */
107
+ async function waitForEvent(subscribe, description, options) {
108
+ const budget = options?.budget ?? 1e3;
109
+ const interval = options?.interval ?? 10;
110
+ if (!Number.isFinite(budget) || budget < 0) throw new Error("Event budget must be finite and non-negative");
111
+ if (!Number.isFinite(interval) || interval < 0) throw new Error("Event interval must be finite and non-negative");
112
+ const signal = options?.signal;
113
+ signal?.throwIfAborted();
114
+ const delivery = Promise.withResolvers();
115
+ const controller = new AbortController();
116
+ let timeout;
117
+ const pending = [delivery.promise, new Promise((_resolve, reject) => {
118
+ timeout = setTimeout(() => {
119
+ reject(/* @__PURE__ */ new Error(`Event "${description}" was not delivered within ${budget}ms`));
120
+ }, budget);
121
+ })];
122
+ if (signal !== void 0) pending.push(new Promise((_resolve, reject) => {
123
+ AbortSignal.any([signal, controller.signal]).addEventListener("abort", () => {
124
+ if (signal.aborted) reject(signal.reason);
125
+ }, { once: true });
126
+ }));
127
+ const result = Promise.race(pending);
128
+ let cleanup = void 0;
129
+ try {
130
+ try {
131
+ cleanup = subscribe((...args) => delivery.resolve(args));
132
+ } catch (error) {
133
+ delivery.reject(error);
134
+ }
135
+ return await result;
136
+ } finally {
137
+ controller.abort();
138
+ if (timeout !== void 0) clearTimeout(timeout);
139
+ cleanup?.();
140
+ }
141
+ }
142
+ /**
143
+ * Decodes newline-delimited JSON values.
144
+ *
145
+ * @param text - The JSON Lines text to decode.
146
+ * @returns The decoded values in physical-line order.
147
+ * @throws An `Error` naming the malformed physical line, with the native `SyntaxError` as its
148
+ * `cause`.
149
+ */
150
+ function decodeJSONLines(text) {
151
+ const values = [];
152
+ for (const [index, physical] of text.split("\n").entries()) {
153
+ const line = physical.endsWith("\r") ? physical.slice(0, -1) : physical;
154
+ if (line.length === 0) continue;
155
+ try {
156
+ const value = JSON.parse(line);
157
+ values.push(value);
158
+ } catch (cause) {
159
+ throw new Error(`Invalid JSON on line ${index + 1}`, { cause });
160
+ }
161
+ }
162
+ return values;
163
+ }
164
+ /**
13
165
  * Captures the value thrown by a thunk.
14
166
  *
15
167
  * @param thunk - The work whose thrown value to capture.
@@ -213,9 +365,13 @@ exports.collectStream = collectStream;
213
365
  exports.createHostileValues = createHostileValues;
214
366
  exports.createRecorder = createRecorder;
215
367
  exports.createTeardown = createTeardown;
368
+ exports.decodeJSONLines = decodeJSONLines;
216
369
  exports.requireValue = requireValue;
217
370
  exports.resolveRoot = resolveRoot;
371
+ exports.retryUntil = retryUntil;
218
372
  exports.roundTripJSON = roundTripJSON;
373
+ exports.waitForCondition = waitForCondition;
219
374
  exports.waitForDelay = waitForDelay;
375
+ exports.waitForEvent = waitForEvent;
220
376
 
221
377
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/core/helpers.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { JSONSafe } from './types.js'\n\n/**\n * Waits for a host timer to elapse.\n *\n * @param ms - The delay in milliseconds.\n * @returns A promise that resolves after the timer fires.\n */\nexport function waitForDelay(ms = 0): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Captures the value thrown by a thunk.\n *\n * @param thunk - The work whose thrown value to capture.\n * @returns The thrown value, or `undefined` when the thunk completes.\n */\nexport function captureError(thunk: () => unknown): unknown {\n\ttry {\n\t\tthunk()\n\t} catch (error) {\n\t\treturn error\n\t}\n\treturn undefined\n}\n\n/**\n * Requires a value to be present.\n *\n * @typeParam T - The required value type.\n * @param value - The value to check.\n * @param message - The error message used when the value is absent.\n * @returns The present value.\n */\nexport function requireValue<T>(value: T | null | undefined, message = 'Value is required'): T {\n\tif (value === null || value === undefined) throw new Error(message)\n\treturn value\n}\n\n/**\n * Collects every value from an async iterable.\n *\n * @typeParam T - The yielded value type.\n * @param source - The async iterable to drain.\n * @returns The yielded values in iteration order.\n */\nexport async function collect<T>(source: AsyncIterable<T>): Promise<readonly T[]> {\n\tconst values: T[] = []\n\tfor await (const value of source) values.push(value)\n\treturn values\n}\n\n/**\n * Collects every value from a readable stream.\n *\n * @typeParam T - The streamed value type.\n * @param stream - The readable stream to drain.\n * @returns The streamed values in read order.\n */\nexport async function collectStream<T>(stream: ReadableStream<T>): Promise<readonly T[]> {\n\tconst reader = stream.getReader()\n\tconst values: T[] = []\n\ttry {\n\t\twhile (true) {\n\t\t\tconst result = await reader.read()\n\t\t\tif (result.done) return values\n\t\t\tvalues.push(result.value)\n\t\t}\n\t} finally {\n\t\treader.releaseLock()\n\t}\n}\n\n/**\n * Copies a JSON value through serialization and parsing.\n *\n * @typeParam T - The copied value's type, which the copy keeps.\n * @param value - The value to copy, bounded by its own `JSONSafe` projection.\n * @returns The parsed JSON copy.\n * @remarks Non-finite numbers throw because JSON would replace them with `null`. Negative zero is\n * normalized to zero by JSON serialization. The bound intersects `JSONSafe<T>` rather than\n * constraining `T` to `JSONValue`, so an interface-typed value round-trips.\n */\nexport function roundTripJSON<T>(value: T & JSONSafe<T>): T {\n\tconst serialized = JSON.stringify(value, (_key, current) => {\n\t\tif (current === undefined || typeof current === 'function' || typeof current === 'symbol') {\n\t\t\tthrow new Error('JSON values must not contain undefined, functions, or symbols')\n\t\t}\n\t\tif (typeof current === 'number' && !Number.isFinite(current)) {\n\t\t\tthrow new Error('JSON values must contain finite numbers')\n\t\t}\n\t\treturn current\n\t})\n\tconst parsed: T = JSON.parse(serialized)\n\tconst pending: unknown[] = [parsed]\n\twhile (pending.length > 0) {\n\t\tconst current = pending.pop()\n\t\tif (typeof current === 'number' && !Number.isFinite(current)) {\n\t\t\tthrow new Error('JSON values must contain finite numbers')\n\t\t}\n\t\tif (Array.isArray(current)) {\n\t\t\tfor (const child of current) pending.push(child)\n\t\t} else if (typeof current === 'object' && current !== null) {\n\t\t\tfor (const child of Object.values(current)) pending.push(child)\n\t\t}\n\t}\n\treturn parsed\n}\n\n/**\n * Resolves the parent directory of a calling module, which is the workspace root when called from\n * the conventional `tests/setup.ts` location.\n *\n * @param meta - The calling module metadata.\n * @returns The root URL one directory above the calling file.\n */\nexport function resolveRoot(meta: ImportMeta): URL {\n\treturn new URL('../', meta.url)\n}\n","import type { RecorderInterface, TeardownHandler, TeardownInterface } from './types.js'\n\n/**\n * Creates values that make common object readers throw or violate their assumptions.\n *\n * @returns A frozen array whose six values are fresh on every call.\n * @remarks Every member makes a naive reader throw. A total guard survives every member without\n * throwing. Whether it accepts or refuses one is that guard's own contract. Membership may grow in\n * a release, so test the whole returned set in a loop and include the index in each failure.\n * @example\n * ```ts\n * import { expect } from 'vitest'\n * import { createHostileValues } from '@orkestrel/test'\n *\n * function isWireRecord(value: unknown): value is Readonly<Record<string, string>> {\n * \tif (typeof value !== 'object' || value === null) return false\n * \ttry {\n * \t\tif (Object.getPrototypeOf(value) !== Object.prototype) return false\n * \t\tReflect.get(value, 'value')\n * \t\tif (Reflect.ownKeys(value).length === 0) return false\n * \t\treturn Object.values(value).every((member) => typeof member === 'string')\n * \t} catch {\n * \t\treturn false\n * \t}\n * }\n *\n * for (const [index, value] of createHostileValues().entries()) {\n * \tlet accepted: boolean | undefined\n * \texpect(() => {\n * \t\taccepted = isWireRecord(value)\n * \t}, `hostile value ${index}`).not.toThrow()\n * \texpect(accepted, `hostile value ${index}`).toBe(false)\n * }\n * ```\n */\nexport function createHostileValues(): readonly unknown[] {\n\tconst cyclic: Record<string, unknown> = {}\n\tcyclic.self = cyclic\n\tconst revoked = Proxy.revocable({}, {})\n\trevoked.revoke()\n\n\treturn Object.freeze([\n\t\tcyclic,\n\t\trevoked.proxy,\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\tget() {\n\t\t\t\t\tthrow new Error('Hostile property read')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\townKeys() {\n\t\t\t\t\tthrow new Error('Hostile key enumeration')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\tgetPrototypeOf() {\n\t\t\t\t\tthrow new Error('Hostile prototype read')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tObject.create(null),\n\t])\n}\n\n/**\n * Creates a recorder for callback arguments.\n *\n * @typeParam TArgs - The argument tuple to record.\n * @returns A recorder whose handler appends calls in order.\n */\nexport function createRecorder<TArgs extends readonly unknown[]>(): RecorderInterface<TArgs> {\n\tconst calls: TArgs[] = []\n\treturn {\n\t\tcalls,\n\t\tget count() {\n\t\t\treturn calls.length\n\t\t},\n\t\thandler(...args) {\n\t\t\tcalls.push(args)\n\t\t},\n\t\tclear() {\n\t\t\tcalls.length = 0\n\t\t},\n\t}\n}\n\n/**\n * Creates a teardown list that runs registered handlers newest-first.\n *\n * @returns A teardown list that awaits every handler and collects failures.\n */\nexport function createTeardown(): TeardownInterface {\n\tlet handlers: TeardownHandler[] = []\n\treturn {\n\t\tget count() {\n\t\t\treturn handlers.length\n\t\t},\n\t\tadd(handler) {\n\t\t\thandlers.push(handler)\n\t\t},\n\t\tasync destroy() {\n\t\t\tconst snapshot = handlers\n\t\t\thandlers = []\n\t\t\tconst failures: unknown[] = []\n\t\t\tfor (const handler of snapshot.reverse()) {\n\t\t\t\ttry {\n\t\t\t\t\tawait handler()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailures.push(error)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (failures.length === 1) throw failures[0]\n\t\t\tif (failures.length > 1) throw new AggregateError(failures)\n\t\t},\n\t}\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,aAAa,KAAK,GAAkB;CACnD,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACxD;;;;;;;AAQA,SAAgB,aAAa,OAA+B;CAC3D,IAAI;EACH,MAAM;CACP,SAAS,OAAO;EACf,OAAO;CACR;AAED;;;;;;;;;AAUA,SAAgB,aAAgB,OAA6B,UAAU,qBAAwB;CAC9F,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,MAAM,IAAI,MAAM,OAAO;CAClE,OAAO;AACR;;;;;;;;AASA,eAAsB,QAAW,QAAiD;CACjF,MAAM,SAAc,CAAC;CACrB,WAAW,MAAM,SAAS,QAAQ,OAAO,KAAK,KAAK;CACnD,OAAO;AACR;;;;;;;;AASA,eAAsB,cAAiB,QAAkD;CACxF,MAAM,SAAS,OAAO,UAAU;CAChC,MAAM,SAAc,CAAC;CACrB,IAAI;EACH,OAAO,MAAM;GACZ,MAAM,SAAS,MAAM,OAAO,KAAK;GACjC,IAAI,OAAO,MAAM,OAAO;GACxB,OAAO,KAAK,OAAO,KAAK;EACzB;CACD,UAAU;EACT,OAAO,YAAY;CACpB;AACD;;;;;;;;;;;AAYA,SAAgB,cAAiB,OAA2B;CAC3D,MAAM,aAAa,KAAK,UAAU,QAAQ,MAAM,YAAY;EAC3D,IAAI,YAAY,KAAA,KAAa,OAAO,YAAY,cAAc,OAAO,YAAY,UAChF,MAAM,IAAI,MAAM,+DAA+D;EAEhF,IAAI,OAAO,YAAY,YAAY,CAAC,OAAO,SAAS,OAAO,GAC1D,MAAM,IAAI,MAAM,yCAAyC;EAE1D,OAAO;CACR,CAAC;CACD,MAAM,SAAY,KAAK,MAAM,UAAU;CACvC,MAAM,UAAqB,CAAC,MAAM;CAClC,OAAO,QAAQ,SAAS,GAAG;EAC1B,MAAM,UAAU,QAAQ,IAAI;EAC5B,IAAI,OAAO,YAAY,YAAY,CAAC,OAAO,SAAS,OAAO,GAC1D,MAAM,IAAI,MAAM,yCAAyC;EAE1D,IAAI,MAAM,QAAQ,OAAO,GACxB,KAAK,MAAM,SAAS,SAAS,QAAQ,KAAK,KAAK;OACzC,IAAI,OAAO,YAAY,YAAY,YAAY,MACrD,KAAK,MAAM,SAAS,OAAO,OAAO,OAAO,GAAG,QAAQ,KAAK,KAAK;CAEhE;CACA,OAAO;AACR;;;;;;;;AASA,SAAgB,YAAY,MAAuB;CAClD,OAAO,IAAI,IAAI,OAAO,KAAK,GAAG;AAC/B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACpFA,SAAgB,sBAA0C;CACzD,MAAM,SAAkC,CAAC;CACzC,OAAO,OAAO;CACd,MAAM,UAAU,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;CACtC,QAAQ,OAAO;CAEf,OAAO,OAAO,OAAO;EACpB;EACA,QAAQ;EACR,IAAI,MACH,CAAC,GACD,EACC,MAAM;GACL,MAAM,IAAI,MAAM,uBAAuB;EACxC,EACD,CACD;EACA,IAAI,MACH,CAAC,GACD,EACC,UAAU;GACT,MAAM,IAAI,MAAM,yBAAyB;EAC1C,EACD,CACD;EACA,IAAI,MACH,CAAC,GACD,EACC,iBAAiB;GAChB,MAAM,IAAI,MAAM,wBAAwB;EACzC,EACD,CACD;EACA,OAAO,OAAO,IAAI;CACnB,CAAC;AACF;;;;;;;AAQA,SAAgB,iBAA6E;CAC5F,MAAM,QAAiB,CAAC;CACxB,OAAO;EACN;EACA,IAAI,QAAQ;GACX,OAAO,MAAM;EACd;EACA,QAAQ,GAAG,MAAM;GAChB,MAAM,KAAK,IAAI;EAChB;EACA,QAAQ;GACP,MAAM,SAAS;EAChB;CACD;AACD;;;;;;AAOA,SAAgB,iBAAoC;CACnD,IAAI,WAA8B,CAAC;CACnC,OAAO;EACN,IAAI,QAAQ;GACX,OAAO,SAAS;EACjB;EACA,IAAI,SAAS;GACZ,SAAS,KAAK,OAAO;EACtB;EACA,MAAM,UAAU;GACf,MAAM,WAAW;GACjB,WAAW,CAAC;GACZ,MAAM,WAAsB,CAAC;GAC7B,KAAK,MAAM,WAAW,SAAS,QAAQ,GACtC,IAAI;IACH,MAAM,QAAQ;GACf,SAAS,OAAO;IACf,SAAS,KAAK,KAAK;GACpB;GAED,IAAI,SAAS,WAAW,GAAG,MAAM,SAAS;GAC1C,IAAI,SAAS,SAAS,GAAG,MAAM,IAAI,eAAe,QAAQ;EAC3D;CACD;AACD"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/core/helpers.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { EventSubscriber, JSONSafe, RetryOptions, WaitOptions } from './types.js'\n\n/**\n * Waits for a host timer to elapse.\n *\n * @param ms - The delay in milliseconds.\n * @returns A promise that resolves after the timer fires.\n */\nexport function waitForDelay(ms = 0): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Waits until a condition holds within an elapsed-time budget.\n *\n * @param description - The condition described in a timeout error.\n * @param condition - The synchronous or asynchronous condition to read.\n * @param options - The time bounds and abort signal.\n * @returns A promise that resolves when the condition first returns `true`.\n * @throws The condition's thrown value, the abort reason, or an `Error` when a bound is invalid or\n * the condition does not hold within the budget.\n * @remarks The first read is immediate. Default budget: `1000` milliseconds. Default interval: `10`\n * milliseconds.\n */\nexport async function waitForCondition(\n\tdescription: string,\n\tcondition: () => boolean | Promise<boolean>,\n\toptions?: WaitOptions,\n): Promise<void> {\n\tconst budget = options?.budget ?? 1000\n\tconst interval = options?.interval ?? 10\n\tif (!Number.isFinite(budget) || budget < 0) {\n\t\tthrow new Error('Wait budget must be finite and non-negative')\n\t}\n\tif (!Number.isFinite(interval) || interval < 0) {\n\t\tthrow new Error('Wait interval must be finite and non-negative')\n\t}\n\n\tconst start = performance.now()\n\twhile (true) {\n\t\toptions?.signal?.throwIfAborted()\n\t\tconst held = await condition()\n\t\toptions?.signal?.throwIfAborted()\n\t\tif (held) return\n\n\t\tconst elapsed = performance.now() - start\n\t\tif (elapsed >= budget) {\n\t\t\tthrow new Error(\n\t\t\t\t`Condition \"${description}\" did not hold within ${budget}ms (waited ${elapsed}ms)`,\n\t\t\t)\n\t\t}\n\t\tawait waitForDelay(interval)\n\t}\n}\n\n/**\n * Repeats a producer until one produced value satisfies a predicate.\n *\n * @typeParam T - The produced value type.\n * @param description - The operation described in an exhaustion error.\n * @param produce - The synchronous or asynchronous operation to repeat.\n * @param satisfied - The predicate that accepts a produced value.\n * @param options - The time, attempt, and abort bounds.\n * @returns The first produced value the predicate accepts.\n * @throws The predicate's thrown value, the abort reason, or an `Error` when a bound is invalid or\n * the retry exhausts its budget or attempts.\n * @remarks A producer throw counts as an unsatisfied attempt. The last producer error becomes the\n * exhaustion error's `cause`. Default budget: `1000` milliseconds. Default interval: `10`\n * milliseconds.\n */\nexport async function retryUntil<T>(\n\tdescription: string,\n\tproduce: () => T | Promise<T>,\n\tsatisfied: (value: T) => boolean,\n\toptions?: RetryOptions,\n): Promise<T> {\n\tconst budget = options?.budget ?? 1000\n\tconst interval = options?.interval ?? 10\n\tconst attempts = options?.attempts\n\tif (!Number.isFinite(budget) || budget < 0) {\n\t\tthrow new Error('Retry budget must be finite and non-negative')\n\t}\n\tif (!Number.isFinite(interval) || interval < 0) {\n\t\tthrow new Error('Retry interval must be finite and non-negative')\n\t}\n\tif (attempts !== undefined && (!Number.isInteger(attempts) || attempts < 1)) {\n\t\tthrow new Error('Retry attempts must be a positive integer')\n\t}\n\n\tconst start = performance.now()\n\tlet count = 0\n\tlet cause: unknown\n\twhile (true) {\n\t\toptions?.signal?.throwIfAborted()\n\t\tif (count > 0) {\n\t\t\tconst elapsed = performance.now() - start\n\t\t\tif (elapsed >= budget) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Retry \"${description}\" did not succeed within ${budget}ms (waited ${elapsed}ms)`,\n\t\t\t\t\t{ cause },\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tlet produced:\n\t\t\t| { readonly success: false; readonly error: unknown }\n\t\t\t| { readonly success: true; readonly value: T }\n\t\ttry {\n\t\t\tproduced = { success: true, value: await produce() }\n\t\t} catch (error) {\n\t\t\tproduced = { success: false, error }\n\t\t}\n\t\tcount += 1\n\t\toptions?.signal?.throwIfAborted()\n\n\t\tif (produced.success) {\n\t\t\tif (satisfied(produced.value)) return produced.value\n\t\t} else {\n\t\t\tcause = produced.error\n\t\t}\n\n\t\tconst elapsed = performance.now() - start\n\t\tif (elapsed >= budget) {\n\t\t\tthrow new Error(\n\t\t\t\t`Retry \"${description}\" did not succeed within ${budget}ms (waited ${elapsed}ms)`,\n\t\t\t\t{ cause },\n\t\t\t)\n\t\t}\n\t\tif (attempts !== undefined && count >= attempts) {\n\t\t\tthrow new Error(`Retry \"${description}\" did not succeed within ${attempts} attempts`, {\n\t\t\t\tcause,\n\t\t\t})\n\t\t}\n\t\tawait waitForDelay(Math.min(interval, budget - elapsed))\n\t}\n}\n\n/**\n * Waits for the first delivery from an event subscription.\n *\n * @typeParam TArgs - The delivered argument tuple.\n * @param subscribe - The function that installs the event listener and may return its cleanup.\n * @param description - The event described in a timeout error.\n * @param options - The time bounds and abort signal.\n * @returns The first delivered argument tuple.\n * @throws The subscription's thrown value, the abort reason, or an `Error` when a bound is invalid\n * or the event is not delivered within the budget.\n * @remarks Default budget: `1000` milliseconds. The interval is validated for consistency with the\n * wait family but is not used because this helper parks on the event.\n */\nexport async function waitForEvent<TArgs extends readonly unknown[]>(\n\tsubscribe: EventSubscriber<TArgs>,\n\tdescription: string,\n\toptions?: WaitOptions,\n): Promise<TArgs> {\n\tconst budget = options?.budget ?? 1000\n\tconst interval = options?.interval ?? 10\n\tif (!Number.isFinite(budget) || budget < 0) {\n\t\tthrow new Error('Event budget must be finite and non-negative')\n\t}\n\tif (!Number.isFinite(interval) || interval < 0) {\n\t\tthrow new Error('Event interval must be finite and non-negative')\n\t}\n\n\tconst signal = options?.signal\n\tsignal?.throwIfAborted()\n\tconst delivery = Promise.withResolvers<TArgs>()\n\tconst controller = new AbortController()\n\tlet timeout: ReturnType<typeof setTimeout> | undefined\n\tconst pending: Array<Promise<TArgs>> = [\n\t\tdelivery.promise,\n\t\tnew Promise((_resolve, reject) => {\n\t\t\ttimeout = setTimeout(() => {\n\t\t\t\treject(new Error(`Event \"${description}\" was not delivered within ${budget}ms`))\n\t\t\t}, budget)\n\t\t}),\n\t]\n\tif (signal !== undefined) {\n\t\tpending.push(\n\t\t\tnew Promise((_resolve, reject) => {\n\t\t\t\tconst combined = AbortSignal.any([signal, controller.signal])\n\t\t\t\tcombined.addEventListener(\n\t\t\t\t\t'abort',\n\t\t\t\t\t() => {\n\t\t\t\t\t\tif (signal.aborted) reject(signal.reason)\n\t\t\t\t\t},\n\t\t\t\t\t{ once: true },\n\t\t\t\t)\n\t\t\t}),\n\t\t)\n\t}\n\tconst result = Promise.race(pending)\n\tlet cleanup: (() => void) | void = undefined\n\ttry {\n\t\ttry {\n\t\t\tcleanup = subscribe((...args) => delivery.resolve(args))\n\t\t} catch (error) {\n\t\t\tdelivery.reject(error)\n\t\t}\n\t\treturn await result\n\t} finally {\n\t\tcontroller.abort()\n\t\tif (timeout !== undefined) clearTimeout(timeout)\n\t\tcleanup?.()\n\t}\n}\n\n/**\n * Decodes newline-delimited JSON values.\n *\n * @param text - The JSON Lines text to decode.\n * @returns The decoded values in physical-line order.\n * @throws An `Error` naming the malformed physical line, with the native `SyntaxError` as its\n * `cause`.\n */\nexport function decodeJSONLines(text: string): readonly unknown[] {\n\tconst values: unknown[] = []\n\tfor (const [index, physical] of text.split('\\n').entries()) {\n\t\tconst line = physical.endsWith('\\r') ? physical.slice(0, -1) : physical\n\t\tif (line.length === 0) continue\n\t\ttry {\n\t\t\tconst value: unknown = JSON.parse(line)\n\t\t\tvalues.push(value)\n\t\t} catch (cause) {\n\t\t\tthrow new Error(`Invalid JSON on line ${index + 1}`, { cause })\n\t\t}\n\t}\n\treturn values\n}\n\n/**\n * Captures the value thrown by a thunk.\n *\n * @param thunk - The work whose thrown value to capture.\n * @returns The thrown value, or `undefined` when the thunk completes.\n */\nexport function captureError(thunk: () => unknown): unknown {\n\ttry {\n\t\tthunk()\n\t} catch (error) {\n\t\treturn error\n\t}\n\treturn undefined\n}\n\n/**\n * Requires a value to be present.\n *\n * @typeParam T - The required value type.\n * @param value - The value to check.\n * @param message - The error message used when the value is absent.\n * @returns The present value.\n */\nexport function requireValue<T>(value: T | null | undefined, message = 'Value is required'): T {\n\tif (value === null || value === undefined) throw new Error(message)\n\treturn value\n}\n\n/**\n * Collects every value from an async iterable.\n *\n * @typeParam T - The yielded value type.\n * @param source - The async iterable to drain.\n * @returns The yielded values in iteration order.\n */\nexport async function collect<T>(source: AsyncIterable<T>): Promise<readonly T[]> {\n\tconst values: T[] = []\n\tfor await (const value of source) values.push(value)\n\treturn values\n}\n\n/**\n * Collects every value from a readable stream.\n *\n * @typeParam T - The streamed value type.\n * @param stream - The readable stream to drain.\n * @returns The streamed values in read order.\n */\nexport async function collectStream<T>(stream: ReadableStream<T>): Promise<readonly T[]> {\n\tconst reader = stream.getReader()\n\tconst values: T[] = []\n\ttry {\n\t\twhile (true) {\n\t\t\tconst result = await reader.read()\n\t\t\tif (result.done) return values\n\t\t\tvalues.push(result.value)\n\t\t}\n\t} finally {\n\t\treader.releaseLock()\n\t}\n}\n\n/**\n * Copies a JSON value through serialization and parsing.\n *\n * @typeParam T - The copied value's type, which the copy keeps.\n * @param value - The value to copy, bounded by its own `JSONSafe` projection.\n * @returns The parsed JSON copy.\n * @remarks Non-finite numbers throw because JSON would replace them with `null`. Negative zero is\n * normalized to zero by JSON serialization. The bound intersects `JSONSafe<T>` rather than\n * constraining `T` to `JSONValue`, so an interface-typed value round-trips.\n */\nexport function roundTripJSON<T>(value: T & JSONSafe<T>): T {\n\tconst serialized = JSON.stringify(value, (_key, current) => {\n\t\tif (current === undefined || typeof current === 'function' || typeof current === 'symbol') {\n\t\t\tthrow new Error('JSON values must not contain undefined, functions, or symbols')\n\t\t}\n\t\tif (typeof current === 'number' && !Number.isFinite(current)) {\n\t\t\tthrow new Error('JSON values must contain finite numbers')\n\t\t}\n\t\treturn current\n\t})\n\tconst parsed: T = JSON.parse(serialized)\n\tconst pending: unknown[] = [parsed]\n\twhile (pending.length > 0) {\n\t\tconst current = pending.pop()\n\t\tif (typeof current === 'number' && !Number.isFinite(current)) {\n\t\t\tthrow new Error('JSON values must contain finite numbers')\n\t\t}\n\t\tif (Array.isArray(current)) {\n\t\t\tfor (const child of current) pending.push(child)\n\t\t} else if (typeof current === 'object' && current !== null) {\n\t\t\tfor (const child of Object.values(current)) pending.push(child)\n\t\t}\n\t}\n\treturn parsed\n}\n\n/**\n * Resolves the parent directory of a calling module, which is the workspace root when called from\n * the conventional `tests/setup.ts` location.\n *\n * @param meta - The calling module metadata.\n * @returns The root URL one directory above the calling file.\n */\nexport function resolveRoot(meta: ImportMeta): URL {\n\treturn new URL('../', meta.url)\n}\n","import type { RecorderInterface, TeardownHandler, TeardownInterface } from './types.js'\n\n/**\n * Creates values that make common object readers throw or violate their assumptions.\n *\n * @returns A frozen array whose six values are fresh on every call.\n * @remarks Every member makes a naive reader throw. A total guard survives every member without\n * throwing. Whether it accepts or refuses one is that guard's own contract. Membership may grow in\n * a release, so test the whole returned set in a loop and include the index in each failure.\n * @example\n * ```ts\n * import { expect } from 'vitest'\n * import { createHostileValues } from '@orkestrel/test'\n *\n * function isWireRecord(value: unknown): value is Readonly<Record<string, string>> {\n * \tif (typeof value !== 'object' || value === null) return false\n * \ttry {\n * \t\tif (Object.getPrototypeOf(value) !== Object.prototype) return false\n * \t\tReflect.get(value, 'value')\n * \t\tif (Reflect.ownKeys(value).length === 0) return false\n * \t\treturn Object.values(value).every((member) => typeof member === 'string')\n * \t} catch {\n * \t\treturn false\n * \t}\n * }\n *\n * for (const [index, value] of createHostileValues().entries()) {\n * \tlet accepted: boolean | undefined\n * \texpect(() => {\n * \t\taccepted = isWireRecord(value)\n * \t}, `hostile value ${index}`).not.toThrow()\n * \texpect(accepted, `hostile value ${index}`).toBe(false)\n * }\n * ```\n */\nexport function createHostileValues(): readonly unknown[] {\n\tconst cyclic: Record<string, unknown> = {}\n\tcyclic.self = cyclic\n\tconst revoked = Proxy.revocable({}, {})\n\trevoked.revoke()\n\n\treturn Object.freeze([\n\t\tcyclic,\n\t\trevoked.proxy,\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\tget() {\n\t\t\t\t\tthrow new Error('Hostile property read')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\townKeys() {\n\t\t\t\t\tthrow new Error('Hostile key enumeration')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\tgetPrototypeOf() {\n\t\t\t\t\tthrow new Error('Hostile prototype read')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tObject.create(null),\n\t])\n}\n\n/**\n * Creates a recorder for callback arguments.\n *\n * @typeParam TArgs - The argument tuple to record.\n * @returns A recorder whose handler appends calls in order.\n */\nexport function createRecorder<TArgs extends readonly unknown[]>(): RecorderInterface<TArgs> {\n\tconst calls: TArgs[] = []\n\treturn {\n\t\tcalls,\n\t\tget count() {\n\t\t\treturn calls.length\n\t\t},\n\t\thandler(...args) {\n\t\t\tcalls.push(args)\n\t\t},\n\t\tclear() {\n\t\t\tcalls.length = 0\n\t\t},\n\t}\n}\n\n/**\n * Creates a teardown list that runs registered handlers newest-first.\n *\n * @returns A teardown list that awaits every handler and collects failures.\n */\nexport function createTeardown(): TeardownInterface {\n\tlet handlers: TeardownHandler[] = []\n\treturn {\n\t\tget count() {\n\t\t\treturn handlers.length\n\t\t},\n\t\tadd(handler) {\n\t\t\thandlers.push(handler)\n\t\t},\n\t\tasync destroy() {\n\t\t\tconst snapshot = handlers\n\t\t\thandlers = []\n\t\t\tconst failures: unknown[] = []\n\t\t\tfor (const handler of snapshot.reverse()) {\n\t\t\t\ttry {\n\t\t\t\t\tawait handler()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailures.push(error)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (failures.length === 1) throw failures[0]\n\t\t\tif (failures.length > 1) throw new AggregateError(failures)\n\t\t},\n\t}\n}\n"],"mappings":"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"}