@vielzeug/assay 2.2.0 → 26.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -21
- package/dist/assay.cjs +1 -1
- package/dist/assay.cjs.map +1 -1
- package/dist/assay.iife.js +1 -1
- package/dist/assay.iife.js.map +1 -1
- package/dist/assay.js +1 -1
- package/dist/assay.js.map +1 -1
- package/dist/errors.cjs +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -9
- package/dist/errors.js.map +1 -1
- package/dist/events.cjs +1 -1
- package/dist/events.cjs.map +1 -1
- package/dist/events.d.ts +18 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +1 -32
- package/dist/events.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/query.cjs +1 -1
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +1 -37
- package/dist/query.js.map +1 -1
- package/dist/wait.cjs +1 -1
- package/dist/wait.cjs.map +1 -1
- package/dist/wait.d.ts +16 -13
- package/dist/wait.d.ts.map +1 -1
- package/dist/wait.js +1 -66
- package/dist/wait.js.map +1 -1
- package/package.json +13 -9
- package/dist/live-region.cjs +0 -2
- package/dist/live-region.cjs.map +0 -1
- package/dist/live-region.d.ts +0 -66
- package/dist/live-region.d.ts.map +0 -1
- package/dist/live-region.js +0 -65
- package/dist/live-region.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vielzeug/assay",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Framework-agnostic DOM testing primitives — scoped queries, event dispatch,
|
|
3
|
+
"version": "26.10.0",
|
|
4
|
+
"description": "Framework-agnostic DOM testing primitives — scoped queries, event dispatch, async waiting",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/helmuthdu/vielzeug",
|
|
@@ -30,20 +30,24 @@
|
|
|
30
30
|
"registry": "https://registry.npmjs.org/"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "vite build && pnpm run build:bundle && pnpm run build:types",
|
|
33
|
+
"build": "vite build && pnpm --silent run build:bundle && pnpm --silent run build:types && pnpm --silent run verify:exports",
|
|
34
34
|
"build:bundle": "vite build --config vite.bundle.config.ts",
|
|
35
35
|
"build:types": "tsc -p tsconfig.declarations.json",
|
|
36
36
|
"fix": "biome check --write src",
|
|
37
37
|
"lint": "biome ci src",
|
|
38
|
-
"prepublishOnly": "pnpm run build",
|
|
38
|
+
"prepublishOnly": "pnpm --silent run build",
|
|
39
39
|
"preview": "vite preview",
|
|
40
|
-
"test": "vitest"
|
|
40
|
+
"test": "vitest",
|
|
41
|
+
"verify:exports": "node verify-exports.mjs"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@vielzeug/arsenal": "26.10.0"
|
|
41
45
|
},
|
|
42
46
|
"devDependencies": {
|
|
43
|
-
"@types/node": "^26.2
|
|
44
|
-
"jsdom": "^30.
|
|
47
|
+
"@types/node": "^26.6.2",
|
|
48
|
+
"jsdom": "^30.1.1",
|
|
45
49
|
"typescript": "^6.0.3",
|
|
46
|
-
"vite": "^8.
|
|
47
|
-
"vitest": "^
|
|
50
|
+
"vite": "^8.3.0",
|
|
51
|
+
"vitest": "^5.0.1"
|
|
48
52
|
}
|
|
49
53
|
}
|
package/dist/live-region.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./errors.cjs"),t=require("./wait.cjs");var n={alert:`assertive`,status:`polite`};function r(e){let t=e.getAttribute(`aria-live`);if(t)return t;let r=e.getAttribute(`role`);return r&&r in n?n[r]:null}function i(e={}){let t=e.document??(typeof document<`u`?document:null),n=e.politeness??`polite`,i=e.root??t?.body??null;if(!i)return null;let a=Array.from(i.querySelectorAll(`[aria-live], [role="status"], [role="alert"]`));for(let t of a)if(r(t)===n&&(e.role===void 0||t.getAttribute(`role`)===e.role))return t;return null}function a(e={}){let t=e.document??(typeof document<`u`?document:null),n=e.politeness??`polite`,i=e.root??t?.body??null;return i?Array.from(i.querySelectorAll(`[aria-live], [role="status"], [role="alert"]`)).filter(t=>!(r(t)!==n||e.role!==void 0&&t.getAttribute(`role`)!==e.role)):[]}async function o(n,r={}){let a=r.politeness??`polite`,o=null;return await t.retry(()=>{let t=i({document:r.document,politeness:a,role:r.role,root:r.root});if(!t)throw new e.AssayQueryError(`No ${a} live region found.`);if(!t.textContent?.includes(n))throw new e.AssayQueryError(`Live region text "${t.textContent}" does not include "${n}".`);o=t},{interval:r.interval,message:`waitForLiveRegion("${n}")`,signal:r.signal,timeout:r.timeout}),o}async function s(n={}){let r=n.politeness??`polite`;await t.retry(()=>{let t=i({document:n.document,politeness:r,role:n.role,root:n.root});if(!t)throw new e.AssayQueryError(`No ${r} live region found.`);if(t.textContent!==``)throw new e.AssayQueryError(`Live region not cleared: "${t.textContent}".`)},{interval:n.interval,message:`waitForLiveRegionCleared`,signal:n.signal,timeout:n.timeout})}exports.queryAllLiveRegions=a,exports.queryLiveRegion=i,exports.waitForLiveRegion=o,exports.waitForLiveRegionCleared=s;
|
|
2
|
-
//# sourceMappingURL=live-region.cjs.map
|
package/dist/live-region.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"live-region.cjs","names":[],"sources":["../src/live-region.ts"],"sourcesContent":["import { AssayQueryError } from './errors';\nimport { retry, type WaitOptions } from './wait';\n\n/** Politeness levels for ARIA live regions. */\nexport type LiveRegionPoliteness = 'assertive' | 'off' | 'polite';\n\n/**\n * ARIA roles that carry an implicit `aria-live` value per the WAI-ARIA spec.\n * `role=\"status\"` → `polite`, `role=\"alert\"` → `assertive`. Elements with these\n * roles are live regions even without an explicit `aria-live` attribute.\n */\nconst IMPLICIT_ROLE_POLITENESS: Record<string, LiveRegionPoliteness> = {\n alert: 'assertive',\n status: 'polite',\n};\n\nexport interface LiveRegionQueryOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live` (or implicit role). Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match (e.g. `'status'`, `'alert'`). Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Resolves the effective politeness of an element: explicit `aria-live` wins,\n * otherwise falls back to the implicit value from `role=\"status\"`/`role=\"alert\"`.\n * Returns `null` when the element is not a live region.\n */\nfunction resolvePoliteness(el: HTMLElement): LiveRegionPoliteness | null {\n const explicit = el.getAttribute('aria-live');\n\n if (explicit) return explicit as LiveRegionPoliteness;\n\n const role = el.getAttribute('role');\n\n if (role && role in IMPLICIT_ROLE_POLITENESS) return IMPLICIT_ROLE_POLITENESS[role];\n\n return null;\n}\n\n/**\n * Queries the first live region matching the given politeness (and optional role)\n * within `root`. Returns `null` when none exists — safe to call before the region\n * has been lazily created.\n *\n * Matches both explicit `aria-live` attributes and implicit roles (`status` →\n * polite, `alert` → assertive) per the WAI-ARIA spec.\n *\n * @example\n * const region = queryLiveRegion({ politeness: 'polite' });\n * expect(region).not.toBeNull();\n */\nexport function queryLiveRegion(options: LiveRegionQueryOptions = {}): HTMLElement | null {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return null;\n\n const candidates = Array.from(root.querySelectorAll<HTMLElement>('[aria-live], [role=\"status\"], [role=\"alert\"]'));\n\n for (const el of candidates) {\n if (resolvePoliteness(el) !== politeness) continue;\n\n if (options.role !== undefined && el.getAttribute('role') !== options.role) continue;\n\n return el;\n }\n\n return null;\n}\n\n/**\n * Queries all live regions matching the given politeness (and optional role)\n * within `root`. Useful for asserting no duplicate regions were created.\n *\n * @example\n * expect(queryAllLiveRegions({ politeness: 'polite' })).toHaveLength(1);\n */\nexport function queryAllLiveRegions(options: LiveRegionQueryOptions = {}): HTMLElement[] {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return [];\n\n return Array.from(root.querySelectorAll<HTMLElement>('[aria-live], [role=\"status\"], [role=\"alert\"]')).filter((el) => {\n if (resolvePoliteness(el) !== politeness) return false;\n\n if (options.role !== undefined && el.getAttribute('role') !== options.role) return false;\n\n return true;\n });\n}\n\nexport interface WaitForLiveRegionOptions extends WaitOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live`. Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match. Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and contains\n * `text` (substring match). Throws `AssayTimeoutError` on timeout.\n *\n * Handles the clear-then-set announce pattern: the region may briefly be empty\n * before the message is written, so polling until the text appears is the\n * correct approach rather than a single snapshot assertion.\n *\n * @example\n * await waitForLiveRegion({ text: '3 results found' });\n * await waitForLiveRegion({ politeness: 'assertive', text: 'Session expired' });\n */\nexport async function waitForLiveRegion(text: string, options: WaitForLiveRegionOptions = {}): Promise<HTMLElement> {\n const politeness = options.politeness ?? 'polite';\n let matched: HTMLElement | null = null;\n\n await retry(\n () => {\n const region = queryLiveRegion({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n\n if (!region) throw new AssayQueryError(`No ${politeness} live region found.`);\n\n if (!region.textContent?.includes(text)) {\n throw new AssayQueryError(`Live region text \"${region.textContent}\" does not include \"${text}\".`);\n }\n\n matched = region;\n },\n {\n interval: options.interval,\n message: `waitForLiveRegion(\"${text}\")`,\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n\n // `retry` only resolves once the assertion stops throwing, so `matched` is set.\n return matched!;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and is empty\n * (cleared). Throws `AssayTimeoutError` on timeout.\n *\n * @example\n * await waitForLiveRegionCleared();\n */\nexport async function waitForLiveRegionCleared(options: WaitForLiveRegionOptions = {}): Promise<void> {\n const politeness = options.politeness ?? 'polite';\n\n await retry(\n () => {\n const region = queryLiveRegion({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n\n if (!region) throw new AssayQueryError(`No ${politeness} live region found.`);\n\n if (region.textContent !== '') {\n throw new AssayQueryError(`Live region not cleared: \"${region.textContent}\".`);\n }\n },\n {\n interval: options.interval,\n message: 'waitForLiveRegionCleared',\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n}\n"],"mappings":"wDAWA,IAAM,EAAiE,CACrE,MAAO,YACP,OAAQ,QACV,EAkBA,SAAS,EAAkB,EAA8C,CACvE,IAAM,EAAW,EAAG,aAAa,WAAW,EAE5C,GAAI,EAAU,OAAO,EAErB,IAAM,EAAO,EAAG,aAAa,MAAM,EAInC,OAFI,GAAQ,KAAQ,EAAiC,EAAyB,GAEvE,IACT,CAcA,SAAgB,EAAgB,EAAkC,CAAC,EAAuB,CACxF,IAAM,EAAM,EAAQ,WAAa,OAAO,SAAa,IAAc,SAAW,MACxE,EAAa,EAAQ,YAAc,SACnC,EAAO,EAAQ,MAAQ,GAAK,MAAQ,KAE1C,GAAI,CAAC,EAAM,OAAO,KAElB,IAAM,EAAa,MAAM,KAAK,EAAK,iBAA8B,8CAA8C,CAAC,EAEhH,IAAK,IAAM,KAAM,EACX,KAAkB,CAAE,IAAM,IAE1B,EAAQ,OAAS,IAAA,IAAa,EAAG,aAAa,MAAM,IAAM,EAAQ,MAEtE,OAAO,EAGT,OAAO,IACT,CASA,SAAgB,EAAoB,EAAkC,CAAC,EAAkB,CACvF,IAAM,EAAM,EAAQ,WAAa,OAAO,SAAa,IAAc,SAAW,MACxE,EAAa,EAAQ,YAAc,SACnC,EAAO,EAAQ,MAAQ,GAAK,MAAQ,KAI1C,OAFK,EAEE,MAAM,KAAK,EAAK,iBAA8B,8CAA8C,CAAC,CAAC,CAAC,OAAQ,GAG5G,EAFI,EAAkB,CAAE,IAAM,GAE1B,EAAQ,OAAS,IAAA,IAAa,EAAG,aAAa,MAAM,IAAM,EAAQ,KAGvE,EARiB,CAAC,CASrB,CAyBA,eAAsB,EAAkB,EAAc,EAAoC,CAAC,EAAyB,CAClH,IAAM,EAAa,EAAQ,YAAc,SACrC,EAA8B,KA4BlC,OA1BA,MAAM,EAAA,UACE,CACJ,IAAM,EAAS,EAAgB,CAC7B,SAAU,EAAQ,SAClB,aACA,KAAM,EAAQ,KACd,KAAM,EAAQ,IAChB,CAAC,EAED,GAAI,CAAC,EAAQ,MAAM,IAAI,EAAA,gBAAgB,MAAM,EAAW,oBAAoB,EAE5E,GAAI,CAAC,EAAO,aAAa,SAAS,CAAI,EACpC,MAAM,IAAI,EAAA,gBAAgB,qBAAqB,EAAO,YAAY,sBAAsB,EAAK,GAAG,EAGlG,EAAU,CACZ,EACA,CACE,SAAU,EAAQ,SAClB,QAAS,sBAAsB,EAAK,IACpC,OAAQ,EAAQ,OAChB,QAAS,EAAQ,OACnB,CACF,EAGO,CACT,CASA,eAAsB,EAAyB,EAAoC,CAAC,EAAkB,CACpG,IAAM,EAAa,EAAQ,YAAc,SAEzC,MAAM,EAAA,UACE,CACJ,IAAM,EAAS,EAAgB,CAC7B,SAAU,EAAQ,SAClB,aACA,KAAM,EAAQ,KACd,KAAM,EAAQ,IAChB,CAAC,EAED,GAAI,CAAC,EAAQ,MAAM,IAAI,EAAA,gBAAgB,MAAM,EAAW,oBAAoB,EAE5E,GAAI,EAAO,cAAgB,GACzB,MAAM,IAAI,EAAA,gBAAgB,6BAA6B,EAAO,YAAY,GAAG,CAEjF,EACA,CACE,SAAU,EAAQ,SAClB,QAAS,2BACT,OAAQ,EAAQ,OAChB,QAAS,EAAQ,OACnB,CACF,CACF"}
|
package/dist/live-region.d.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { type WaitOptions } from './wait';
|
|
2
|
-
/** Politeness levels for ARIA live regions. */
|
|
3
|
-
export type LiveRegionPoliteness = 'assertive' | 'off' | 'polite';
|
|
4
|
-
export interface LiveRegionQueryOptions {
|
|
5
|
-
/** The document to search. Defaults to the global `document`. */
|
|
6
|
-
document?: Document;
|
|
7
|
-
/** Politeness to match via `aria-live` (or implicit role). Defaults to `'polite'`. */
|
|
8
|
-
politeness?: LiveRegionPoliteness;
|
|
9
|
-
/** ARIA role to match (e.g. `'status'`, `'alert'`). Omit to match any role. */
|
|
10
|
-
role?: string;
|
|
11
|
-
/** Root to scope the query. Defaults to `document.body`. */
|
|
12
|
-
root?: ParentNode;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Queries the first live region matching the given politeness (and optional role)
|
|
16
|
-
* within `root`. Returns `null` when none exists — safe to call before the region
|
|
17
|
-
* has been lazily created.
|
|
18
|
-
*
|
|
19
|
-
* Matches both explicit `aria-live` attributes and implicit roles (`status` →
|
|
20
|
-
* polite, `alert` → assertive) per the WAI-ARIA spec.
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* const region = queryLiveRegion({ politeness: 'polite' });
|
|
24
|
-
* expect(region).not.toBeNull();
|
|
25
|
-
*/
|
|
26
|
-
export declare function queryLiveRegion(options?: LiveRegionQueryOptions): HTMLElement | null;
|
|
27
|
-
/**
|
|
28
|
-
* Queries all live regions matching the given politeness (and optional role)
|
|
29
|
-
* within `root`. Useful for asserting no duplicate regions were created.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* expect(queryAllLiveRegions({ politeness: 'polite' })).toHaveLength(1);
|
|
33
|
-
*/
|
|
34
|
-
export declare function queryAllLiveRegions(options?: LiveRegionQueryOptions): HTMLElement[];
|
|
35
|
-
export interface WaitForLiveRegionOptions extends WaitOptions {
|
|
36
|
-
/** The document to search. Defaults to the global `document`. */
|
|
37
|
-
document?: Document;
|
|
38
|
-
/** Politeness to match via `aria-live`. Defaults to `'polite'`. */
|
|
39
|
-
politeness?: LiveRegionPoliteness;
|
|
40
|
-
/** ARIA role to match. Omit to match any role. */
|
|
41
|
-
role?: string;
|
|
42
|
-
/** Root to scope the query. Defaults to `document.body`. */
|
|
43
|
-
root?: ParentNode;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Retries until a live region matching the politeness/role exists and contains
|
|
47
|
-
* `text` (substring match). Throws `AssayTimeoutError` on timeout.
|
|
48
|
-
*
|
|
49
|
-
* Handles the clear-then-set announce pattern: the region may briefly be empty
|
|
50
|
-
* before the message is written, so polling until the text appears is the
|
|
51
|
-
* correct approach rather than a single snapshot assertion.
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* await waitForLiveRegion({ text: '3 results found' });
|
|
55
|
-
* await waitForLiveRegion({ politeness: 'assertive', text: 'Session expired' });
|
|
56
|
-
*/
|
|
57
|
-
export declare function waitForLiveRegion(text: string, options?: WaitForLiveRegionOptions): Promise<HTMLElement>;
|
|
58
|
-
/**
|
|
59
|
-
* Retries until a live region matching the politeness/role exists and is empty
|
|
60
|
-
* (cleared). Throws `AssayTimeoutError` on timeout.
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* await waitForLiveRegionCleared();
|
|
64
|
-
*/
|
|
65
|
-
export declare function waitForLiveRegionCleared(options?: WaitForLiveRegionOptions): Promise<void>;
|
|
66
|
-
//# sourceMappingURL=live-region.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"live-region.d.ts","sourceRoot":"","sources":["../src/live-region.ts"],"names":[],"mappings":"AACA,OAAO,EAAS,KAAK,WAAW,EAAE,MAAM,QAAQ,CAAC;AAEjD,+CAA+C;AAC/C,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG,KAAK,GAAG,QAAQ,CAAC;AAYlE,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,sFAAsF;IACtF,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAmBD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,WAAW,GAAG,IAAI,CAkBxF;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,sBAA2B,GAAG,WAAW,EAAE,CAcvF;AAED,MAAM,WAAW,wBAAyB,SAAQ,WAAW;IAC3D,iEAAiE;IACjE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,mEAAmE;IACnE,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,kDAAkD;IAClD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,WAAW,CAAC,CA+BlH;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,GAAE,wBAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBpG"}
|
package/dist/live-region.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { AssayQueryError as e } from "./errors.js";
|
|
2
|
-
import { retry as t } from "./wait.js";
|
|
3
|
-
//#region src/live-region.ts
|
|
4
|
-
var n = {
|
|
5
|
-
alert: "assertive",
|
|
6
|
-
status: "polite"
|
|
7
|
-
};
|
|
8
|
-
function r(e) {
|
|
9
|
-
let t = e.getAttribute("aria-live");
|
|
10
|
-
if (t) return t;
|
|
11
|
-
let r = e.getAttribute("role");
|
|
12
|
-
return r && r in n ? n[r] : null;
|
|
13
|
-
}
|
|
14
|
-
function i(e = {}) {
|
|
15
|
-
let t = e.document ?? (typeof document < "u" ? document : null), n = e.politeness ?? "polite", i = e.root ?? t?.body ?? null;
|
|
16
|
-
if (!i) return null;
|
|
17
|
-
let a = Array.from(i.querySelectorAll("[aria-live], [role=\"status\"], [role=\"alert\"]"));
|
|
18
|
-
for (let t of a) if (r(t) === n && (e.role === void 0 || t.getAttribute("role") === e.role)) return t;
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
function a(e = {}) {
|
|
22
|
-
let t = e.document ?? (typeof document < "u" ? document : null), n = e.politeness ?? "polite", i = e.root ?? t?.body ?? null;
|
|
23
|
-
return i ? Array.from(i.querySelectorAll("[aria-live], [role=\"status\"], [role=\"alert\"]")).filter((t) => !(r(t) !== n || e.role !== void 0 && t.getAttribute("role") !== e.role)) : [];
|
|
24
|
-
}
|
|
25
|
-
async function o(n, r = {}) {
|
|
26
|
-
let a = r.politeness ?? "polite", o = null;
|
|
27
|
-
return await t(() => {
|
|
28
|
-
let t = i({
|
|
29
|
-
document: r.document,
|
|
30
|
-
politeness: a,
|
|
31
|
-
role: r.role,
|
|
32
|
-
root: r.root
|
|
33
|
-
});
|
|
34
|
-
if (!t) throw new e(`No ${a} live region found.`);
|
|
35
|
-
if (!t.textContent?.includes(n)) throw new e(`Live region text "${t.textContent}" does not include "${n}".`);
|
|
36
|
-
o = t;
|
|
37
|
-
}, {
|
|
38
|
-
interval: r.interval,
|
|
39
|
-
message: `waitForLiveRegion("${n}")`,
|
|
40
|
-
signal: r.signal,
|
|
41
|
-
timeout: r.timeout
|
|
42
|
-
}), o;
|
|
43
|
-
}
|
|
44
|
-
async function s(n = {}) {
|
|
45
|
-
let r = n.politeness ?? "polite";
|
|
46
|
-
await t(() => {
|
|
47
|
-
let t = i({
|
|
48
|
-
document: n.document,
|
|
49
|
-
politeness: r,
|
|
50
|
-
role: n.role,
|
|
51
|
-
root: n.root
|
|
52
|
-
});
|
|
53
|
-
if (!t) throw new e(`No ${r} live region found.`);
|
|
54
|
-
if (t.textContent !== "") throw new e(`Live region not cleared: "${t.textContent}".`);
|
|
55
|
-
}, {
|
|
56
|
-
interval: n.interval,
|
|
57
|
-
message: "waitForLiveRegionCleared",
|
|
58
|
-
signal: n.signal,
|
|
59
|
-
timeout: n.timeout
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
//#endregion
|
|
63
|
-
export { a as queryAllLiveRegions, i as queryLiveRegion, o as waitForLiveRegion, s as waitForLiveRegionCleared };
|
|
64
|
-
|
|
65
|
-
//# sourceMappingURL=live-region.js.map
|
package/dist/live-region.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"live-region.js","names":[],"sources":["../src/live-region.ts"],"sourcesContent":["import { AssayQueryError } from './errors';\nimport { retry, type WaitOptions } from './wait';\n\n/** Politeness levels for ARIA live regions. */\nexport type LiveRegionPoliteness = 'assertive' | 'off' | 'polite';\n\n/**\n * ARIA roles that carry an implicit `aria-live` value per the WAI-ARIA spec.\n * `role=\"status\"` → `polite`, `role=\"alert\"` → `assertive`. Elements with these\n * roles are live regions even without an explicit `aria-live` attribute.\n */\nconst IMPLICIT_ROLE_POLITENESS: Record<string, LiveRegionPoliteness> = {\n alert: 'assertive',\n status: 'polite',\n};\n\nexport interface LiveRegionQueryOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live` (or implicit role). Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match (e.g. `'status'`, `'alert'`). Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Resolves the effective politeness of an element: explicit `aria-live` wins,\n * otherwise falls back to the implicit value from `role=\"status\"`/`role=\"alert\"`.\n * Returns `null` when the element is not a live region.\n */\nfunction resolvePoliteness(el: HTMLElement): LiveRegionPoliteness | null {\n const explicit = el.getAttribute('aria-live');\n\n if (explicit) return explicit as LiveRegionPoliteness;\n\n const role = el.getAttribute('role');\n\n if (role && role in IMPLICIT_ROLE_POLITENESS) return IMPLICIT_ROLE_POLITENESS[role];\n\n return null;\n}\n\n/**\n * Queries the first live region matching the given politeness (and optional role)\n * within `root`. Returns `null` when none exists — safe to call before the region\n * has been lazily created.\n *\n * Matches both explicit `aria-live` attributes and implicit roles (`status` →\n * polite, `alert` → assertive) per the WAI-ARIA spec.\n *\n * @example\n * const region = queryLiveRegion({ politeness: 'polite' });\n * expect(region).not.toBeNull();\n */\nexport function queryLiveRegion(options: LiveRegionQueryOptions = {}): HTMLElement | null {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return null;\n\n const candidates = Array.from(root.querySelectorAll<HTMLElement>('[aria-live], [role=\"status\"], [role=\"alert\"]'));\n\n for (const el of candidates) {\n if (resolvePoliteness(el) !== politeness) continue;\n\n if (options.role !== undefined && el.getAttribute('role') !== options.role) continue;\n\n return el;\n }\n\n return null;\n}\n\n/**\n * Queries all live regions matching the given politeness (and optional role)\n * within `root`. Useful for asserting no duplicate regions were created.\n *\n * @example\n * expect(queryAllLiveRegions({ politeness: 'polite' })).toHaveLength(1);\n */\nexport function queryAllLiveRegions(options: LiveRegionQueryOptions = {}): HTMLElement[] {\n const doc = options.document ?? (typeof document !== 'undefined' ? document : null);\n const politeness = options.politeness ?? 'polite';\n const root = options.root ?? doc?.body ?? null;\n\n if (!root) return [];\n\n return Array.from(root.querySelectorAll<HTMLElement>('[aria-live], [role=\"status\"], [role=\"alert\"]')).filter((el) => {\n if (resolvePoliteness(el) !== politeness) return false;\n\n if (options.role !== undefined && el.getAttribute('role') !== options.role) return false;\n\n return true;\n });\n}\n\nexport interface WaitForLiveRegionOptions extends WaitOptions {\n /** The document to search. Defaults to the global `document`. */\n document?: Document;\n /** Politeness to match via `aria-live`. Defaults to `'polite'`. */\n politeness?: LiveRegionPoliteness;\n /** ARIA role to match. Omit to match any role. */\n role?: string;\n /** Root to scope the query. Defaults to `document.body`. */\n root?: ParentNode;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and contains\n * `text` (substring match). Throws `AssayTimeoutError` on timeout.\n *\n * Handles the clear-then-set announce pattern: the region may briefly be empty\n * before the message is written, so polling until the text appears is the\n * correct approach rather than a single snapshot assertion.\n *\n * @example\n * await waitForLiveRegion({ text: '3 results found' });\n * await waitForLiveRegion({ politeness: 'assertive', text: 'Session expired' });\n */\nexport async function waitForLiveRegion(text: string, options: WaitForLiveRegionOptions = {}): Promise<HTMLElement> {\n const politeness = options.politeness ?? 'polite';\n let matched: HTMLElement | null = null;\n\n await retry(\n () => {\n const region = queryLiveRegion({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n\n if (!region) throw new AssayQueryError(`No ${politeness} live region found.`);\n\n if (!region.textContent?.includes(text)) {\n throw new AssayQueryError(`Live region text \"${region.textContent}\" does not include \"${text}\".`);\n }\n\n matched = region;\n },\n {\n interval: options.interval,\n message: `waitForLiveRegion(\"${text}\")`,\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n\n // `retry` only resolves once the assertion stops throwing, so `matched` is set.\n return matched!;\n}\n\n/**\n * Retries until a live region matching the politeness/role exists and is empty\n * (cleared). Throws `AssayTimeoutError` on timeout.\n *\n * @example\n * await waitForLiveRegionCleared();\n */\nexport async function waitForLiveRegionCleared(options: WaitForLiveRegionOptions = {}): Promise<void> {\n const politeness = options.politeness ?? 'polite';\n\n await retry(\n () => {\n const region = queryLiveRegion({\n document: options.document,\n politeness,\n role: options.role,\n root: options.root,\n });\n\n if (!region) throw new AssayQueryError(`No ${politeness} live region found.`);\n\n if (region.textContent !== '') {\n throw new AssayQueryError(`Live region not cleared: \"${region.textContent}\".`);\n }\n },\n {\n interval: options.interval,\n message: 'waitForLiveRegionCleared',\n signal: options.signal,\n timeout: options.timeout,\n },\n );\n}\n"],"mappings":";;;AAWA,IAAM,IAAiE;CACrE,OAAO;CACP,QAAQ;AACV;AAkBA,SAAS,EAAkB,GAA8C;CACvE,IAAM,IAAW,EAAG,aAAa,WAAW;CAE5C,IAAI,GAAU,OAAO;CAErB,IAAM,IAAO,EAAG,aAAa,MAAM;CAInC,OAFI,KAAQ,KAAQ,IAAiC,EAAyB,KAEvE;AACT;AAcA,SAAgB,EAAgB,IAAkC,CAAC,GAAuB;CACxF,IAAM,IAAM,EAAQ,aAAa,OAAO,WAAa,MAAc,WAAW,OACxE,IAAa,EAAQ,cAAc,UACnC,IAAO,EAAQ,QAAQ,GAAK,QAAQ;CAE1C,IAAI,CAAC,GAAM,OAAO;CAElB,IAAM,IAAa,MAAM,KAAK,EAAK,iBAA8B,kDAA8C,CAAC;CAEhH,KAAK,IAAM,KAAM,GACX,MAAkB,CAAE,MAAM,MAE1B,EAAQ,SAAS,KAAA,KAAa,EAAG,aAAa,MAAM,MAAM,EAAQ,OAEtE,OAAO;CAGT,OAAO;AACT;AASA,SAAgB,EAAoB,IAAkC,CAAC,GAAkB;CACvF,IAAM,IAAM,EAAQ,aAAa,OAAO,WAAa,MAAc,WAAW,OACxE,IAAa,EAAQ,cAAc,UACnC,IAAO,EAAQ,QAAQ,GAAK,QAAQ;CAI1C,OAFK,IAEE,MAAM,KAAK,EAAK,iBAA8B,kDAA8C,CAAC,CAAC,CAAC,QAAQ,MAG5G,EAFI,EAAkB,CAAE,MAAM,KAE1B,EAAQ,SAAS,KAAA,KAAa,EAAG,aAAa,MAAM,MAAM,EAAQ,KAGvE,IARiB,CAAC;AASrB;AAyBA,eAAsB,EAAkB,GAAc,IAAoC,CAAC,GAAyB;CAClH,IAAM,IAAa,EAAQ,cAAc,UACrC,IAA8B;CA4BlC,OA1BA,MAAM,QACE;EACJ,IAAM,IAAS,EAAgB;GAC7B,UAAU,EAAQ;GAClB;GACA,MAAM,EAAQ;GACd,MAAM,EAAQ;EAChB,CAAC;EAED,IAAI,CAAC,GAAQ,MAAM,IAAI,EAAgB,MAAM,EAAW,oBAAoB;EAE5E,IAAI,CAAC,EAAO,aAAa,SAAS,CAAI,GACpC,MAAM,IAAI,EAAgB,qBAAqB,EAAO,YAAY,sBAAsB,EAAK,GAAG;EAGlG,IAAU;CACZ,GACA;EACE,UAAU,EAAQ;EAClB,SAAS,sBAAsB,EAAK;EACpC,QAAQ,EAAQ;EAChB,SAAS,EAAQ;CACnB,CACF,GAGO;AACT;AASA,eAAsB,EAAyB,IAAoC,CAAC,GAAkB;CACpG,IAAM,IAAa,EAAQ,cAAc;CAEzC,MAAM,QACE;EACJ,IAAM,IAAS,EAAgB;GAC7B,UAAU,EAAQ;GAClB;GACA,MAAM,EAAQ;GACd,MAAM,EAAQ;EAChB,CAAC;EAED,IAAI,CAAC,GAAQ,MAAM,IAAI,EAAgB,MAAM,EAAW,oBAAoB;EAE5E,IAAI,EAAO,gBAAgB,IACzB,MAAM,IAAI,EAAgB,6BAA6B,EAAO,YAAY,GAAG;CAEjF,GACA;EACE,UAAU,EAAQ;EAClB,SAAS;EACT,QAAQ,EAAQ;EAChB,SAAS,EAAQ;CACnB,CACF;AACF"}
|