@baseline-ui/test-utils 0.60.1 → 0.62.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/dist/index.d.mts CHANGED
@@ -107,4 +107,197 @@ declare const expect: _playwright_test.Expect<{
107
107
  }>;
108
108
  }>;
109
109
 
110
- export { DEFAULT_CRITICAL_INCOMPLETE_RULES, DEFAULT_DISABLED_RULES, DEFAULT_WCAG_TAGS, type ToBeAccessibleOptions, expect };
110
+ /**
111
+ * Either a Playwright `Page` or a `FrameLocator` — both expose the same
112
+ * locator-building API used by the testers. Component-test fixtures typically
113
+ * provide a `FrameLocator`; full E2E tests provide a `Page`.
114
+ */
115
+ type Frame = Page | FrameLocator;
116
+
117
+ /**
118
+ * Common options accepted by every component tester. Component testers extend
119
+ * this with their own optional fields (e.g. a custom listbox locator), but
120
+ * `frame` and `trigger` always behave the same way:
121
+ *
122
+ * - `frame` is the {@link Frame} the tester searches inside — typically the
123
+ * component-test mount frame, but may be a `Page` for full E2E tests.
124
+ * - `trigger` is the focusable element that opens/closes the component. Every
125
+ * tester provides a sensible default keyed off the component's stable
126
+ * `data-testid` (see CONVENTIONS.md). Override it when the consumer renders
127
+ * a custom trigger that doesn't carry that test id.
128
+ */
129
+ interface BaseTesterOptions {
130
+ frame: Frame;
131
+ trigger?: Locator;
132
+ }
133
+ /**
134
+ * Keys recognised by {@link disclosure}'s `open(how)`. The same set covers
135
+ * Select, Menu, Combobox and DatePicker — disclosure-style components whose
136
+ * trigger expands a popover. Components that only open via click (e.g. a
137
+ * Dialog with no keyboard activation other than Enter) can ignore the keyboard
138
+ * paths and simply call `open("click")`.
139
+ */
140
+ type DisclosureOpenWith = "click" | "Space" | "Enter" | "ArrowDown" | "ArrowUp";
141
+
142
+ /**
143
+ * Options for {@link selectTester}. Defaults the trigger to
144
+ * `frame.getByTestId("select-button")` (the convention for both single- and
145
+ * multi-select). Pass `trigger` when the Select renders a custom trigger that
146
+ * doesn't carry that test id.
147
+ *
148
+ * `multiple` and `autocomplete` are **orthogonal** flags that mirror the
149
+ * component shape:
150
+ *
151
+ * - `multiple: true` matches `<Select selectionMode="multiple">` and adds the
152
+ * `tag`, `removeTagButton`, `selectAllButton`, `clearButton` surface.
153
+ * - `autocomplete: true` matches `<Select>` wrapped in `<Autocomplete>` and
154
+ * adds `searchbox` + `filter()`.
155
+ *
156
+ * They can be combined.
157
+ */
158
+ interface SelectTesterOptions extends BaseTesterOptions {
159
+ multiple?: boolean;
160
+ autocomplete?: boolean;
161
+ }
162
+ /**
163
+ * Driver for a single-select `<Select>` in Playwright component tests.
164
+ * Construct with {@link selectTester}.
165
+ */
166
+ interface SelectTester {
167
+ /** The trigger button. */
168
+ readonly trigger: Locator;
169
+ /** The popover listbox. Only attached while the Select is open. */
170
+ readonly listbox: Locator;
171
+ /** Locator for a single option by accessible name. */
172
+ option: (name: string | RegExp) => Locator;
173
+ /**
174
+ * Opens the Select if it isn't already open. Idempotent — checks
175
+ * `aria-expanded` on the trigger and bails if the popover is already shown.
176
+ *
177
+ * @param how How to open. Defaults to `"click"`. Key names focus the
178
+ * trigger and press the key.
179
+ */
180
+ open: (how?: DisclosureOpenWith) => Promise<void>;
181
+ /** Opens the Select (if needed) and clicks the option matching `name`. */
182
+ selectOption: (name: string | RegExp) => Promise<void>;
183
+ }
184
+ /**
185
+ * Multi-select capability mixed in when {@link SelectTesterOptions.multiple}
186
+ * is `true`. Adds tag chips on the trigger plus the Select All / Clear
187
+ * buttons inside the popover.
188
+ */
189
+ interface MultiSelectTester extends SelectTester {
190
+ /**
191
+ * The "Select All" action button inside the popover. Located by stable
192
+ * `data-testid` rather than the translated label. Only present while open.
193
+ */
194
+ readonly selectAllButton: Locator;
195
+ /**
196
+ * The "Clear" action button inside the popover. Located by stable
197
+ * `data-testid` rather than the translated label. Only present while open.
198
+ */
199
+ readonly clearButton: Locator;
200
+ /**
201
+ * Locator for a selected-value tag chip rendered inside the trigger. Tags
202
+ * are rendered by `TagGroup` as `role="row"`; `name` is matched against
203
+ * the row's accessible name (string substring or RegExp — useful for the
204
+ * `+N more` overflow tag).
205
+ */
206
+ tag: (name: string | RegExp) => Locator;
207
+ /**
208
+ * The remove button on a tag chip identified by `tagName`. Resolved as the
209
+ * button *inside* the matching row, so the helper does not depend on the
210
+ * translated "Remove X" label format.
211
+ */
212
+ removeTagButton: (tagName: string | RegExp) => Locator;
213
+ }
214
+ /**
215
+ * Autocomplete capability mixed in when {@link SelectTesterOptions.autocomplete}
216
+ * is `true`. Adds the popover searchbox.
217
+ */
218
+ interface AutocompleteTester extends SelectTester {
219
+ /**
220
+ * The search input inside the popover. Only attached while the popover is
221
+ * open.
222
+ */
223
+ readonly searchbox: Locator;
224
+ /**
225
+ * Types into the searchbox to filter options. Does **not** open the
226
+ * Select — call `open()` first. Uses `fill` (replaces existing value); to
227
+ * clear, use `searchbox.clear()`.
228
+ */
229
+ filter: (text: string) => Promise<void>;
230
+ }
231
+ /**
232
+ * Overloaded call signature for {@link selectTester}. The return type narrows
233
+ * based on the `multiple` and `autocomplete` flags:
234
+ *
235
+ * - `select({})` → {@link SelectTester}
236
+ * - `select({ multiple: true })` → {@link MultiSelectTester}
237
+ * - `select({ autocomplete: true })` → {@link AutocompleteTester}
238
+ * - `select({ multiple: true, autocomplete: true })` →
239
+ * {@link MultiSelectTester} & {@link AutocompleteTester}
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * const select = testers(frame).select();
244
+ * await select.open("Space");
245
+ * await select.selectOption("Square");
246
+ *
247
+ * const multi = testers(frame).select({ multiple: true });
248
+ * await multi.selectAllButton.click();
249
+ *
250
+ * const auto = testers(frame).select({ autocomplete: true });
251
+ * await auto.open();
252
+ * await auto.filter("first");
253
+ * ```
254
+ */
255
+ interface SelectTesterFactoryFor<O extends {
256
+ multiple?: boolean;
257
+ autocomplete?: boolean;
258
+ }> {
259
+ (options: O & {
260
+ multiple: true;
261
+ autocomplete: true;
262
+ }): MultiSelectTester & AutocompleteTester;
263
+ (options: O & {
264
+ multiple: true;
265
+ autocomplete?: false;
266
+ }): MultiSelectTester;
267
+ (options: O & {
268
+ multiple?: false;
269
+ autocomplete: true;
270
+ }): AutocompleteTester;
271
+ (options?: O): SelectTester;
272
+ }
273
+
274
+ type BoundSelectOptions = Omit<SelectTesterOptions, "frame">;
275
+ /**
276
+ * The frame-bound tester factory. Methods are flat — one per component — so
277
+ * IDE autocomplete on `t.` lists the full surface in one place. Variants of a
278
+ * single component are options on its method (e.g. `t.select({ multiple: true })`),
279
+ * not separate top-level methods, so the namespace stays component-shaped.
280
+ *
281
+ * Each method's overloads mirror its underlying factory with `frame` stripped
282
+ * (the factory binds it), so the narrowing rules live in exactly one place.
283
+ */
284
+ interface Testers {
285
+ select: SelectTesterFactoryFor<BoundSelectOptions>;
286
+ }
287
+ /**
288
+ * Build a frame-bound tester factory. Construct once per test and pick the
289
+ * component you need:
290
+ *
291
+ * ```ts
292
+ * const t = testers(frame);
293
+ * const select = t.select();
294
+ * const multi = t.select({ multiple: true, trigger: customTrigger });
295
+ * const auto = t.select({ autocomplete: true });
296
+ * ```
297
+ *
298
+ * Each method forwards per-call options to the underlying factory; `frame` is
299
+ * supplied automatically.
300
+ */
301
+ declare function testers(frame: Frame): Testers;
302
+
303
+ export { type AutocompleteTester, DEFAULT_CRITICAL_INCOMPLETE_RULES, DEFAULT_DISABLED_RULES, DEFAULT_WCAG_TAGS, type Frame, type MultiSelectTester, type SelectTester, type Testers, type ToBeAccessibleOptions, expect, testers };
package/dist/index.d.ts CHANGED
@@ -107,4 +107,197 @@ declare const expect: _playwright_test.Expect<{
107
107
  }>;
108
108
  }>;
109
109
 
110
- export { DEFAULT_CRITICAL_INCOMPLETE_RULES, DEFAULT_DISABLED_RULES, DEFAULT_WCAG_TAGS, type ToBeAccessibleOptions, expect };
110
+ /**
111
+ * Either a Playwright `Page` or a `FrameLocator` — both expose the same
112
+ * locator-building API used by the testers. Component-test fixtures typically
113
+ * provide a `FrameLocator`; full E2E tests provide a `Page`.
114
+ */
115
+ type Frame = Page | FrameLocator;
116
+
117
+ /**
118
+ * Common options accepted by every component tester. Component testers extend
119
+ * this with their own optional fields (e.g. a custom listbox locator), but
120
+ * `frame` and `trigger` always behave the same way:
121
+ *
122
+ * - `frame` is the {@link Frame} the tester searches inside — typically the
123
+ * component-test mount frame, but may be a `Page` for full E2E tests.
124
+ * - `trigger` is the focusable element that opens/closes the component. Every
125
+ * tester provides a sensible default keyed off the component's stable
126
+ * `data-testid` (see CONVENTIONS.md). Override it when the consumer renders
127
+ * a custom trigger that doesn't carry that test id.
128
+ */
129
+ interface BaseTesterOptions {
130
+ frame: Frame;
131
+ trigger?: Locator;
132
+ }
133
+ /**
134
+ * Keys recognised by {@link disclosure}'s `open(how)`. The same set covers
135
+ * Select, Menu, Combobox and DatePicker — disclosure-style components whose
136
+ * trigger expands a popover. Components that only open via click (e.g. a
137
+ * Dialog with no keyboard activation other than Enter) can ignore the keyboard
138
+ * paths and simply call `open("click")`.
139
+ */
140
+ type DisclosureOpenWith = "click" | "Space" | "Enter" | "ArrowDown" | "ArrowUp";
141
+
142
+ /**
143
+ * Options for {@link selectTester}. Defaults the trigger to
144
+ * `frame.getByTestId("select-button")` (the convention for both single- and
145
+ * multi-select). Pass `trigger` when the Select renders a custom trigger that
146
+ * doesn't carry that test id.
147
+ *
148
+ * `multiple` and `autocomplete` are **orthogonal** flags that mirror the
149
+ * component shape:
150
+ *
151
+ * - `multiple: true` matches `<Select selectionMode="multiple">` and adds the
152
+ * `tag`, `removeTagButton`, `selectAllButton`, `clearButton` surface.
153
+ * - `autocomplete: true` matches `<Select>` wrapped in `<Autocomplete>` and
154
+ * adds `searchbox` + `filter()`.
155
+ *
156
+ * They can be combined.
157
+ */
158
+ interface SelectTesterOptions extends BaseTesterOptions {
159
+ multiple?: boolean;
160
+ autocomplete?: boolean;
161
+ }
162
+ /**
163
+ * Driver for a single-select `<Select>` in Playwright component tests.
164
+ * Construct with {@link selectTester}.
165
+ */
166
+ interface SelectTester {
167
+ /** The trigger button. */
168
+ readonly trigger: Locator;
169
+ /** The popover listbox. Only attached while the Select is open. */
170
+ readonly listbox: Locator;
171
+ /** Locator for a single option by accessible name. */
172
+ option: (name: string | RegExp) => Locator;
173
+ /**
174
+ * Opens the Select if it isn't already open. Idempotent — checks
175
+ * `aria-expanded` on the trigger and bails if the popover is already shown.
176
+ *
177
+ * @param how How to open. Defaults to `"click"`. Key names focus the
178
+ * trigger and press the key.
179
+ */
180
+ open: (how?: DisclosureOpenWith) => Promise<void>;
181
+ /** Opens the Select (if needed) and clicks the option matching `name`. */
182
+ selectOption: (name: string | RegExp) => Promise<void>;
183
+ }
184
+ /**
185
+ * Multi-select capability mixed in when {@link SelectTesterOptions.multiple}
186
+ * is `true`. Adds tag chips on the trigger plus the Select All / Clear
187
+ * buttons inside the popover.
188
+ */
189
+ interface MultiSelectTester extends SelectTester {
190
+ /**
191
+ * The "Select All" action button inside the popover. Located by stable
192
+ * `data-testid` rather than the translated label. Only present while open.
193
+ */
194
+ readonly selectAllButton: Locator;
195
+ /**
196
+ * The "Clear" action button inside the popover. Located by stable
197
+ * `data-testid` rather than the translated label. Only present while open.
198
+ */
199
+ readonly clearButton: Locator;
200
+ /**
201
+ * Locator for a selected-value tag chip rendered inside the trigger. Tags
202
+ * are rendered by `TagGroup` as `role="row"`; `name` is matched against
203
+ * the row's accessible name (string substring or RegExp — useful for the
204
+ * `+N more` overflow tag).
205
+ */
206
+ tag: (name: string | RegExp) => Locator;
207
+ /**
208
+ * The remove button on a tag chip identified by `tagName`. Resolved as the
209
+ * button *inside* the matching row, so the helper does not depend on the
210
+ * translated "Remove X" label format.
211
+ */
212
+ removeTagButton: (tagName: string | RegExp) => Locator;
213
+ }
214
+ /**
215
+ * Autocomplete capability mixed in when {@link SelectTesterOptions.autocomplete}
216
+ * is `true`. Adds the popover searchbox.
217
+ */
218
+ interface AutocompleteTester extends SelectTester {
219
+ /**
220
+ * The search input inside the popover. Only attached while the popover is
221
+ * open.
222
+ */
223
+ readonly searchbox: Locator;
224
+ /**
225
+ * Types into the searchbox to filter options. Does **not** open the
226
+ * Select — call `open()` first. Uses `fill` (replaces existing value); to
227
+ * clear, use `searchbox.clear()`.
228
+ */
229
+ filter: (text: string) => Promise<void>;
230
+ }
231
+ /**
232
+ * Overloaded call signature for {@link selectTester}. The return type narrows
233
+ * based on the `multiple` and `autocomplete` flags:
234
+ *
235
+ * - `select({})` → {@link SelectTester}
236
+ * - `select({ multiple: true })` → {@link MultiSelectTester}
237
+ * - `select({ autocomplete: true })` → {@link AutocompleteTester}
238
+ * - `select({ multiple: true, autocomplete: true })` →
239
+ * {@link MultiSelectTester} & {@link AutocompleteTester}
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * const select = testers(frame).select();
244
+ * await select.open("Space");
245
+ * await select.selectOption("Square");
246
+ *
247
+ * const multi = testers(frame).select({ multiple: true });
248
+ * await multi.selectAllButton.click();
249
+ *
250
+ * const auto = testers(frame).select({ autocomplete: true });
251
+ * await auto.open();
252
+ * await auto.filter("first");
253
+ * ```
254
+ */
255
+ interface SelectTesterFactoryFor<O extends {
256
+ multiple?: boolean;
257
+ autocomplete?: boolean;
258
+ }> {
259
+ (options: O & {
260
+ multiple: true;
261
+ autocomplete: true;
262
+ }): MultiSelectTester & AutocompleteTester;
263
+ (options: O & {
264
+ multiple: true;
265
+ autocomplete?: false;
266
+ }): MultiSelectTester;
267
+ (options: O & {
268
+ multiple?: false;
269
+ autocomplete: true;
270
+ }): AutocompleteTester;
271
+ (options?: O): SelectTester;
272
+ }
273
+
274
+ type BoundSelectOptions = Omit<SelectTesterOptions, "frame">;
275
+ /**
276
+ * The frame-bound tester factory. Methods are flat — one per component — so
277
+ * IDE autocomplete on `t.` lists the full surface in one place. Variants of a
278
+ * single component are options on its method (e.g. `t.select({ multiple: true })`),
279
+ * not separate top-level methods, so the namespace stays component-shaped.
280
+ *
281
+ * Each method's overloads mirror its underlying factory with `frame` stripped
282
+ * (the factory binds it), so the narrowing rules live in exactly one place.
283
+ */
284
+ interface Testers {
285
+ select: SelectTesterFactoryFor<BoundSelectOptions>;
286
+ }
287
+ /**
288
+ * Build a frame-bound tester factory. Construct once per test and pick the
289
+ * component you need:
290
+ *
291
+ * ```ts
292
+ * const t = testers(frame);
293
+ * const select = t.select();
294
+ * const multi = t.select({ multiple: true, trigger: customTrigger });
295
+ * const auto = t.select({ autocomplete: true });
296
+ * ```
297
+ *
298
+ * Each method forwards per-call options to the underlying factory; `frame` is
299
+ * supplied automatically.
300
+ */
301
+ declare function testers(frame: Frame): Testers;
302
+
303
+ export { type AutocompleteTester, DEFAULT_CRITICAL_INCOMPLETE_RULES, DEFAULT_DISABLED_RULES, DEFAULT_WCAG_TAGS, type Frame, type MultiSelectTester, type SelectTester, type Testers, type ToBeAccessibleOptions, expect, testers };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- 'use strict';var test=require('@playwright/test'),F=require('axe-core'),promises=require('fs/promises'),R=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var F__default=/*#__PURE__*/_interopDefault(F);var R__default=/*#__PURE__*/_interopDefault(R);/**
1
+ 'use strict';var test=require('@playwright/test'),$=require('axe-core'),promises=require('fs/promises'),R=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var $__default=/*#__PURE__*/_interopDefault($);var R__default=/*#__PURE__*/_interopDefault(R);/**
2
2
  * Copyright (c) 2023-2024 PSPDFKit GmbH. All rights reserved.
3
3
  *
4
4
  * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW
@@ -7,15 +7,15 @@
7
7
  * This notice may not be removed from this file.
8
8
  *
9
9
  */
10
- var A=["wcag2a","wcag2aa","wcag21a","wcag21aa","best-practice"],b=["landmark-one-main","page-has-heading-one","region","aria-allowed-role","duplicate-id","scrollable-region-focusable"],L=["aria-valid-attr-value","aria-valid-attr","aria-required-attr"];function T(e,t={}){let{prefix:n="BaselineUI-",ignoreHashed:s=false}=t,d=Array.isArray(n)?n:[n];function i(o){return o.split(/[-_]/).some(a=>a.length>=5&&/^[a-z\d]+$/.test(a)&&/[a-z]/.test(a)&&/\d/.test(a))}function c(o){return [...o.classList].filter(a=>d.some(p=>a.startsWith(p))&&(!s||!i(a)))}function l(o,a=false){let p=[],x=o.shadowRoot?o.shadowRoot.children:o.children;for(let h of x){let w=a||h.getAttribute("aria-hidden")==="true",y=c(h),E=l(h,w);y.length>0?p.push({classes:y,children:E,hidden:w}):p.push(...E);}return p}function g(o,a="",p=false){let x=[];for(let h=0;h<o.length;h++){let w=o[h],y=h===o.length-1,E=p?"":y?"\u2514\u2500\u2500 ":"\u251C\u2500\u2500 ",I=p?"":a+(y?" ":"\u2502 "),$=w.classes.join(", ");x.push(a+E+$+(w.hidden?" [aria-hidden]":""),...g(w.children,I));}return x}let u=c(e),r=l(e);return u.length===0&&r.length===0?"":u.length>0?g([{classes:u,children:r,hidden:false}],"",true).join(`
11
- `):g(r,"",true).join(`
12
- `)}var m=e=>{if(e instanceof Error)return e.message;if(typeof e=="string")return e;if(e==null)return "unknown error";try{return JSON.stringify(e)}catch{return "unstringifiable error"}},U=async e=>{let t=typeof e.goto=="function",n=!t&&typeof e.owner=="function";if(t||n)return e.locator("body");let s=e;return await s.first().evaluate(i=>i.tagName?.toLowerCase()==="iframe").catch(()=>false)?s.contentFrame().locator("body"):s},j=async e=>{await e.first().evaluate((t,n)=>{if(window.axe)return;let d=document.createElement("script");d.textContent=n,document.head.append(d);},F__default.default.source);},k=["document-title","html-has-lang","html-lang-valid","html-xml-lang-mismatch"],N=(e,t=[])=>{let n=new Set([...b,...t,...e.disableRules??[]]);return {runOnly:{type:"tag",values:[...e.tags??A]},rules:Object.fromEntries([...n].map(s=>[s,{enabled:false}]))}},_=e=>{if(e!==void 0)return typeof e=="string"?[e]:[...e]},v=async(e,t,n)=>{let s=await e.count();if(s===0)throw new Error("toBeAccessible: locator resolved to zero elements; nothing to scan.");await j(e);let d=_(t.include),i=_(t.exclude);return s===1?await e.evaluate(async(c,l)=>{let g=window,u={include:[c,...l.userInclude??[]],exclude:l.userExclude??[]};return await g.axe.run(u,l.runOptions)},{userInclude:d,userExclude:i,runOptions:n}):await e.evaluateAll(async(c,l)=>{let g=window,u={include:[...c,...l.userInclude??[]],exclude:l.userExclude??[]};return await g.axe.run(u,l.runOptions)},{userInclude:d,userExclude:i,runOptions:n})},C="BaselineUI-";function S(e){return e.replaceAll(/\W+/g,"-").replaceAll(/^-|-$/g,"")}function W(e){return Array.isArray(e)?e.length===1&&e[0]===C:e===C}function H(e){return (Array.isArray(e)?e:[e]).join("+")}function G(e,t,n,s,d){let i=t.slice(1).map(c=>S(c));return n&&i.push(S(n)),s!==void 0&&!W(s)&&i.push(S(H(s))),d&&i.push("ignoreHashed"),R__default.default.join(e,`${i.join("--")}.txt`)}var M=test.expect.extend({toBeWithinDelta(e,t,n){return Math.abs(e-t)<=n?{message:()=>`expected ${e} not to be within ${n} of ${t}`,pass:true}:{message:()=>`expected ${e} to be within ${n} of ${t}`,pass:false}},async toBeCalledOnce(e){try{return await test.expect.poll(()=>e.callCount).toBe(1),{message:()=>"Expected function to be called once.",pass:!0}}catch(t){return {message:()=>m(t),pass:false}}},async toBeCalledTwice(e){try{return await test.expect.poll(()=>e.callCount).toBe(2),{message:()=>"Expected function to be called twice.",pass:!0}}catch(t){return {message:()=>m(t),pass:false}}},async toBeCalledThrice(e){try{return await test.expect.poll(()=>e.callCount).toBe(3),{message:()=>"Expected function to be called thrice.",pass:!0}}catch(t){return {message:()=>m(t),pass:false}}},async toBeCalledWith(e,...t){try{return await test.expect(()=>{for(let[n,s]of t.entries())test.expect(e.lastCall?.args?.[n]).toStrictEqual(s);}).toPass(),{message:()=>`Expected function to be called with ${t.join(", ")}.`,pass:!0}}catch(n){return {message:()=>m(n),pass:false}}},async toBeCalledWithExactly(e,...t){try{return await test.expect.poll(()=>e.lastCall?.args).toStrictEqual(t),{message:()=>`Expected function to be called with exactly ${t.join(", ")}.`,pass:!0}}catch(n){return {message:()=>m(n),pass:false}}},toBeCalled(e){try{return test.expect(e.called).toBeTruthy(),{message:()=>"Expected function to be called.",pass:!0}}catch(t){return {message:()=>m(t),pass:false}}},async toBeCalledTimes(e,t){try{return await test.expect.poll(()=>e.callCount).toBe(t),{message:()=>`Expected function to be called ${t} times.`,pass:!0}}catch(n){return {message:()=>m(n),pass:false}}},async toBeAccessible(e,t={}){let n=await U(e),s=await n.first().evaluate(r=>{let o=r.ownerDocument?.defaultView;return o!==null&&o!==window.top}).catch(()=>false),d=N(t,s?k:[]),i,c=async()=>{let r=await v(n,t,d),o=t.treatIncompleteAsViolationIds??L,a=[...r.violations,...r.incomplete.filter(p=>o.includes(p.id))];return i={result:r,violations:a},a},l;try{if(t.timeout&&t.timeout>0){let r=test.expect.poll(c,{timeout:t.timeout});await(this.isNot?r.not.toHaveLength(0):r.toHaveLength(0));}else await c();}catch(r){l=r;}if(!i)throw new Error(`toBeAccessible: axe scan never completed.
13
- ${m(l)}`);if(i.violations.length===0)return {message:()=>"Expected accessibility violations but found none.",pass:true};this.isNot||await test.test.info().attach(t.attachmentName??"accessibility-scan-results",{body:JSON.stringify(i.result,null,2),contentType:"application/json"});let g=i.violations.map(r=>{let o=r.nodes.map(a=>Array.isArray(a.target)?a.target.join(" "):String(a.target)).join(`
14
- `);return ` \u2022 [${r.id}] ${r.help}
15
- ${o}
16
- ${r.helpUrl??""}`}).join(`
17
- `),u=i.violations.length;return {message:()=>`Found ${u} accessibility violation${u===1?"":"s"}:
18
- ${g}`,pass:false}},async toMatchClassTree(e,t,n={}){let{prefix:s=C,ignoreHashed:d=false}=n,i=await e.evaluate(T,{prefix:s,ignoreHashed:d});if(!i){let c=Array.isArray(s)?s.map(l=>`${l}*`).join(" / "):`${s}*`;return {message:()=>`No ${c} classes found in the DOM tree.`,pass:false}}try{let c=`${i}
19
- `,l=test.test.info();if(l.config.ignoreSnapshots)return {message:()=>"Class tree snapshot check skipped.",pass:!0};let u=G(l.project.snapshotDir,l.titlePath,t,s,d),r=l.config.updateSnapshots;await promises.mkdir(R__default.default.dirname(u),{recursive:!0});let o;try{o=await promises.readFile(u,"utf8");}catch(a){if(a.code==="ENOENT"&&r!=="none")return await promises.writeFile(u,c),{message:()=>"Class tree snapshot created.",pass:!0};throw a}if(o!==c){if(r==="all"||r==="changed")return await promises.writeFile(u,c),{message:()=>"Class tree snapshot updated.",pass:!0};try{test.expect(c).toBe(o);}catch(a){return {message:()=>`Class tree snapshot mismatch at ${u}
10
+ var b=["wcag2a","wcag2aa","wcag21a","wcag21aa","best-practice"],S=["landmark-one-main","page-has-heading-one","region","aria-allowed-role","duplicate-id","scrollable-region-focusable"],A=["aria-valid-attr-value","aria-valid-attr","aria-required-attr"];function O(e,t={}){let{prefix:s="BaselineUI-",ignoreHashed:n=false}=t,o=Array.isArray(s)?s:[s];function r(c){return c.split(/[-_]/).some(a=>a.length>=5&&/^[a-z\d]+$/.test(a)&&/[a-z]/.test(a)&&/\d/.test(a))}function l(c){return [...c.classList].filter(a=>o.some(d=>a.startsWith(d))&&(!n||!r(a)))}function u(c,a=false){let d=[],w=c.shadowRoot?c.shadowRoot.children:c.children;for(let h of w){let y=a||h.getAttribute("aria-hidden")==="true",x=l(h),T=u(h,y);x.length>0?d.push({classes:x,children:T,hidden:y}):d.push(...T);}return d}function f(c,a="",d=false){let w=[];for(let h=0;h<c.length;h++){let y=c[h],x=h===c.length-1,T=d?"":x?"\u2514\u2500\u2500 ":"\u251C\u2500\u2500 ",P=d?"":a+(x?" ":"\u2502 "),D=y.classes.join(", ");w.push(a+T+D+(y.hidden?" [aria-hidden]":""),...f(y.children,P));}return w}let p=l(e),i=u(e);return p.length===0&&i.length===0?"":p.length>0?f([{classes:p,children:i,hidden:false}],"",true).join(`
11
+ `):f(i,"",true).join(`
12
+ `)}var g=e=>{if(e instanceof Error)return e.message;if(typeof e=="string")return e;if(e==null)return "unknown error";try{return JSON.stringify(e)}catch{return "unstringifiable error"}},U=async e=>{let t=typeof e.goto=="function",s=!t&&typeof e.owner=="function";if(t||s)return e.locator("body");let n=e;return await n.first().evaluate(r=>r.tagName?.toLowerCase()==="iframe").catch(()=>false)?n.contentFrame().locator("body"):n},N=async e=>{await e.first().evaluate((t,s)=>{if(window.axe)return;let o=document.createElement("script");o.textContent=s,document.head.append(o);},$__default.default.source);},j=["document-title","html-has-lang","html-lang-valid","html-xml-lang-mismatch"],M=(e,t=[])=>{let s=new Set([...S,...t,...e.disableRules??[]]);return {runOnly:{type:"tag",values:[...e.tags??b]},rules:Object.fromEntries([...s].map(n=>[n,{enabled:false}]))}},F=e=>{if(e!==void 0)return typeof e=="string"?[e]:[...e]},W=async(e,t,s)=>{let n=await e.count();if(n===0)throw new Error("toBeAccessible: locator resolved to zero elements; nothing to scan.");await N(e);let o=F(t.include),r=F(t.exclude);return n===1?await e.evaluate(async(l,u)=>{let f=window,p={include:[l,...u.userInclude??[]],exclude:u.userExclude??[]};return await f.axe.run(p,u.runOptions)},{userInclude:o,userExclude:r,runOptions:s}):await e.evaluateAll(async(l,u)=>{let f=window,p={include:[...l,...u.userInclude??[]],exclude:u.userExclude??[]};return await f.axe.run(p,u.runOptions)},{userInclude:o,userExclude:r,runOptions:s})},L="BaselineUI-";function E(e){return e.replaceAll(/\W+/g,"-").replaceAll(/^-|-$/g,"")}function H(e){return Array.isArray(e)?e.length===1&&e[0]===L:e===L}function G(e){return (Array.isArray(e)?e:[e]).join("+")}function V(e,t,s,n,o){let r=t.slice(1).map(l=>E(l));return s&&r.push(E(s)),n!==void 0&&!H(n)&&r.push(E(G(n))),o&&r.push("ignoreHashed"),R__default.default.join(e,`${r.join("--")}.txt`)}var z=test.expect.extend({toBeWithinDelta(e,t,s){return Math.abs(e-t)<=s?{message:()=>`expected ${e} not to be within ${s} of ${t}`,pass:true}:{message:()=>`expected ${e} to be within ${s} of ${t}`,pass:false}},async toBeCalledOnce(e){try{return await test.expect.poll(()=>e.callCount).toBe(1),{message:()=>"Expected function to be called once.",pass:!0}}catch(t){return {message:()=>g(t),pass:false}}},async toBeCalledTwice(e){try{return await test.expect.poll(()=>e.callCount).toBe(2),{message:()=>"Expected function to be called twice.",pass:!0}}catch(t){return {message:()=>g(t),pass:false}}},async toBeCalledThrice(e){try{return await test.expect.poll(()=>e.callCount).toBe(3),{message:()=>"Expected function to be called thrice.",pass:!0}}catch(t){return {message:()=>g(t),pass:false}}},async toBeCalledWith(e,...t){try{return await test.expect(()=>{for(let[s,n]of t.entries())test.expect(e.lastCall?.args?.[s]).toStrictEqual(n);}).toPass(),{message:()=>`Expected function to be called with ${t.join(", ")}.`,pass:!0}}catch(s){return {message:()=>g(s),pass:false}}},async toBeCalledWithExactly(e,...t){try{return await test.expect.poll(()=>e.lastCall?.args).toStrictEqual(t),{message:()=>`Expected function to be called with exactly ${t.join(", ")}.`,pass:!0}}catch(s){return {message:()=>g(s),pass:false}}},toBeCalled(e){try{return test.expect(e.called).toBeTruthy(),{message:()=>"Expected function to be called.",pass:!0}}catch(t){return {message:()=>g(t),pass:false}}},async toBeCalledTimes(e,t){try{return await test.expect.poll(()=>e.callCount).toBe(t),{message:()=>`Expected function to be called ${t} times.`,pass:!0}}catch(s){return {message:()=>g(s),pass:false}}},async toBeAccessible(e,t={}){let s=await U(e),n=await s.first().evaluate(i=>{let c=i.ownerDocument?.defaultView;return c!==null&&c!==window.top}).catch(()=>false),o=M(t,n?j:[]),r,l=async()=>{let i=await W(s,t,o),c=t.treatIncompleteAsViolationIds??A,a=[...i.violations,...i.incomplete.filter(d=>c.includes(d.id))];return r={result:i,violations:a},a},u;try{if(t.timeout&&t.timeout>0){let i=test.expect.poll(l,{timeout:t.timeout});await(this.isNot?i.not.toHaveLength(0):i.toHaveLength(0));}else await l();}catch(i){u=i;}if(!r)throw new Error(`toBeAccessible: axe scan never completed.
13
+ ${g(u)}`);if(r.violations.length===0)return {message:()=>"Expected accessibility violations but found none.",pass:true};this.isNot||await test.test.info().attach(t.attachmentName??"accessibility-scan-results",{body:JSON.stringify(r.result,null,2),contentType:"application/json"});let f=r.violations.map(i=>{let c=i.nodes.map(a=>Array.isArray(a.target)?a.target.join(" "):String(a.target)).join(`
14
+ `);return ` \u2022 [${i.id}] ${i.help}
15
+ ${c}
16
+ ${i.helpUrl??""}`}).join(`
17
+ `),p=r.violations.length;return {message:()=>`Found ${p} accessibility violation${p===1?"":"s"}:
18
+ ${f}`,pass:false}},async toMatchClassTree(e,t,s={}){let{prefix:n=L,ignoreHashed:o=false}=s,r=await e.evaluate(O,{prefix:n,ignoreHashed:o});if(!r){let l=Array.isArray(n)?n.map(u=>`${u}*`).join(" / "):`${n}*`;return {message:()=>`No ${l} classes found in the DOM tree.`,pass:false}}try{let l=`${r}
19
+ `,u=test.test.info();if(u.config.ignoreSnapshots)return {message:()=>"Class tree snapshot check skipped.",pass:!0};let p=V(u.project.snapshotDir,u.titlePath,t,n,o),i=u.config.updateSnapshots;await promises.mkdir(R__default.default.dirname(p),{recursive:!0});let c;try{c=await promises.readFile(p,"utf8");}catch(a){if(a.code==="ENOENT"&&i!=="none")return await promises.writeFile(p,l),{message:()=>"Class tree snapshot created.",pass:!0};throw a}if(c!==l){if(i==="all"||i==="changed")return await promises.writeFile(p,l),{message:()=>"Class tree snapshot updated.",pass:!0};try{test.expect(l).toBe(c);}catch(a){return {message:()=>`Class tree snapshot mismatch at ${p}
20
20
 
21
- ${a.message}`,pass:!1}}}return {message:()=>"Class tree matched snapshot.",pass:!0}}catch(c){return {message:()=>m(c),pass:false}}}});exports.DEFAULT_CRITICAL_INCOMPLETE_RULES=L;exports.DEFAULT_DISABLED_RULES=b;exports.DEFAULT_WCAG_TAGS=A;exports.expect=M;
21
+ ${a.message}`,pass:!1}}}return {message:()=>"Class tree matched snapshot.",pass:!0}}catch(l){return {message:()=>g(l),pass:false}}}});function I(e){let t=async()=>{if(await e.getAttribute("aria-expanded")!==null)return e;let o=e.locator("[aria-expanded]").first();return await o.count()>0?o:e},s=async()=>await(await t()).getAttribute("aria-expanded")==="true";return {isOpen:s,open:async(o="click")=>{if(await s())return;if(o==="click"){await e.click();return}await(await t()).press(o);}}}var _=(e=>{let s=q(e);return e.multiple&&(s=J(s,e.frame)),e.autocomplete&&(s=K(s,e.frame)),s});function q({frame:e,trigger:t}){let s=t??e.getByTestId("select-button"),n=r=>e.getByRole("option",{name:r}),{open:o}=I(s);return {trigger:s,listbox:e.getByRole("listbox"),option:n,open:o,async selectOption(r){await o(),await n(r).click();}}}function J(e,t){return {...e,selectAllButton:t.getByTestId("select-all-button"),clearButton:t.getByTestId("select-clear-button"),tag:s=>e.trigger.getByRole("row",{name:s}),removeTagButton:s=>e.trigger.getByRole("row",{name:s}).getByRole("button")}}function K(e,t){let s=t.getByRole("searchbox");return {...e,searchbox:s,async filter(n){await s.fill(n);}}}function X(e){return {select:((t={})=>_({frame:e,...t}))}}exports.DEFAULT_CRITICAL_INCOMPLETE_RULES=A;exports.DEFAULT_DISABLED_RULES=S;exports.DEFAULT_WCAG_TAGS=b;exports.expect=z;exports.testers=X;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import {expect,test}from'@playwright/test';import F from'axe-core';import {mkdir,readFile,writeFile}from'fs/promises';import R from'path';/**
1
+ import {expect,test}from'@playwright/test';import $ from'axe-core';import {mkdir,readFile,writeFile}from'fs/promises';import R from'path';/**
2
2
  * Copyright (c) 2023-2024 PSPDFKit GmbH. All rights reserved.
3
3
  *
4
4
  * THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW
@@ -7,15 +7,15 @@ import {expect,test}from'@playwright/test';import F from'axe-core';import {mkdir
7
7
  * This notice may not be removed from this file.
8
8
  *
9
9
  */
10
- var A=["wcag2a","wcag2aa","wcag21a","wcag21aa","best-practice"],b=["landmark-one-main","page-has-heading-one","region","aria-allowed-role","duplicate-id","scrollable-region-focusable"],L=["aria-valid-attr-value","aria-valid-attr","aria-required-attr"];function T(e,t={}){let{prefix:n="BaselineUI-",ignoreHashed:s=false}=t,d=Array.isArray(n)?n:[n];function i(o){return o.split(/[-_]/).some(a=>a.length>=5&&/^[a-z\d]+$/.test(a)&&/[a-z]/.test(a)&&/\d/.test(a))}function c(o){return [...o.classList].filter(a=>d.some(p=>a.startsWith(p))&&(!s||!i(a)))}function l(o,a=false){let p=[],x=o.shadowRoot?o.shadowRoot.children:o.children;for(let h of x){let w=a||h.getAttribute("aria-hidden")==="true",y=c(h),E=l(h,w);y.length>0?p.push({classes:y,children:E,hidden:w}):p.push(...E);}return p}function g(o,a="",p=false){let x=[];for(let h=0;h<o.length;h++){let w=o[h],y=h===o.length-1,E=p?"":y?"\u2514\u2500\u2500 ":"\u251C\u2500\u2500 ",I=p?"":a+(y?" ":"\u2502 "),$=w.classes.join(", ");x.push(a+E+$+(w.hidden?" [aria-hidden]":""),...g(w.children,I));}return x}let u=c(e),r=l(e);return u.length===0&&r.length===0?"":u.length>0?g([{classes:u,children:r,hidden:false}],"",true).join(`
11
- `):g(r,"",true).join(`
12
- `)}var m=e=>{if(e instanceof Error)return e.message;if(typeof e=="string")return e;if(e==null)return "unknown error";try{return JSON.stringify(e)}catch{return "unstringifiable error"}},U=async e=>{let t=typeof e.goto=="function",n=!t&&typeof e.owner=="function";if(t||n)return e.locator("body");let s=e;return await s.first().evaluate(i=>i.tagName?.toLowerCase()==="iframe").catch(()=>false)?s.contentFrame().locator("body"):s},j=async e=>{await e.first().evaluate((t,n)=>{if(window.axe)return;let d=document.createElement("script");d.textContent=n,document.head.append(d);},F.source);},k=["document-title","html-has-lang","html-lang-valid","html-xml-lang-mismatch"],N=(e,t=[])=>{let n=new Set([...b,...t,...e.disableRules??[]]);return {runOnly:{type:"tag",values:[...e.tags??A]},rules:Object.fromEntries([...n].map(s=>[s,{enabled:false}]))}},_=e=>{if(e!==void 0)return typeof e=="string"?[e]:[...e]},v=async(e,t,n)=>{let s=await e.count();if(s===0)throw new Error("toBeAccessible: locator resolved to zero elements; nothing to scan.");await j(e);let d=_(t.include),i=_(t.exclude);return s===1?await e.evaluate(async(c,l)=>{let g=window,u={include:[c,...l.userInclude??[]],exclude:l.userExclude??[]};return await g.axe.run(u,l.runOptions)},{userInclude:d,userExclude:i,runOptions:n}):await e.evaluateAll(async(c,l)=>{let g=window,u={include:[...c,...l.userInclude??[]],exclude:l.userExclude??[]};return await g.axe.run(u,l.runOptions)},{userInclude:d,userExclude:i,runOptions:n})},C="BaselineUI-";function S(e){return e.replaceAll(/\W+/g,"-").replaceAll(/^-|-$/g,"")}function W(e){return Array.isArray(e)?e.length===1&&e[0]===C:e===C}function H(e){return (Array.isArray(e)?e:[e]).join("+")}function G(e,t,n,s,d){let i=t.slice(1).map(c=>S(c));return n&&i.push(S(n)),s!==void 0&&!W(s)&&i.push(S(H(s))),d&&i.push("ignoreHashed"),R.join(e,`${i.join("--")}.txt`)}var M=expect.extend({toBeWithinDelta(e,t,n){return Math.abs(e-t)<=n?{message:()=>`expected ${e} not to be within ${n} of ${t}`,pass:true}:{message:()=>`expected ${e} to be within ${n} of ${t}`,pass:false}},async toBeCalledOnce(e){try{return await expect.poll(()=>e.callCount).toBe(1),{message:()=>"Expected function to be called once.",pass:!0}}catch(t){return {message:()=>m(t),pass:false}}},async toBeCalledTwice(e){try{return await expect.poll(()=>e.callCount).toBe(2),{message:()=>"Expected function to be called twice.",pass:!0}}catch(t){return {message:()=>m(t),pass:false}}},async toBeCalledThrice(e){try{return await expect.poll(()=>e.callCount).toBe(3),{message:()=>"Expected function to be called thrice.",pass:!0}}catch(t){return {message:()=>m(t),pass:false}}},async toBeCalledWith(e,...t){try{return await expect(()=>{for(let[n,s]of t.entries())expect(e.lastCall?.args?.[n]).toStrictEqual(s);}).toPass(),{message:()=>`Expected function to be called with ${t.join(", ")}.`,pass:!0}}catch(n){return {message:()=>m(n),pass:false}}},async toBeCalledWithExactly(e,...t){try{return await expect.poll(()=>e.lastCall?.args).toStrictEqual(t),{message:()=>`Expected function to be called with exactly ${t.join(", ")}.`,pass:!0}}catch(n){return {message:()=>m(n),pass:false}}},toBeCalled(e){try{return expect(e.called).toBeTruthy(),{message:()=>"Expected function to be called.",pass:!0}}catch(t){return {message:()=>m(t),pass:false}}},async toBeCalledTimes(e,t){try{return await expect.poll(()=>e.callCount).toBe(t),{message:()=>`Expected function to be called ${t} times.`,pass:!0}}catch(n){return {message:()=>m(n),pass:false}}},async toBeAccessible(e,t={}){let n=await U(e),s=await n.first().evaluate(r=>{let o=r.ownerDocument?.defaultView;return o!==null&&o!==window.top}).catch(()=>false),d=N(t,s?k:[]),i,c=async()=>{let r=await v(n,t,d),o=t.treatIncompleteAsViolationIds??L,a=[...r.violations,...r.incomplete.filter(p=>o.includes(p.id))];return i={result:r,violations:a},a},l;try{if(t.timeout&&t.timeout>0){let r=expect.poll(c,{timeout:t.timeout});await(this.isNot?r.not.toHaveLength(0):r.toHaveLength(0));}else await c();}catch(r){l=r;}if(!i)throw new Error(`toBeAccessible: axe scan never completed.
13
- ${m(l)}`);if(i.violations.length===0)return {message:()=>"Expected accessibility violations but found none.",pass:true};this.isNot||await test.info().attach(t.attachmentName??"accessibility-scan-results",{body:JSON.stringify(i.result,null,2),contentType:"application/json"});let g=i.violations.map(r=>{let o=r.nodes.map(a=>Array.isArray(a.target)?a.target.join(" "):String(a.target)).join(`
14
- `);return ` \u2022 [${r.id}] ${r.help}
15
- ${o}
16
- ${r.helpUrl??""}`}).join(`
17
- `),u=i.violations.length;return {message:()=>`Found ${u} accessibility violation${u===1?"":"s"}:
18
- ${g}`,pass:false}},async toMatchClassTree(e,t,n={}){let{prefix:s=C,ignoreHashed:d=false}=n,i=await e.evaluate(T,{prefix:s,ignoreHashed:d});if(!i){let c=Array.isArray(s)?s.map(l=>`${l}*`).join(" / "):`${s}*`;return {message:()=>`No ${c} classes found in the DOM tree.`,pass:false}}try{let c=`${i}
19
- `,l=test.info();if(l.config.ignoreSnapshots)return {message:()=>"Class tree snapshot check skipped.",pass:!0};let u=G(l.project.snapshotDir,l.titlePath,t,s,d),r=l.config.updateSnapshots;await mkdir(R.dirname(u),{recursive:!0});let o;try{o=await readFile(u,"utf8");}catch(a){if(a.code==="ENOENT"&&r!=="none")return await writeFile(u,c),{message:()=>"Class tree snapshot created.",pass:!0};throw a}if(o!==c){if(r==="all"||r==="changed")return await writeFile(u,c),{message:()=>"Class tree snapshot updated.",pass:!0};try{expect(c).toBe(o);}catch(a){return {message:()=>`Class tree snapshot mismatch at ${u}
10
+ var b=["wcag2a","wcag2aa","wcag21a","wcag21aa","best-practice"],S=["landmark-one-main","page-has-heading-one","region","aria-allowed-role","duplicate-id","scrollable-region-focusable"],A=["aria-valid-attr-value","aria-valid-attr","aria-required-attr"];function O(e,t={}){let{prefix:s="BaselineUI-",ignoreHashed:n=false}=t,o=Array.isArray(s)?s:[s];function r(c){return c.split(/[-_]/).some(a=>a.length>=5&&/^[a-z\d]+$/.test(a)&&/[a-z]/.test(a)&&/\d/.test(a))}function l(c){return [...c.classList].filter(a=>o.some(d=>a.startsWith(d))&&(!n||!r(a)))}function u(c,a=false){let d=[],w=c.shadowRoot?c.shadowRoot.children:c.children;for(let h of w){let y=a||h.getAttribute("aria-hidden")==="true",x=l(h),T=u(h,y);x.length>0?d.push({classes:x,children:T,hidden:y}):d.push(...T);}return d}function f(c,a="",d=false){let w=[];for(let h=0;h<c.length;h++){let y=c[h],x=h===c.length-1,T=d?"":x?"\u2514\u2500\u2500 ":"\u251C\u2500\u2500 ",P=d?"":a+(x?" ":"\u2502 "),D=y.classes.join(", ");w.push(a+T+D+(y.hidden?" [aria-hidden]":""),...f(y.children,P));}return w}let p=l(e),i=u(e);return p.length===0&&i.length===0?"":p.length>0?f([{classes:p,children:i,hidden:false}],"",true).join(`
11
+ `):f(i,"",true).join(`
12
+ `)}var g=e=>{if(e instanceof Error)return e.message;if(typeof e=="string")return e;if(e==null)return "unknown error";try{return JSON.stringify(e)}catch{return "unstringifiable error"}},U=async e=>{let t=typeof e.goto=="function",s=!t&&typeof e.owner=="function";if(t||s)return e.locator("body");let n=e;return await n.first().evaluate(r=>r.tagName?.toLowerCase()==="iframe").catch(()=>false)?n.contentFrame().locator("body"):n},N=async e=>{await e.first().evaluate((t,s)=>{if(window.axe)return;let o=document.createElement("script");o.textContent=s,document.head.append(o);},$.source);},j=["document-title","html-has-lang","html-lang-valid","html-xml-lang-mismatch"],M=(e,t=[])=>{let s=new Set([...S,...t,...e.disableRules??[]]);return {runOnly:{type:"tag",values:[...e.tags??b]},rules:Object.fromEntries([...s].map(n=>[n,{enabled:false}]))}},F=e=>{if(e!==void 0)return typeof e=="string"?[e]:[...e]},W=async(e,t,s)=>{let n=await e.count();if(n===0)throw new Error("toBeAccessible: locator resolved to zero elements; nothing to scan.");await N(e);let o=F(t.include),r=F(t.exclude);return n===1?await e.evaluate(async(l,u)=>{let f=window,p={include:[l,...u.userInclude??[]],exclude:u.userExclude??[]};return await f.axe.run(p,u.runOptions)},{userInclude:o,userExclude:r,runOptions:s}):await e.evaluateAll(async(l,u)=>{let f=window,p={include:[...l,...u.userInclude??[]],exclude:u.userExclude??[]};return await f.axe.run(p,u.runOptions)},{userInclude:o,userExclude:r,runOptions:s})},L="BaselineUI-";function E(e){return e.replaceAll(/\W+/g,"-").replaceAll(/^-|-$/g,"")}function H(e){return Array.isArray(e)?e.length===1&&e[0]===L:e===L}function G(e){return (Array.isArray(e)?e:[e]).join("+")}function V(e,t,s,n,o){let r=t.slice(1).map(l=>E(l));return s&&r.push(E(s)),n!==void 0&&!H(n)&&r.push(E(G(n))),o&&r.push("ignoreHashed"),R.join(e,`${r.join("--")}.txt`)}var z=expect.extend({toBeWithinDelta(e,t,s){return Math.abs(e-t)<=s?{message:()=>`expected ${e} not to be within ${s} of ${t}`,pass:true}:{message:()=>`expected ${e} to be within ${s} of ${t}`,pass:false}},async toBeCalledOnce(e){try{return await expect.poll(()=>e.callCount).toBe(1),{message:()=>"Expected function to be called once.",pass:!0}}catch(t){return {message:()=>g(t),pass:false}}},async toBeCalledTwice(e){try{return await expect.poll(()=>e.callCount).toBe(2),{message:()=>"Expected function to be called twice.",pass:!0}}catch(t){return {message:()=>g(t),pass:false}}},async toBeCalledThrice(e){try{return await expect.poll(()=>e.callCount).toBe(3),{message:()=>"Expected function to be called thrice.",pass:!0}}catch(t){return {message:()=>g(t),pass:false}}},async toBeCalledWith(e,...t){try{return await expect(()=>{for(let[s,n]of t.entries())expect(e.lastCall?.args?.[s]).toStrictEqual(n);}).toPass(),{message:()=>`Expected function to be called with ${t.join(", ")}.`,pass:!0}}catch(s){return {message:()=>g(s),pass:false}}},async toBeCalledWithExactly(e,...t){try{return await expect.poll(()=>e.lastCall?.args).toStrictEqual(t),{message:()=>`Expected function to be called with exactly ${t.join(", ")}.`,pass:!0}}catch(s){return {message:()=>g(s),pass:false}}},toBeCalled(e){try{return expect(e.called).toBeTruthy(),{message:()=>"Expected function to be called.",pass:!0}}catch(t){return {message:()=>g(t),pass:false}}},async toBeCalledTimes(e,t){try{return await expect.poll(()=>e.callCount).toBe(t),{message:()=>`Expected function to be called ${t} times.`,pass:!0}}catch(s){return {message:()=>g(s),pass:false}}},async toBeAccessible(e,t={}){let s=await U(e),n=await s.first().evaluate(i=>{let c=i.ownerDocument?.defaultView;return c!==null&&c!==window.top}).catch(()=>false),o=M(t,n?j:[]),r,l=async()=>{let i=await W(s,t,o),c=t.treatIncompleteAsViolationIds??A,a=[...i.violations,...i.incomplete.filter(d=>c.includes(d.id))];return r={result:i,violations:a},a},u;try{if(t.timeout&&t.timeout>0){let i=expect.poll(l,{timeout:t.timeout});await(this.isNot?i.not.toHaveLength(0):i.toHaveLength(0));}else await l();}catch(i){u=i;}if(!r)throw new Error(`toBeAccessible: axe scan never completed.
13
+ ${g(u)}`);if(r.violations.length===0)return {message:()=>"Expected accessibility violations but found none.",pass:true};this.isNot||await test.info().attach(t.attachmentName??"accessibility-scan-results",{body:JSON.stringify(r.result,null,2),contentType:"application/json"});let f=r.violations.map(i=>{let c=i.nodes.map(a=>Array.isArray(a.target)?a.target.join(" "):String(a.target)).join(`
14
+ `);return ` \u2022 [${i.id}] ${i.help}
15
+ ${c}
16
+ ${i.helpUrl??""}`}).join(`
17
+ `),p=r.violations.length;return {message:()=>`Found ${p} accessibility violation${p===1?"":"s"}:
18
+ ${f}`,pass:false}},async toMatchClassTree(e,t,s={}){let{prefix:n=L,ignoreHashed:o=false}=s,r=await e.evaluate(O,{prefix:n,ignoreHashed:o});if(!r){let l=Array.isArray(n)?n.map(u=>`${u}*`).join(" / "):`${n}*`;return {message:()=>`No ${l} classes found in the DOM tree.`,pass:false}}try{let l=`${r}
19
+ `,u=test.info();if(u.config.ignoreSnapshots)return {message:()=>"Class tree snapshot check skipped.",pass:!0};let p=V(u.project.snapshotDir,u.titlePath,t,n,o),i=u.config.updateSnapshots;await mkdir(R.dirname(p),{recursive:!0});let c;try{c=await readFile(p,"utf8");}catch(a){if(a.code==="ENOENT"&&i!=="none")return await writeFile(p,l),{message:()=>"Class tree snapshot created.",pass:!0};throw a}if(c!==l){if(i==="all"||i==="changed")return await writeFile(p,l),{message:()=>"Class tree snapshot updated.",pass:!0};try{expect(l).toBe(c);}catch(a){return {message:()=>`Class tree snapshot mismatch at ${p}
20
20
 
21
- ${a.message}`,pass:!1}}}return {message:()=>"Class tree matched snapshot.",pass:!0}}catch(c){return {message:()=>m(c),pass:false}}}});export{L as DEFAULT_CRITICAL_INCOMPLETE_RULES,b as DEFAULT_DISABLED_RULES,A as DEFAULT_WCAG_TAGS,M as expect};
21
+ ${a.message}`,pass:!1}}}return {message:()=>"Class tree matched snapshot.",pass:!0}}catch(l){return {message:()=>g(l),pass:false}}}});function I(e){let t=async()=>{if(await e.getAttribute("aria-expanded")!==null)return e;let o=e.locator("[aria-expanded]").first();return await o.count()>0?o:e},s=async()=>await(await t()).getAttribute("aria-expanded")==="true";return {isOpen:s,open:async(o="click")=>{if(await s())return;if(o==="click"){await e.click();return}await(await t()).press(o);}}}var _=(e=>{let s=q(e);return e.multiple&&(s=J(s,e.frame)),e.autocomplete&&(s=K(s,e.frame)),s});function q({frame:e,trigger:t}){let s=t??e.getByTestId("select-button"),n=r=>e.getByRole("option",{name:r}),{open:o}=I(s);return {trigger:s,listbox:e.getByRole("listbox"),option:n,open:o,async selectOption(r){await o(),await n(r).click();}}}function J(e,t){return {...e,selectAllButton:t.getByTestId("select-all-button"),clearButton:t.getByTestId("select-clear-button"),tag:s=>e.trigger.getByRole("row",{name:s}),removeTagButton:s=>e.trigger.getByRole("row",{name:s}).getByRole("button")}}function K(e,t){let s=t.getByRole("searchbox");return {...e,searchbox:s,async filter(n){await s.fill(n);}}}function X(e){return {select:((t={})=>_({frame:e,...t}))}}export{A as DEFAULT_CRITICAL_INCOMPLETE_RULES,S as DEFAULT_DISABLED_RULES,b as DEFAULT_WCAG_TAGS,z as expect,X as testers};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baseline-ui/test-utils",
3
- "version": "0.60.1",
3
+ "version": "0.62.0",
4
4
  "description": "A collection of test utilities for Baseline UI",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -52,7 +52,7 @@
52
52
  "dependencies": {
53
53
  "axe-core": "^4.11.0",
54
54
  "react-frame-component": "^5.2.7",
55
- "@baseline-ui/core": "0.60.1"
55
+ "@baseline-ui/core": "0.62.0"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsup",