@baseline-ui/test-utils 1.2.0 → 2.1.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.
@@ -3,7 +3,7 @@ import { StylingProps, I18nProviderProps, ThemeProviderProps } from '@baseline-u
3
3
  import { FrameComponentProps } from 'react-frame-component';
4
4
 
5
5
  declare const IFrame: React.ForwardRefExoticComponent<Omit<IFrameProps, "ref"> & React.RefAttributes<HTMLIFrameElement>>;
6
- interface IFrameProps extends FrameComponentProps, StylingProps {
6
+ interface IFrameProps extends FrameComponentProps, Omit<React.IframeHTMLAttributes<HTMLIFrameElement>, keyof FrameComponentProps>, StylingProps {
7
7
  }
8
8
  declare const ShadowRoot: React.ForwardRefExoticComponent<ShadowRootProps & React.RefAttributes<HTMLDivElement>>;
9
9
  interface ShadowRootProps {
package/client/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { StylingProps, I18nProviderProps, ThemeProviderProps } from '@baseline-u
3
3
  import { FrameComponentProps } from 'react-frame-component';
4
4
 
5
5
  declare const IFrame: React.ForwardRefExoticComponent<Omit<IFrameProps, "ref"> & React.RefAttributes<HTMLIFrameElement>>;
6
- interface IFrameProps extends FrameComponentProps, StylingProps {
6
+ interface IFrameProps extends FrameComponentProps, Omit<React.IframeHTMLAttributes<HTMLIFrameElement>, keyof FrameComponentProps>, StylingProps {
7
7
  }
8
8
  declare const ShadowRoot: React.ForwardRefExoticComponent<ShadowRootProps & React.RefAttributes<HTMLDivElement>>;
9
9
  interface ShadowRootProps {
package/client/index.js CHANGED
@@ -1,4 +1,4 @@
1
- 'use strict';var core=require('@baseline-ui/core'),l=require('react'),b=require('react-dom/client'),x=require('react-frame-component'),jsxRuntime=require('react/jsx-runtime');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var l__default=/*#__PURE__*/_interopDefault(l);var b__default=/*#__PURE__*/_interopDefault(b);var x__default=/*#__PURE__*/_interopDefault(x);/**
1
+ 'use strict';var core=require('@baseline-ui/core'),m=require('react'),w=require('react-dom/client'),E=require('react-frame-component'),jsxRuntime=require('react/jsx-runtime');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var m__default=/*#__PURE__*/_interopDefault(m);var w__default=/*#__PURE__*/_interopDefault(w);var E__default=/*#__PURE__*/_interopDefault(E);/**
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
@@ -8,4 +8,4 @@
8
8
  *
9
9
  */
10
10
  function h(){let t=[],r=document;for(let o=0;o<r.styleSheets.length;o++){let n=r.styleSheets[o];if(n.cssRules){let i=n.cssRules;for(let s of i)t.push(s.cssText);}}return t.join(`
11
- `)}var E=l__default.default.forwardRef(({head:t=jsxRuntime.jsx("style",{children:h()}),...r},o)=>jsxRuntime.jsx(x__default.default,{style:{width:"100vw",height:"100vh",border:"none"},ref:o,...r,head:t}));E.displayName="IFrame";var T=l__default.default.forwardRef(({children:t,style:r},o)=>{let n=core.useObjectRef(o),i=l__default.default.useRef(null),s=l__default.default.useRef(null),d=l__default.default.useRef(null),m=l__default.default.useRef(false),P=l__default.default.useMemo(()=>h(),[]);return l.useEffect(()=>{if(m.current=false,n.current&&!i.current){let e=n.current.attachShadow({mode:"open"});i.current=e;let c=document.createElement("style");c.textContent=P,e.append(c);let u=document.createElement("div");u.className="content-container",e.append(u);let p=document.createElement("div");p.className="overlay-portal",e.append(p),d.current=p;let y=b__default.default.createRoot(u);s.current=y;}return ()=>{m.current=true;let e=s.current;e&&(queueMicrotask(()=>{e.unmount();}),s.current=null),i.current=null,d.current=null;}},[P,n]),l.useEffect(()=>{if(m.current)return;let e=s.current,c=d.current;e&&c&&e.render(jsxRuntime.jsx(core.PortalContainerProvider,{portalContainer:c,children:t}));},[t]),jsxRuntime.jsx("div",{style:r,ref:n})});T.displayName="ShadowRoot";var Q=({children:t,locale:r,theme:o,themeStyle:n,disableThemeProvider:i=false,disableFrameProvider:s=false,messages:d})=>{let[m,P]=l.useState(),{document:e}=x.useFrame(),c=r==="ar"||r==="he";l.useLayoutEffect(()=>{c?e?.documentElement.setAttribute("dir","rtl"):e?.documentElement.removeAttribute("dir");},[c,e]);let u=m?.ownerDocument?.body,p=[i?void 0:({children:a})=>jsxRuntime.jsx(core.ThemeProvider,{theme:o,style:n,children:a}),s?void 0:({children:a})=>jsxRuntime.jsx(core.FrameProvider,{container:u,portalContainer:m,children:a})].filter(Boolean);return jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx(core.I18nProvider,{locale:r,messages:d,children:jsxRuntime.jsx(({children:a})=>{let v=a;for(let C of [...p].toReversed())v=C?.({children:v});return v},{children:t})}),jsxRuntime.jsx("div",{id:"portal-container",ref:a=>{P(a??void 0);}})]})};exports.IFrame=E;exports.ProvidersContainer=Q;exports.ShadowRoot=T;exports.getRootStyles=h;
11
+ `)}var T=m__default.default.forwardRef(({head:t=jsxRuntime.jsx("style",{children:h()}),...r},o)=>jsxRuntime.jsx(E__default.default,{style:{width:"100vw",height:"100vh",border:"none"},ref:o,...r,head:t}));T.displayName="IFrame";var x=m__default.default.forwardRef(({children:t,style:r},o)=>{let n=core.useObjectRef(o),i=m__default.default.useRef(null),s=m__default.default.useRef(null),d=m__default.default.useRef(null),l=m__default.default.useRef(false),P=m__default.default.useMemo(()=>h(),[]);return m.useEffect(()=>{if(l.current=false,n.current&&!i.current){let e=n.current.attachShadow({mode:"open"});i.current=e;let a=document.createElement("style");a.textContent=P,e.append(a);let u=document.createElement("div");u.className="content-container",e.append(u);let p=document.createElement("div");p.className="overlay-portal",e.append(p),d.current=p;let y=w__default.default.createRoot(u);s.current=y;}return ()=>{l.current=true;let e=s.current;e&&(queueMicrotask(()=>{e.unmount();}),s.current=null),i.current=null,d.current=null;}},[P,n]),m.useEffect(()=>{if(l.current)return;let e=s.current,a=d.current;e&&a&&e.render(jsxRuntime.jsx(core.PortalContainerProvider,{portalContainer:a,children:t}));},[t]),jsxRuntime.jsx("div",{style:r,ref:n})});x.displayName="ShadowRoot";var Q=({children:t,locale:r,theme:o,themeStyle:n,disableThemeProvider:i=false,disableFrameProvider:s=false,messages:d})=>{let[l,P]=m.useState(),{document:e}=E.useFrame(),a=r==="ar"||r==="he";m.useLayoutEffect(()=>{a?e?.documentElement.setAttribute("dir","rtl"):e?.documentElement.removeAttribute("dir");},[a,e]);let u=l?.ownerDocument?.body,p=[i?void 0:({children:c})=>jsxRuntime.jsx(core.ThemeProvider,{theme:o,style:n,children:c}),s?void 0:({children:c})=>jsxRuntime.jsx(core.FrameProvider,{container:u,portalContainer:l,children:c})].filter(Boolean);return jsxRuntime.jsxs(jsxRuntime.Fragment,{children:[jsxRuntime.jsx(core.I18nProvider,{locale:r,messages:d,children:jsxRuntime.jsx(({children:c})=>{let v=c;for(let C of [...p].toReversed())v=C?.({children:v});return v},{children:t})}),jsxRuntime.jsx("div",{id:"portal-container",ref:c=>{P(c??void 0);}})]})};exports.IFrame=T;exports.ProvidersContainer=Q;exports.ShadowRoot=x;exports.getRootStyles=h;
package/client/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import {useObjectRef,PortalContainerProvider,I18nProvider,ThemeProvider,FrameProvider}from'@baseline-ui/core';import l,{useEffect,useState,useLayoutEffect}from'react';import b from'react-dom/client';import x,{useFrame}from'react-frame-component';import {jsx,jsxs,Fragment}from'react/jsx-runtime';/**
1
+ import {useObjectRef,PortalContainerProvider,I18nProvider,ThemeProvider,FrameProvider}from'@baseline-ui/core';import m,{useEffect,useState,useLayoutEffect}from'react';import w from'react-dom/client';import E,{useFrame}from'react-frame-component';import {jsx,jsxs,Fragment}from'react/jsx-runtime';/**
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
@@ -8,4 +8,4 @@ import {useObjectRef,PortalContainerProvider,I18nProvider,ThemeProvider,FramePro
8
8
  *
9
9
  */
10
10
  function h(){let t=[],r=document;for(let o=0;o<r.styleSheets.length;o++){let n=r.styleSheets[o];if(n.cssRules){let i=n.cssRules;for(let s of i)t.push(s.cssText);}}return t.join(`
11
- `)}var E=l.forwardRef(({head:t=jsx("style",{children:h()}),...r},o)=>jsx(x,{style:{width:"100vw",height:"100vh",border:"none"},ref:o,...r,head:t}));E.displayName="IFrame";var T=l.forwardRef(({children:t,style:r},o)=>{let n=useObjectRef(o),i=l.useRef(null),s=l.useRef(null),d=l.useRef(null),m=l.useRef(false),P=l.useMemo(()=>h(),[]);return useEffect(()=>{if(m.current=false,n.current&&!i.current){let e=n.current.attachShadow({mode:"open"});i.current=e;let c=document.createElement("style");c.textContent=P,e.append(c);let u=document.createElement("div");u.className="content-container",e.append(u);let p=document.createElement("div");p.className="overlay-portal",e.append(p),d.current=p;let y=b.createRoot(u);s.current=y;}return ()=>{m.current=true;let e=s.current;e&&(queueMicrotask(()=>{e.unmount();}),s.current=null),i.current=null,d.current=null;}},[P,n]),useEffect(()=>{if(m.current)return;let e=s.current,c=d.current;e&&c&&e.render(jsx(PortalContainerProvider,{portalContainer:c,children:t}));},[t]),jsx("div",{style:r,ref:n})});T.displayName="ShadowRoot";var Q=({children:t,locale:r,theme:o,themeStyle:n,disableThemeProvider:i=false,disableFrameProvider:s=false,messages:d})=>{let[m,P]=useState(),{document:e}=useFrame(),c=r==="ar"||r==="he";useLayoutEffect(()=>{c?e?.documentElement.setAttribute("dir","rtl"):e?.documentElement.removeAttribute("dir");},[c,e]);let u=m?.ownerDocument?.body,p=[i?void 0:({children:a})=>jsx(ThemeProvider,{theme:o,style:n,children:a}),s?void 0:({children:a})=>jsx(FrameProvider,{container:u,portalContainer:m,children:a})].filter(Boolean);return jsxs(Fragment,{children:[jsx(I18nProvider,{locale:r,messages:d,children:jsx(({children:a})=>{let v=a;for(let C of [...p].toReversed())v=C?.({children:v});return v},{children:t})}),jsx("div",{id:"portal-container",ref:a=>{P(a??void 0);}})]})};export{E as IFrame,Q as ProvidersContainer,T as ShadowRoot,h as getRootStyles};
11
+ `)}var T=m.forwardRef(({head:t=jsx("style",{children:h()}),...r},o)=>jsx(E,{style:{width:"100vw",height:"100vh",border:"none"},ref:o,...r,head:t}));T.displayName="IFrame";var x=m.forwardRef(({children:t,style:r},o)=>{let n=useObjectRef(o),i=m.useRef(null),s=m.useRef(null),d=m.useRef(null),l=m.useRef(false),P=m.useMemo(()=>h(),[]);return useEffect(()=>{if(l.current=false,n.current&&!i.current){let e=n.current.attachShadow({mode:"open"});i.current=e;let a=document.createElement("style");a.textContent=P,e.append(a);let u=document.createElement("div");u.className="content-container",e.append(u);let p=document.createElement("div");p.className="overlay-portal",e.append(p),d.current=p;let y=w.createRoot(u);s.current=y;}return ()=>{l.current=true;let e=s.current;e&&(queueMicrotask(()=>{e.unmount();}),s.current=null),i.current=null,d.current=null;}},[P,n]),useEffect(()=>{if(l.current)return;let e=s.current,a=d.current;e&&a&&e.render(jsx(PortalContainerProvider,{portalContainer:a,children:t}));},[t]),jsx("div",{style:r,ref:n})});x.displayName="ShadowRoot";var Q=({children:t,locale:r,theme:o,themeStyle:n,disableThemeProvider:i=false,disableFrameProvider:s=false,messages:d})=>{let[l,P]=useState(),{document:e}=useFrame(),a=r==="ar"||r==="he";useLayoutEffect(()=>{a?e?.documentElement.setAttribute("dir","rtl"):e?.documentElement.removeAttribute("dir");},[a,e]);let u=l?.ownerDocument?.body,p=[i?void 0:({children:c})=>jsx(ThemeProvider,{theme:o,style:n,children:c}),s?void 0:({children:c})=>jsx(FrameProvider,{container:u,portalContainer:l,children:c})].filter(Boolean);return jsxs(Fragment,{children:[jsx(I18nProvider,{locale:r,messages:d,children:jsx(({children:c})=>{let v=c;for(let C of [...p].toReversed())v=C?.({children:v});return v},{children:t})}),jsx("div",{id:"portal-container",ref:c=>{P(c??void 0);}})]})};export{T as IFrame,Q as ProvidersContainer,x as ShadowRoot,h as getRootStyles};
package/dist/index.d.mts CHANGED
@@ -133,11 +133,69 @@ interface BaseTesterOptions {
133
133
  /**
134
134
  * Keys recognised by {@link disclosure}'s `open(how)`. The same set covers
135
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")`.
136
+ * trigger expands a popover. `ArrowRight`/`ArrowLeft` additionally cover Menu
137
+ * submenu triggers (which expand toward the inline-end in LTR). Components that
138
+ * only open via click (e.g. a Dialog with no keyboard activation other than
139
+ * Enter) can ignore the keyboard paths and simply call `open("click")`.
139
140
  */
140
- type DisclosureOpenWith = "click" | "Space" | "Enter" | "ArrowDown" | "ArrowUp";
141
+ type DisclosureOpenWith = "click" | "Space" | "Enter" | "ArrowDown" | "ArrowUp" | "ArrowRight" | "ArrowLeft";
142
+
143
+ /**
144
+ * How to open a ComboBox from the keyboard. `Space` and `Enter` are excluded
145
+ * from {@link DisclosureOpenWith}: the input is a text field, so `Space` would
146
+ * type a character and `Enter` submits rather than opening.
147
+ */
148
+ type ComboBoxOpenWith = Extract<DisclosureOpenWith, "click" | "ArrowDown" | "ArrowUp">;
149
+ /**
150
+ * Options for {@link comboBoxTester}. Unlike Select, a ComboBox has two
151
+ * distinct interactive surfaces: the text input (which carries
152
+ * `aria-expanded`, so it is the disclosure host) and the chevron toggle
153
+ * button. Override both together when the consumer renders a custom trigger
154
+ * whose parts the defaults do not reach.
155
+ */
156
+ interface ComboBoxTesterOptions extends BaseTesterOptions {
157
+ /**
158
+ * The chevron button. Defaults to `data-testid="combobox-toggle-button"`.
159
+ * Pass this alongside `trigger` for a custom trigger — otherwise `open()`
160
+ * clicks a test id the custom markup does not render.
161
+ */
162
+ toggleButton?: Locator;
163
+ }
164
+ /**
165
+ * Driver for a `<ComboBox>` in Playwright component tests.
166
+ * Construct with {@link comboBoxTester}.
167
+ */
168
+ interface ComboBoxTester {
169
+ /** The text input. Located by `role="combobox"`, which carries `aria-expanded`. */
170
+ readonly input: Locator;
171
+ /** The chevron button that opens the popover. */
172
+ readonly toggleButton: Locator;
173
+ /** The popover listbox. Only attached while the ComboBox is open. */
174
+ readonly listbox: Locator;
175
+ /**
176
+ * Locator for a single option by accessible name. A string matches the
177
+ * full name exactly (case-sensitive, whitespace-trimmed); pass a RegExp
178
+ * for partial matching.
179
+ */
180
+ option: (name: string | RegExp) => Locator;
181
+ /** Whether the ComboBox reports `aria-expanded="true"`. */
182
+ isOpen: () => Promise<boolean>;
183
+ /**
184
+ * Opens the ComboBox if it isn't already open. Idempotent.
185
+ *
186
+ * @param how How to open. Defaults to `"click"`, which presses the toggle
187
+ * button. Arrow keys focus the input and press the key — matching how a
188
+ * keyboard user reaches the listbox.
189
+ */
190
+ open: (how?: ComboBoxOpenWith) => Promise<void>;
191
+ /** Opens the ComboBox (if needed) and clicks the option matching `name`. */
192
+ selectOption: (name: string | RegExp) => Promise<void>;
193
+ /**
194
+ * Types into the input to filter options, replacing any existing value.
195
+ * Opens the listbox as a side effect under the default `menuTrigger`.
196
+ */
197
+ filter: (text: string) => Promise<void>;
198
+ }
141
199
 
142
200
  /**
143
201
  * Options for {@link editorTester}. The Editor has no single disclosure
@@ -261,6 +319,71 @@ interface FileUploaderTester {
261
319
  upload: (files: Parameters<Locator["setInputFiles"]>[0]) => Promise<void>;
262
320
  }
263
321
 
322
+ /**
323
+ * Options for {@link menuTester}. Defaults the trigger to
324
+ * `frame.getByTestId("menu-trigger")` (the default `<Menu>` trigger). Pass
325
+ * `trigger` when the Menu renders a custom trigger via `renderTrigger` that
326
+ * doesn't carry that test id.
327
+ */
328
+ type MenuTesterOptions = BaseTesterOptions;
329
+ /**
330
+ * Locators + verbs shared by a menu and each of its submenus. A submenu is
331
+ * itself a menu opened from a `menuitem`, so the recursive surface (items,
332
+ * sections, nested submenus) is identical — only how you *open* it differs
333
+ * (root: click/keyboard the trigger button; submenu: hover/arrow the
334
+ * `menuitem` trigger). Both are modelled through {@link disclosure} on their
335
+ * respective `aria-expanded` trigger.
336
+ */
337
+ interface MenuLevelTester {
338
+ /** The `role="menu"` element for this level. Only attached while open. */
339
+ readonly menu: Locator;
340
+ /** A `menuitem` within this level, by accessible name. */
341
+ item: (name: string | RegExp) => Locator;
342
+ /** A `role="group"` section within this level, by accessible name. */
343
+ section: (name: string | RegExp) => Locator;
344
+ /** Whether this level's trigger reports `aria-expanded="true"`. */
345
+ isOpen: () => Promise<boolean>;
346
+ /**
347
+ * Scopes into a submenu opened from the `menuitem` matching `triggerName`.
348
+ * Returns a {@link MenuSubmenuTester} whose `item`/`section` are scoped to
349
+ * the nested menu, so identically-named items across levels don't collide.
350
+ */
351
+ submenu: (triggerName: string | RegExp) => MenuSubmenuTester;
352
+ }
353
+ /**
354
+ * Driver for a `<Menu>` in Playwright component tests. Construct via
355
+ * `testers(frame).menu()`.
356
+ */
357
+ interface MenuTester extends MenuLevelTester {
358
+ /** The trigger button that opens the root menu. */
359
+ readonly trigger: Locator;
360
+ /**
361
+ * Opens the root menu if it isn't already open. Idempotent (checks
362
+ * `aria-expanded`). Defaults to `"click"`.
363
+ */
364
+ open: (how?: DisclosureOpenWith) => Promise<void>;
365
+ /** Closes the menu if open, via `Escape`. Idempotent. */
366
+ close: () => Promise<void>;
367
+ /** Opens the root menu (if needed) and clicks the leaf item `name`. */
368
+ selectItem: (name: string | RegExp) => Promise<void>;
369
+ }
370
+ /**
371
+ * A submenu level. Adds the `menuitem` trigger and an `open()` that expands it
372
+ * (submenu triggers open on hover or `ArrowRight`/`Enter`).
373
+ */
374
+ interface MenuSubmenuTester extends MenuLevelTester {
375
+ /** The `menuitem` that expands this submenu. */
376
+ readonly trigger: Locator;
377
+ /**
378
+ * Expands this submenu if it isn't already open. Idempotent (checks the
379
+ * trigger's `aria-expanded`). Defaults to `"Enter"`, which opens the submenu
380
+ * in both LTR and RTL (the expand arrow is direction-dependent:
381
+ * `ArrowRight` in LTR, `ArrowLeft` in RTL). Pass `"click"` to activate by
382
+ * pointer, or an explicit arrow key to test direction-specific behavior.
383
+ */
384
+ open: (how?: DisclosureOpenWith) => Promise<void>;
385
+ }
386
+
264
387
  /**
265
388
  * Options for {@link selectTester}. Defaults the trigger to
266
389
  * `frame.getByTestId("select-button")` (the convention for both single- and
@@ -290,7 +413,11 @@ interface SelectTester {
290
413
  readonly trigger: Locator;
291
414
  /** The popover listbox. Only attached while the Select is open. */
292
415
  readonly listbox: Locator;
293
- /** Locator for a single option by accessible name. */
416
+ /**
417
+ * Locator for a single option by accessible name. A string matches the
418
+ * full name exactly (case-sensitive, whitespace-trimmed); pass a RegExp
419
+ * for partial matching.
420
+ */
294
421
  option: (name: string | RegExp) => Locator;
295
422
  /**
296
423
  * Opens the Select if it isn't already open. Idempotent — checks
@@ -394,8 +521,10 @@ interface SelectTesterFactoryFor<O extends {
394
521
  }
395
522
 
396
523
  type BoundSelectOptions = Omit<SelectTesterOptions, "frame">;
524
+ type BoundComboBoxOptions = Omit<ComboBoxTesterOptions, "frame">;
397
525
  type BoundEditorOptions = Omit<EditorTesterOptions, "frame">;
398
526
  type BoundFileUploaderOptions = Omit<FileUploaderTesterOptions, "frame">;
527
+ type BoundMenuOptions = Omit<MenuTesterOptions, "frame">;
399
528
  /**
400
529
  * The frame-bound tester factory. Methods are flat — one per component — so
401
530
  * IDE autocomplete on `t.` lists the full surface in one place. Variants of a
@@ -407,8 +536,10 @@ type BoundFileUploaderOptions = Omit<FileUploaderTesterOptions, "frame">;
407
536
  */
408
537
  interface Testers {
409
538
  select: SelectTesterFactoryFor<BoundSelectOptions>;
539
+ comboBox: (options?: BoundComboBoxOptions) => ComboBoxTester;
410
540
  editor: (options?: BoundEditorOptions) => EditorTester;
411
541
  fileUploader: (options?: BoundFileUploaderOptions) => FileUploaderTester;
542
+ menu: (options?: BoundMenuOptions) => MenuTester;
412
543
  }
413
544
  /**
414
545
  * Build a frame-bound tester factory. Construct once per test and pick the
@@ -426,4 +557,4 @@ interface Testers {
426
557
  */
427
558
  declare function testers(frame: Frame): Testers;
428
559
 
429
- export { type AutocompleteTester, DEFAULT_CRITICAL_INCOMPLETE_RULES, DEFAULT_DISABLED_RULES, DEFAULT_WCAG_TAGS, type EditorTester, type FileUploaderTester, type Frame, type MultiSelectTester, type SelectTester, type Testers, type ToBeAccessibleOptions, expect, testers };
560
+ export { type AutocompleteTester, type ComboBoxTester, DEFAULT_CRITICAL_INCOMPLETE_RULES, DEFAULT_DISABLED_RULES, DEFAULT_WCAG_TAGS, type EditorTester, type FileUploaderTester, type Frame, type MenuSubmenuTester, type MenuTester, type MultiSelectTester, type SelectTester, type Testers, type ToBeAccessibleOptions, expect, testers };
package/dist/index.d.ts CHANGED
@@ -133,11 +133,69 @@ interface BaseTesterOptions {
133
133
  /**
134
134
  * Keys recognised by {@link disclosure}'s `open(how)`. The same set covers
135
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")`.
136
+ * trigger expands a popover. `ArrowRight`/`ArrowLeft` additionally cover Menu
137
+ * submenu triggers (which expand toward the inline-end in LTR). Components that
138
+ * only open via click (e.g. a Dialog with no keyboard activation other than
139
+ * Enter) can ignore the keyboard paths and simply call `open("click")`.
139
140
  */
140
- type DisclosureOpenWith = "click" | "Space" | "Enter" | "ArrowDown" | "ArrowUp";
141
+ type DisclosureOpenWith = "click" | "Space" | "Enter" | "ArrowDown" | "ArrowUp" | "ArrowRight" | "ArrowLeft";
142
+
143
+ /**
144
+ * How to open a ComboBox from the keyboard. `Space` and `Enter` are excluded
145
+ * from {@link DisclosureOpenWith}: the input is a text field, so `Space` would
146
+ * type a character and `Enter` submits rather than opening.
147
+ */
148
+ type ComboBoxOpenWith = Extract<DisclosureOpenWith, "click" | "ArrowDown" | "ArrowUp">;
149
+ /**
150
+ * Options for {@link comboBoxTester}. Unlike Select, a ComboBox has two
151
+ * distinct interactive surfaces: the text input (which carries
152
+ * `aria-expanded`, so it is the disclosure host) and the chevron toggle
153
+ * button. Override both together when the consumer renders a custom trigger
154
+ * whose parts the defaults do not reach.
155
+ */
156
+ interface ComboBoxTesterOptions extends BaseTesterOptions {
157
+ /**
158
+ * The chevron button. Defaults to `data-testid="combobox-toggle-button"`.
159
+ * Pass this alongside `trigger` for a custom trigger — otherwise `open()`
160
+ * clicks a test id the custom markup does not render.
161
+ */
162
+ toggleButton?: Locator;
163
+ }
164
+ /**
165
+ * Driver for a `<ComboBox>` in Playwright component tests.
166
+ * Construct with {@link comboBoxTester}.
167
+ */
168
+ interface ComboBoxTester {
169
+ /** The text input. Located by `role="combobox"`, which carries `aria-expanded`. */
170
+ readonly input: Locator;
171
+ /** The chevron button that opens the popover. */
172
+ readonly toggleButton: Locator;
173
+ /** The popover listbox. Only attached while the ComboBox is open. */
174
+ readonly listbox: Locator;
175
+ /**
176
+ * Locator for a single option by accessible name. A string matches the
177
+ * full name exactly (case-sensitive, whitespace-trimmed); pass a RegExp
178
+ * for partial matching.
179
+ */
180
+ option: (name: string | RegExp) => Locator;
181
+ /** Whether the ComboBox reports `aria-expanded="true"`. */
182
+ isOpen: () => Promise<boolean>;
183
+ /**
184
+ * Opens the ComboBox if it isn't already open. Idempotent.
185
+ *
186
+ * @param how How to open. Defaults to `"click"`, which presses the toggle
187
+ * button. Arrow keys focus the input and press the key — matching how a
188
+ * keyboard user reaches the listbox.
189
+ */
190
+ open: (how?: ComboBoxOpenWith) => Promise<void>;
191
+ /** Opens the ComboBox (if needed) and clicks the option matching `name`. */
192
+ selectOption: (name: string | RegExp) => Promise<void>;
193
+ /**
194
+ * Types into the input to filter options, replacing any existing value.
195
+ * Opens the listbox as a side effect under the default `menuTrigger`.
196
+ */
197
+ filter: (text: string) => Promise<void>;
198
+ }
141
199
 
142
200
  /**
143
201
  * Options for {@link editorTester}. The Editor has no single disclosure
@@ -261,6 +319,71 @@ interface FileUploaderTester {
261
319
  upload: (files: Parameters<Locator["setInputFiles"]>[0]) => Promise<void>;
262
320
  }
263
321
 
322
+ /**
323
+ * Options for {@link menuTester}. Defaults the trigger to
324
+ * `frame.getByTestId("menu-trigger")` (the default `<Menu>` trigger). Pass
325
+ * `trigger` when the Menu renders a custom trigger via `renderTrigger` that
326
+ * doesn't carry that test id.
327
+ */
328
+ type MenuTesterOptions = BaseTesterOptions;
329
+ /**
330
+ * Locators + verbs shared by a menu and each of its submenus. A submenu is
331
+ * itself a menu opened from a `menuitem`, so the recursive surface (items,
332
+ * sections, nested submenus) is identical — only how you *open* it differs
333
+ * (root: click/keyboard the trigger button; submenu: hover/arrow the
334
+ * `menuitem` trigger). Both are modelled through {@link disclosure} on their
335
+ * respective `aria-expanded` trigger.
336
+ */
337
+ interface MenuLevelTester {
338
+ /** The `role="menu"` element for this level. Only attached while open. */
339
+ readonly menu: Locator;
340
+ /** A `menuitem` within this level, by accessible name. */
341
+ item: (name: string | RegExp) => Locator;
342
+ /** A `role="group"` section within this level, by accessible name. */
343
+ section: (name: string | RegExp) => Locator;
344
+ /** Whether this level's trigger reports `aria-expanded="true"`. */
345
+ isOpen: () => Promise<boolean>;
346
+ /**
347
+ * Scopes into a submenu opened from the `menuitem` matching `triggerName`.
348
+ * Returns a {@link MenuSubmenuTester} whose `item`/`section` are scoped to
349
+ * the nested menu, so identically-named items across levels don't collide.
350
+ */
351
+ submenu: (triggerName: string | RegExp) => MenuSubmenuTester;
352
+ }
353
+ /**
354
+ * Driver for a `<Menu>` in Playwright component tests. Construct via
355
+ * `testers(frame).menu()`.
356
+ */
357
+ interface MenuTester extends MenuLevelTester {
358
+ /** The trigger button that opens the root menu. */
359
+ readonly trigger: Locator;
360
+ /**
361
+ * Opens the root menu if it isn't already open. Idempotent (checks
362
+ * `aria-expanded`). Defaults to `"click"`.
363
+ */
364
+ open: (how?: DisclosureOpenWith) => Promise<void>;
365
+ /** Closes the menu if open, via `Escape`. Idempotent. */
366
+ close: () => Promise<void>;
367
+ /** Opens the root menu (if needed) and clicks the leaf item `name`. */
368
+ selectItem: (name: string | RegExp) => Promise<void>;
369
+ }
370
+ /**
371
+ * A submenu level. Adds the `menuitem` trigger and an `open()` that expands it
372
+ * (submenu triggers open on hover or `ArrowRight`/`Enter`).
373
+ */
374
+ interface MenuSubmenuTester extends MenuLevelTester {
375
+ /** The `menuitem` that expands this submenu. */
376
+ readonly trigger: Locator;
377
+ /**
378
+ * Expands this submenu if it isn't already open. Idempotent (checks the
379
+ * trigger's `aria-expanded`). Defaults to `"Enter"`, which opens the submenu
380
+ * in both LTR and RTL (the expand arrow is direction-dependent:
381
+ * `ArrowRight` in LTR, `ArrowLeft` in RTL). Pass `"click"` to activate by
382
+ * pointer, or an explicit arrow key to test direction-specific behavior.
383
+ */
384
+ open: (how?: DisclosureOpenWith) => Promise<void>;
385
+ }
386
+
264
387
  /**
265
388
  * Options for {@link selectTester}. Defaults the trigger to
266
389
  * `frame.getByTestId("select-button")` (the convention for both single- and
@@ -290,7 +413,11 @@ interface SelectTester {
290
413
  readonly trigger: Locator;
291
414
  /** The popover listbox. Only attached while the Select is open. */
292
415
  readonly listbox: Locator;
293
- /** Locator for a single option by accessible name. */
416
+ /**
417
+ * Locator for a single option by accessible name. A string matches the
418
+ * full name exactly (case-sensitive, whitespace-trimmed); pass a RegExp
419
+ * for partial matching.
420
+ */
294
421
  option: (name: string | RegExp) => Locator;
295
422
  /**
296
423
  * Opens the Select if it isn't already open. Idempotent — checks
@@ -394,8 +521,10 @@ interface SelectTesterFactoryFor<O extends {
394
521
  }
395
522
 
396
523
  type BoundSelectOptions = Omit<SelectTesterOptions, "frame">;
524
+ type BoundComboBoxOptions = Omit<ComboBoxTesterOptions, "frame">;
397
525
  type BoundEditorOptions = Omit<EditorTesterOptions, "frame">;
398
526
  type BoundFileUploaderOptions = Omit<FileUploaderTesterOptions, "frame">;
527
+ type BoundMenuOptions = Omit<MenuTesterOptions, "frame">;
399
528
  /**
400
529
  * The frame-bound tester factory. Methods are flat — one per component — so
401
530
  * IDE autocomplete on `t.` lists the full surface in one place. Variants of a
@@ -407,8 +536,10 @@ type BoundFileUploaderOptions = Omit<FileUploaderTesterOptions, "frame">;
407
536
  */
408
537
  interface Testers {
409
538
  select: SelectTesterFactoryFor<BoundSelectOptions>;
539
+ comboBox: (options?: BoundComboBoxOptions) => ComboBoxTester;
410
540
  editor: (options?: BoundEditorOptions) => EditorTester;
411
541
  fileUploader: (options?: BoundFileUploaderOptions) => FileUploaderTester;
542
+ menu: (options?: BoundMenuOptions) => MenuTester;
412
543
  }
413
544
  /**
414
545
  * Build a frame-bound tester factory. Construct once per test and pick the
@@ -426,4 +557,4 @@ interface Testers {
426
557
  */
427
558
  declare function testers(frame: Frame): Testers;
428
559
 
429
- export { type AutocompleteTester, DEFAULT_CRITICAL_INCOMPLETE_RULES, DEFAULT_DISABLED_RULES, DEFAULT_WCAG_TAGS, type EditorTester, type FileUploaderTester, type Frame, type MultiSelectTester, type SelectTester, type Testers, type ToBeAccessibleOptions, expect, testers };
560
+ export { type AutocompleteTester, type ComboBoxTester, DEFAULT_CRITICAL_INCOMPLETE_RULES, DEFAULT_DISABLED_RULES, DEFAULT_WCAG_TAGS, type EditorTester, type FileUploaderTester, type Frame, type MenuSubmenuTester, type MenuTester, 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'),_=require('axe-core'),promises=require('fs/promises'),C=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var ___default=/*#__PURE__*/_interopDefault(_);var C__default=/*#__PURE__*/_interopDefault(C);/**
1
+ 'use strict';var test=require('@playwright/test'),W=require('axe-core'),promises=require('fs/promises'),I=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var W__default=/*#__PURE__*/_interopDefault(W);var I__default=/*#__PURE__*/_interopDefault(I);/**
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 w=["wcag2a","wcag2aa","wcag21a","wcag21aa","best-practice"],L=["landmark-one-main","page-has-heading-one","region","aria-allowed-role","duplicate-id","scrollable-region-focusable"],E=["aria-valid-attr-value","aria-valid-attr","aria-required-attr"];function A(e,o={}){let{prefix:t="BaselineUI-",ignoreHashed:n=false}=o,r=Array.isArray(t)?t:[t];function s(c){return c.split(/[-_]/).some(i=>i.length>=5&&/^[a-z\d]+$/.test(i)&&/[a-z]/.test(i)&&/\d/.test(i))}function l(c){return [...c.classList].filter(i=>r.some(g=>i.startsWith(g))&&(!n||!s(i)))}function u(c,i=false){let g=[],b=c.shadowRoot?c.shadowRoot.children:c.children;for(let f of b){let h=i||f.getAttribute("aria-hidden")==="true",B=l(f),x=u(f,h);B.length>0?g.push({classes:B,children:x,hidden:h}):g.push(...x);}return g}function d(c,i="",g=false){let b=[];for(let f=0;f<c.length;f++){let h=c[f],B=f===c.length-1,x=g?"":B?"\u2514\u2500\u2500 ":"\u251C\u2500\u2500 ",P=g?"":i+(B?" ":"\u2502 "),U=h.classes.join(", ");b.push(i+x+U+(h.hidden?" [aria-hidden]":""),...d(h.children,P));}return b}let p=l(e),a=u(e);return p.length===0&&a.length===0?"":p.length>0?d([{classes:p,children:a,hidden:false}],"",true).join(`
11
- `):d(a,"",true).join(`
12
- `)}var y=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"}},j=async e=>{let o=typeof e.goto=="function",t=!o&&typeof e.owner=="function";if(o||t)return e.locator("body");let n=e;return await n.first().evaluate(s=>s.tagName?.toLowerCase()==="iframe").catch(()=>false)?n.contentFrame().locator("body"):n},M=async e=>{await e.first().evaluate((o,t)=>{if(window.axe)return;let r=document.createElement("script");r.textContent=t,document.head.append(r);},___default.default.source);},W=["document-title","html-has-lang","html-lang-valid","html-xml-lang-mismatch"],z=(e,o=[])=>{let t=new Set([...L,...o,...e.disableRules??[]]);return {runOnly:{type:"tag",values:[...e.tags??w]},rules:Object.fromEntries([...t].map(n=>[n,{enabled:false}]))}},F=e=>{if(e!==void 0)return typeof e=="string"?[e]:[...e]},H=async(e,o,t)=>{let n=await e.count();if(n===0)throw new Error("toBeAccessible: locator resolved to zero elements; nothing to scan.");await M(e);let r=F(o.include),s=F(o.exclude);return n===1?await e.evaluate(async(l,u)=>{let d=window,p={include:[l,...u.userInclude??[]],exclude:u.userExclude??[]};return await d.axe.run(p,u.runOptions)},{userInclude:r,userExclude:s,runOptions:t}):await e.evaluateAll(async(l,u)=>{let d=window,p={include:[...l,...u.userInclude??[]],exclude:u.userExclude??[]};return await d.axe.run(p,u.runOptions)},{userInclude:r,userExclude:s,runOptions:t})},O="BaselineUI-";function S(e){return e.replaceAll(/\W+/g,"-").replaceAll(/^-|-$/g,"")}function V(e){return Array.isArray(e)?e.length===1&&e[0]===O:e===O}function G(e){return (Array.isArray(e)?e:[e]).join("+")}function q(e,o,t,n,r){let s=o.slice(1).map(l=>S(l));return t&&s.push(S(t)),n!==void 0&&!V(n)&&s.push(S(G(n))),r&&s.push("ignoreHashed"),C__default.default.join(e,`${s.join("--")}.txt`)}var J=test.expect.extend({toBeWithinDelta(e,o,t){return Math.abs(e-o)<=t?{message:()=>`expected ${e} not to be within ${t} of ${o}`,pass:true}:{message:()=>`expected ${e} to be within ${t} of ${o}`,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(o){return {message:()=>y(o),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(o){return {message:()=>y(o),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(o){return {message:()=>y(o),pass:false}}},async toBeCalledWith(e,...o){try{return await test.expect(()=>{for(let[t,n]of o.entries())test.expect(e.lastCall?.args?.[t]).toStrictEqual(n);}).toPass(),{message:()=>`Expected function to be called with ${o.join(", ")}.`,pass:!0}}catch(t){return {message:()=>y(t),pass:false}}},async toBeCalledWithExactly(e,...o){try{return await test.expect.poll(()=>e.lastCall?.args).toStrictEqual(o),{message:()=>`Expected function to be called with exactly ${o.join(", ")}.`,pass:!0}}catch(t){return {message:()=>y(t),pass:false}}},toBeCalled(e){try{return test.expect(e.called).toBeTruthy(),{message:()=>"Expected function to be called.",pass:!0}}catch(o){return {message:()=>y(o),pass:false}}},async toBeCalledTimes(e,o){try{return await test.expect.poll(()=>e.callCount).toBe(o),{message:()=>`Expected function to be called ${o} times.`,pass:!0}}catch(t){return {message:()=>y(t),pass:false}}},async toBeAccessible(e,o={}){let t=await j(e),n=await t.first().evaluate(a=>{let c=a.ownerDocument?.defaultView;return c!==null&&c!==window.top}).catch(()=>false),r=z(o,n?W:[]),s,l=async()=>{let a=await H(t,o,r),c=o.treatIncompleteAsViolationIds??E,i=[...a.violations,...a.incomplete.filter(g=>c.includes(g.id))];return s={result:a,violations:i},i},u;try{if(o.timeout&&o.timeout>0){let a=test.expect.poll(l,{timeout:o.timeout});await(this.isNot?a.not.toHaveLength(0):a.toHaveLength(0));}else await l();}catch(a){u=a;}if(!s)throw new Error(`toBeAccessible: axe scan never completed.
13
- ${y(u)}`);if(s.violations.length===0)return {message:()=>"Expected accessibility violations but found none.",pass:true};this.isNot||await test.test.info().attach(o.attachmentName??"accessibility-scan-results",{body:JSON.stringify(s.result,null,2),contentType:"application/json"});let d=s.violations.map(a=>{let c=a.nodes.map(i=>Array.isArray(i.target)?i.target.join(" "):String(i.target)).join(`
14
- `);return ` \u2022 [${a.id}] ${a.help}
15
- ${c}
16
- ${a.helpUrl??""}`}).join(`
17
- `),p=s.violations.length;return {message:()=>`Found ${p} accessibility violation${p===1?"":"s"}:
18
- ${d}`,pass:false}},async toMatchClassTree(e,o,t={}){let{prefix:n=O,ignoreHashed:r=false}=t,s=await e.evaluate(A,{prefix:n,ignoreHashed:r});if(!s){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=`${s}
19
- `,u=test.test.info();if(u.config.ignoreSnapshots)return {message:()=>"Class tree snapshot check skipped.",pass:!0};let p=q(u.project.snapshotDir,u.titlePath,o,n,r),a=u.config.updateSnapshots;await promises.mkdir(C__default.default.dirname(p),{recursive:!0});let c;try{c=await promises.readFile(p,"utf8");}catch(i){if(i.code==="ENOENT"&&a!=="none")return await promises.writeFile(p,l),{message:()=>"Class tree snapshot created.",pass:!0};throw i}if(c!==l){if(a==="all"||a==="changed")return await promises.writeFile(p,l),{message:()=>"Class tree snapshot updated.",pass:!0};try{test.expect(l).toBe(c);}catch(i){return {message:()=>`Class tree snapshot mismatch at ${p}
10
+ var w=["wcag2a","wcag2aa","wcag21a","wcag21aa","best-practice"],L=["landmark-one-main","page-has-heading-one","region","aria-allowed-role","duplicate-id","scrollable-region-focusable"],O=["aria-valid-attr-value","aria-valid-attr","aria-required-attr"];function A(e,o={}){let{prefix:t="BaselineUI-",ignoreHashed:n=false}=o,s=Array.isArray(t)?t:[t];function r(u){return u.split(/[-_]/).some(p=>p.length>=5&&/^[a-z\d]+$/.test(p)&&/[a-z]/.test(p)&&/\d/.test(p))}function i(u){return [...u.classList].filter(p=>s.some(m=>p.startsWith(m))&&(!n||!r(p)))}function a(u,p=false){let m=[],h=u.shadowRoot?u.shadowRoot.children:u.children;for(let T of h){let x=p||T.getAttribute("aria-hidden")==="true",B=i(T),b=a(T,x);B.length>0?m.push({classes:B,children:b,hidden:x}):m.push(...b);}return m}function d(u,p="",m=false){let h=[];for(let T=0;T<u.length;T++){let x=u[T],B=T===u.length-1,b=m?"":B?"\u2514\u2500\u2500 ":"\u251C\u2500\u2500 ",_=m?"":p+(B?" ":"\u2502 "),$=x.classes.join(", ");h.push(p+b+$+(x.hidden?" [aria-hidden]":""),...d(x.children,_));}return h}let l=i(e),c=a(e);return l.length===0&&c.length===0?"":l.length>0?d([{classes:l,children:c,hidden:false}],"",true).join(`
11
+ `):d(c,"",true).join(`
12
+ `)}var f=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"}},H=async e=>{let o=typeof e.goto=="function",t=!o&&typeof e.owner=="function";if(o||t)return e.locator("body");let n=e;return await n.first().evaluate(r=>r.tagName?.toLowerCase()==="iframe").catch(()=>false)?n.contentFrame().locator("body"):n},z=async e=>{await e.first().evaluate((o,t)=>{if(window.axe)return;let s=document.createElement("script");s.textContent=t,document.head.append(s);},W__default.default.source);},V=["document-title","html-has-lang","html-lang-valid","html-xml-lang-mismatch"],G=(e,o=[])=>{let t=new Set([...L,...o,...e.disableRules??[]]);return {runOnly:{type:"tag",values:[...e.tags??w]},rules:Object.fromEntries([...t].map(n=>[n,{enabled:false}]))}},k=e=>{if(e!==void 0)return typeof e=="string"?[e]:[...e]},q=async(e,o,t)=>{let n=await e.count();if(n===0)throw new Error("toBeAccessible: locator resolved to zero elements; nothing to scan.");await z(e);let s=k(o.include),r=k(o.exclude);return n===1?await e.evaluate(async(i,a)=>{let d=window,l={include:[i,...a.userInclude??[]],exclude:a.userExclude??[]};return await d.axe.run(l,a.runOptions)},{userInclude:s,userExclude:r,runOptions:t}):await e.evaluateAll(async(i,a)=>{let d=window,l={include:[...i,...a.userInclude??[]],exclude:a.userExclude??[]};return await d.axe.run(l,a.runOptions)},{userInclude:s,userExclude:r,runOptions:t})},S="BaselineUI-";function E(e){return e.replaceAll(/\W+/g,"-").replaceAll(/^-|-$/g,"")}function J(e){return Array.isArray(e)?e.length===1&&e[0]===S:e===S}function K(e){return (Array.isArray(e)?e:[e]).join("+")}function X(e,o,t,n,s){let r=o.slice(1).map(i=>E(i));return t&&r.push(E(t)),n!==void 0&&!J(n)&&r.push(E(K(n))),s&&r.push("ignoreHashed"),I__default.default.join(e,`${r.join("--")}.txt`)}var Q=test.expect.extend({toBeWithinDelta(e,o,t){return Math.abs(e-o)<=t?{message:()=>`expected ${e} not to be within ${t} of ${o}`,pass:true}:{message:()=>`expected ${e} to be within ${t} of ${o}`,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(o){return {message:()=>f(o),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(o){return {message:()=>f(o),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(o){return {message:()=>f(o),pass:false}}},async toBeCalledWith(e,...o){try{return await test.expect(()=>{for(let[t,n]of o.entries())test.expect(e.lastCall?.args?.[t]).toStrictEqual(n);}).toPass(),{message:()=>`Expected function to be called with ${o.join(", ")}.`,pass:!0}}catch(t){return {message:()=>f(t),pass:false}}},async toBeCalledWithExactly(e,...o){try{return await test.expect.poll(()=>e.lastCall?.args).toStrictEqual(o),{message:()=>`Expected function to be called with exactly ${o.join(", ")}.`,pass:!0}}catch(t){return {message:()=>f(t),pass:false}}},toBeCalled(e){try{return test.expect(e.called).toBeTruthy(),{message:()=>"Expected function to be called.",pass:!0}}catch(o){return {message:()=>f(o),pass:false}}},async toBeCalledTimes(e,o){try{return await test.expect.poll(()=>e.callCount).toBe(o),{message:()=>`Expected function to be called ${o} times.`,pass:!0}}catch(t){return {message:()=>f(t),pass:false}}},async toBeAccessible(e,o={}){let t=await H(e),n=await t.first().evaluate(c=>{let u=c.ownerDocument?.defaultView;return u!==null&&u!==window.top}).catch(()=>false),s=G(o,n?V:[]),r,i=async()=>{let c=await q(t,o,s),u=o.treatIncompleteAsViolationIds??O,p=[...c.violations,...c.incomplete.filter(m=>u.includes(m.id))];return r={result:c,violations:p},p},a;try{if(o.timeout&&o.timeout>0){let c=test.expect.poll(i,{timeout:o.timeout});await(this.isNot?c.not.toHaveLength(0):c.toHaveLength(0));}else await i();}catch(c){a=c;}if(!r)throw new Error(`toBeAccessible: axe scan never completed.
13
+ ${f(a)}`);if(r.violations.length===0)return {message:()=>"Expected accessibility violations but found none.",pass:true};this.isNot||await test.test.info().attach(o.attachmentName??"accessibility-scan-results",{body:JSON.stringify(r.result,null,2),contentType:"application/json"});let d=r.violations.map(c=>{let u=c.nodes.map(p=>Array.isArray(p.target)?p.target.join(" "):String(p.target)).join(`
14
+ `);return ` \u2022 [${c.id}] ${c.help}
15
+ ${u}
16
+ ${c.helpUrl??""}`}).join(`
17
+ `),l=r.violations.length;return {message:()=>`Found ${l} accessibility violation${l===1?"":"s"}:
18
+ ${d}`,pass:false}},async toMatchClassTree(e,o,t={}){let{prefix:n=S,ignoreHashed:s=false}=t,r=await e.evaluate(A,{prefix:n,ignoreHashed:s});if(!r){let i=Array.isArray(n)?n.map(a=>`${a}*`).join(" / "):`${n}*`;return {message:()=>`No ${i} classes found in the DOM tree.`,pass:false}}try{let i=`${r}
19
+ `,a=test.test.info();if(a.config.ignoreSnapshots)return {message:()=>"Class tree snapshot check skipped.",pass:!0};let l=X(a.project.snapshotDir,a.titlePath,o,n,s),c=a.config.updateSnapshots;await promises.mkdir(I__default.default.dirname(l),{recursive:!0});let u;try{u=await promises.readFile(l,"utf8");}catch(p){if(p.code==="ENOENT"&&c!=="none")return await promises.writeFile(l,i),{message:()=>"Class tree snapshot created.",pass:!0};throw p}if(u!==i){if(c==="all"||c==="changed")return await promises.writeFile(l,i),{message:()=>"Class tree snapshot updated.",pass:!0};try{test.expect(i).toBe(u);}catch(p){return {message:()=>`Class tree snapshot mismatch at ${l}
20
20
 
21
- ${i.message}`,pass:!1}}}return {message:()=>"Class tree matched snapshot.",pass:!0}}catch(l){return {message:()=>y(l),pass:false}}}});function T(e){let o=async()=>{if(await e.getAttribute("aria-expanded")!==null)return e;let r=e.locator("[aria-expanded]").first();return await r.count()>0?r:e},t=async()=>await(await o()).getAttribute("aria-expanded")==="true";return {isOpen:t,open:async(r="click")=>{if(await t())return;if(r==="click"){await e.click();return}await(await o()).press(r);}}}function R({frame:e,root:o}){let t=o??e.locator(".BaselineUI-Editor"),n=t.getByTestId("editor-font-color-button"),r=t.getByTestId("editor-background-color-button"),s=t.getByTestId("editor-add-link-button"),l=t.getByTestId("editor-help-button"),u=t.getByTestId("editor-mention-button"),d=t.getByRole("combobox"),p=e.getByTestId("editor-mention-listbox"),a=e.getByTestId("editor-help-dialog-close-button"),c=e.getByRole("dialog").filter({has:a}),i=e.getByRole("dialog").filter({has:e.getByTestId(/^editor-link-/)});return {root:t,textbox:t.getByRole("textbox"),placeholder:t.getByTestId("editor-placeholder"),toolbar:t.getByRole("toolbar"),boldButton:t.getByTestId("editor-bold-button"),italicButton:t.getByTestId("editor-italic-button"),underlineButton:t.getByTestId("editor-underline-button"),fontColorButton:n,backgroundColorButton:r,openFontColorPicker:T(n).open,openBackgroundColorPicker:T(r).open,addLinkButton:s,linkDialog:i,linkInput:e.getByTestId("editor-link-input"),linkAddButton:e.getByTestId("editor-link-add-button"),linkEditButton:e.getByTestId("editor-link-edit-button"),linkRemoveButton:e.getByTestId("editor-link-remove-button"),linkCancelButton:e.getByTestId("editor-link-cancel-button"),linkSaveButton:e.getByTestId("editor-link-save-button"),openLinkDialog:T(s).open,saveButton:t.getByTestId("editor-save-button"),cancelButton:t.getByTestId("editor-cancel-button"),mentionButton:u,helpButton:l,helpDialog:c,helpDialogCloseButton:a,async openHelpDialog(){await c.isVisible()||await l.click();},mentionInput:d,mentionListbox:p,mentionOption:g=>p.getByRole("option",{name:g}),async startMention(){await d.isVisible()||await u.click();}}}function D({frame:e,trigger:o,variant:t="dropzone"}){let n=e.getByTestId("file-uploader"),r=n.locator('input[type="file"]'),s=o??(t==="button"?n.getByRole("button"):e.getByTestId("file-uploader-dropzone"));return {root:n,button:s,input:r,dropzone:e.getByTestId("file-uploader-dropzone"),errorMessage:n.getByRole("alert"),async upload(l){await r.setInputFiles(l);}}}var v=(e=>{let t=K(e);return e.multiple&&(t=X(t,e.frame)),e.autocomplete&&(t=Q(t,e.frame)),t});function K({frame:e,trigger:o}){let t=o??e.getByTestId("select-button"),n=s=>e.getByRole("option",{name:s}),{open:r}=T(t);return {trigger:t,listbox:e.getByRole("listbox"),option:n,open:r,async selectOption(s){await r(),await n(s).click();}}}function X(e,o){return {...e,selectAllButton:o.getByTestId("select-all-button"),clearButton:o.getByTestId("select-clear-button"),tag:t=>e.trigger.getByRole("row",{name:t}),removeTagButton:t=>e.trigger.getByRole("row",{name:t}).getByRole("button")}}function Q(e,o){let t=o.getByRole("searchbox");return {...e,searchbox:t,async filter(n){await t.fill(n);}}}function Y(e){return {select:((o={})=>v({frame:e,...o})),editor:(o={})=>R({frame:e,...o}),fileUploader:(o={})=>D({frame:e,...o})}}exports.DEFAULT_CRITICAL_INCOMPLETE_RULES=E;exports.DEFAULT_DISABLED_RULES=L;exports.DEFAULT_WCAG_TAGS=w;exports.expect=J;exports.testers=Y;
21
+ ${p.message}`,pass:!1}}}return {message:()=>"Class tree matched snapshot.",pass:!0}}catch(i){return {message:()=>f(i),pass:false}}}});function g(e){let o=async()=>{if(await e.getAttribute("aria-expanded")!==null)return e;let s=e.locator("[aria-expanded]").first();return await s.count()>0?s:e},t=async()=>await(await o()).getAttribute("aria-expanded")==="true";return {isOpen:t,open:async(s="click")=>{if(await t())return;if(s==="click"){await e.click();return}await(await o()).press(s);}}}function F({frame:e,trigger:o,toggleButton:t}){let n=o??e.getByRole("combobox",{includeHidden:true}),s=t??e.getByTestId("combobox-toggle-button"),r=l=>e.getByRole("option",{name:l,exact:typeof l=="string"}),{isOpen:i,open:a}=g(n),d=async(l="click")=>{if(!await i()){if(l==="click"){await s.click();return}await a(l);}};return {input:n,toggleButton:s,listbox:e.getByRole("listbox"),option:r,isOpen:i,open:d,async selectOption(l){await d(),await r(l).click();},async filter(l){await n.fill(l);}}}function v({frame:e,root:o}){let t=o??e.locator(".BaselineUI-Editor"),n=t.getByTestId("editor-font-color-button"),s=t.getByTestId("editor-background-color-button"),r=t.getByTestId("editor-add-link-button"),i=t.getByTestId("editor-help-button"),a=t.getByTestId("editor-mention-button"),d=t.getByRole("combobox"),l=e.getByTestId("editor-mention-listbox"),c=e.getByTestId("editor-help-dialog-close-button"),u=e.getByRole("dialog").filter({has:c}),p=e.getByRole("dialog").filter({has:e.getByTestId(/^editor-link-/)});return {root:t,textbox:t.getByRole("textbox"),placeholder:t.getByTestId("editor-placeholder"),toolbar:t.getByRole("toolbar"),boldButton:t.getByTestId("editor-bold-button"),italicButton:t.getByTestId("editor-italic-button"),underlineButton:t.getByTestId("editor-underline-button"),fontColorButton:n,backgroundColorButton:s,openFontColorPicker:g(n).open,openBackgroundColorPicker:g(s).open,addLinkButton:r,linkDialog:p,linkInput:e.getByTestId("editor-link-input"),linkAddButton:e.getByTestId("editor-link-add-button"),linkEditButton:e.getByTestId("editor-link-edit-button"),linkRemoveButton:e.getByTestId("editor-link-remove-button"),linkCancelButton:e.getByTestId("editor-link-cancel-button"),linkSaveButton:e.getByTestId("editor-link-save-button"),openLinkDialog:g(r).open,saveButton:t.getByTestId("editor-save-button"),cancelButton:t.getByTestId("editor-cancel-button"),mentionButton:a,helpButton:i,helpDialog:u,helpDialogCloseButton:c,async openHelpDialog(){await u.isVisible()||await i.click();},mentionInput:d,mentionListbox:l,mentionOption:m=>l.getByRole("option",{name:m}),async startMention(){await d.isVisible()||await a.click();}}}function D({frame:e,trigger:o,variant:t="dropzone"}){let n=e.getByTestId("file-uploader"),s=n.locator('input[type="file"]'),r=o??(t==="button"?n.getByRole("button"):e.getByTestId("file-uploader-dropzone"));return {root:n,button:r,input:s,dropzone:e.getByTestId("file-uploader-dropzone"),errorMessage:n.getByRole("alert"),async upload(i){await s.setInputFiles(i);}}}function P(e,o,t){let n=i=>o.getByRole("menuitem",{name:i}),s=i=>o.getByRole("group",{name:i}),{isOpen:r}=g(t);return {menu:o,item:n,section:s,isOpen:r,submenu(i){let a=n(i),d=e.getByRole("menu",{name:i}),l=P(e,d,a),{open:c}=g(a);return {...l,trigger:a,open:async(u="Enter")=>c(u)}}}}function U({frame:e,trigger:o}){let t=o??e.getByTestId("menu-trigger"),n=e.getByRole("menu").first(),s=P(e,n,t),{open:r,isOpen:i}=g(t);return {...s,trigger:t,open:r,isOpen:i,async close(){await i()&&await t.press("Escape");},async selectItem(a){await r(),await s.item(a).click();}}}var M=(e=>{let t=Y(e);return e.multiple&&(t=Z(t,e.frame)),e.autocomplete&&(t=ee(t,e.frame)),t});function Y({frame:e,trigger:o}){let t=o??e.getByTestId("select-button"),n=r=>e.getByRole("option",{name:r,exact:typeof r=="string"}),{open:s}=g(t);return {trigger:t,listbox:e.getByRole("listbox"),option:n,open:s,async selectOption(r){await s(),await n(r).click();}}}function Z(e,o){return {...e,selectAllButton:o.getByTestId("select-all-button"),clearButton:o.getByTestId("select-clear-button"),tag:t=>e.trigger.getByRole("row",{name:t}),removeTagButton:t=>e.trigger.getByRole("row",{name:t}).getByRole("button")}}function ee(e,o){let t=o.getByRole("searchbox");return {...e,searchbox:t,async filter(n){await t.fill(n);}}}function te(e){return {select:((o={})=>M({frame:e,...o})),comboBox:(o={})=>F({frame:e,...o}),editor:(o={})=>v({frame:e,...o}),fileUploader:(o={})=>D({frame:e,...o}),menu:(o={})=>U({frame:e,...o})}}exports.DEFAULT_CRITICAL_INCOMPLETE_RULES=O;exports.DEFAULT_DISABLED_RULES=L;exports.DEFAULT_WCAG_TAGS=w;exports.expect=Q;exports.testers=te;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import {expect,test}from'@playwright/test';import _ from'axe-core';import {mkdir,readFile,writeFile}from'fs/promises';import C from'path';/**
1
+ import {expect,test}from'@playwright/test';import W from'axe-core';import {mkdir,readFile,writeFile}from'fs/promises';import I 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 _ from'axe-core';import {mkdir
7
7
  * This notice may not be removed from this file.
8
8
  *
9
9
  */
10
- var w=["wcag2a","wcag2aa","wcag21a","wcag21aa","best-practice"],L=["landmark-one-main","page-has-heading-one","region","aria-allowed-role","duplicate-id","scrollable-region-focusable"],E=["aria-valid-attr-value","aria-valid-attr","aria-required-attr"];function A(e,o={}){let{prefix:t="BaselineUI-",ignoreHashed:n=false}=o,r=Array.isArray(t)?t:[t];function s(c){return c.split(/[-_]/).some(i=>i.length>=5&&/^[a-z\d]+$/.test(i)&&/[a-z]/.test(i)&&/\d/.test(i))}function l(c){return [...c.classList].filter(i=>r.some(g=>i.startsWith(g))&&(!n||!s(i)))}function u(c,i=false){let g=[],b=c.shadowRoot?c.shadowRoot.children:c.children;for(let f of b){let h=i||f.getAttribute("aria-hidden")==="true",B=l(f),x=u(f,h);B.length>0?g.push({classes:B,children:x,hidden:h}):g.push(...x);}return g}function d(c,i="",g=false){let b=[];for(let f=0;f<c.length;f++){let h=c[f],B=f===c.length-1,x=g?"":B?"\u2514\u2500\u2500 ":"\u251C\u2500\u2500 ",P=g?"":i+(B?" ":"\u2502 "),U=h.classes.join(", ");b.push(i+x+U+(h.hidden?" [aria-hidden]":""),...d(h.children,P));}return b}let p=l(e),a=u(e);return p.length===0&&a.length===0?"":p.length>0?d([{classes:p,children:a,hidden:false}],"",true).join(`
11
- `):d(a,"",true).join(`
12
- `)}var y=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"}},j=async e=>{let o=typeof e.goto=="function",t=!o&&typeof e.owner=="function";if(o||t)return e.locator("body");let n=e;return await n.first().evaluate(s=>s.tagName?.toLowerCase()==="iframe").catch(()=>false)?n.contentFrame().locator("body"):n},M=async e=>{await e.first().evaluate((o,t)=>{if(window.axe)return;let r=document.createElement("script");r.textContent=t,document.head.append(r);},_.source);},W=["document-title","html-has-lang","html-lang-valid","html-xml-lang-mismatch"],z=(e,o=[])=>{let t=new Set([...L,...o,...e.disableRules??[]]);return {runOnly:{type:"tag",values:[...e.tags??w]},rules:Object.fromEntries([...t].map(n=>[n,{enabled:false}]))}},F=e=>{if(e!==void 0)return typeof e=="string"?[e]:[...e]},H=async(e,o,t)=>{let n=await e.count();if(n===0)throw new Error("toBeAccessible: locator resolved to zero elements; nothing to scan.");await M(e);let r=F(o.include),s=F(o.exclude);return n===1?await e.evaluate(async(l,u)=>{let d=window,p={include:[l,...u.userInclude??[]],exclude:u.userExclude??[]};return await d.axe.run(p,u.runOptions)},{userInclude:r,userExclude:s,runOptions:t}):await e.evaluateAll(async(l,u)=>{let d=window,p={include:[...l,...u.userInclude??[]],exclude:u.userExclude??[]};return await d.axe.run(p,u.runOptions)},{userInclude:r,userExclude:s,runOptions:t})},O="BaselineUI-";function S(e){return e.replaceAll(/\W+/g,"-").replaceAll(/^-|-$/g,"")}function V(e){return Array.isArray(e)?e.length===1&&e[0]===O:e===O}function G(e){return (Array.isArray(e)?e:[e]).join("+")}function q(e,o,t,n,r){let s=o.slice(1).map(l=>S(l));return t&&s.push(S(t)),n!==void 0&&!V(n)&&s.push(S(G(n))),r&&s.push("ignoreHashed"),C.join(e,`${s.join("--")}.txt`)}var J=expect.extend({toBeWithinDelta(e,o,t){return Math.abs(e-o)<=t?{message:()=>`expected ${e} not to be within ${t} of ${o}`,pass:true}:{message:()=>`expected ${e} to be within ${t} of ${o}`,pass:false}},async toBeCalledOnce(e){try{return await expect.poll(()=>e.callCount).toBe(1),{message:()=>"Expected function to be called once.",pass:!0}}catch(o){return {message:()=>y(o),pass:false}}},async toBeCalledTwice(e){try{return await expect.poll(()=>e.callCount).toBe(2),{message:()=>"Expected function to be called twice.",pass:!0}}catch(o){return {message:()=>y(o),pass:false}}},async toBeCalledThrice(e){try{return await expect.poll(()=>e.callCount).toBe(3),{message:()=>"Expected function to be called thrice.",pass:!0}}catch(o){return {message:()=>y(o),pass:false}}},async toBeCalledWith(e,...o){try{return await expect(()=>{for(let[t,n]of o.entries())expect(e.lastCall?.args?.[t]).toStrictEqual(n);}).toPass(),{message:()=>`Expected function to be called with ${o.join(", ")}.`,pass:!0}}catch(t){return {message:()=>y(t),pass:false}}},async toBeCalledWithExactly(e,...o){try{return await expect.poll(()=>e.lastCall?.args).toStrictEqual(o),{message:()=>`Expected function to be called with exactly ${o.join(", ")}.`,pass:!0}}catch(t){return {message:()=>y(t),pass:false}}},toBeCalled(e){try{return expect(e.called).toBeTruthy(),{message:()=>"Expected function to be called.",pass:!0}}catch(o){return {message:()=>y(o),pass:false}}},async toBeCalledTimes(e,o){try{return await expect.poll(()=>e.callCount).toBe(o),{message:()=>`Expected function to be called ${o} times.`,pass:!0}}catch(t){return {message:()=>y(t),pass:false}}},async toBeAccessible(e,o={}){let t=await j(e),n=await t.first().evaluate(a=>{let c=a.ownerDocument?.defaultView;return c!==null&&c!==window.top}).catch(()=>false),r=z(o,n?W:[]),s,l=async()=>{let a=await H(t,o,r),c=o.treatIncompleteAsViolationIds??E,i=[...a.violations,...a.incomplete.filter(g=>c.includes(g.id))];return s={result:a,violations:i},i},u;try{if(o.timeout&&o.timeout>0){let a=expect.poll(l,{timeout:o.timeout});await(this.isNot?a.not.toHaveLength(0):a.toHaveLength(0));}else await l();}catch(a){u=a;}if(!s)throw new Error(`toBeAccessible: axe scan never completed.
13
- ${y(u)}`);if(s.violations.length===0)return {message:()=>"Expected accessibility violations but found none.",pass:true};this.isNot||await test.info().attach(o.attachmentName??"accessibility-scan-results",{body:JSON.stringify(s.result,null,2),contentType:"application/json"});let d=s.violations.map(a=>{let c=a.nodes.map(i=>Array.isArray(i.target)?i.target.join(" "):String(i.target)).join(`
14
- `);return ` \u2022 [${a.id}] ${a.help}
15
- ${c}
16
- ${a.helpUrl??""}`}).join(`
17
- `),p=s.violations.length;return {message:()=>`Found ${p} accessibility violation${p===1?"":"s"}:
18
- ${d}`,pass:false}},async toMatchClassTree(e,o,t={}){let{prefix:n=O,ignoreHashed:r=false}=t,s=await e.evaluate(A,{prefix:n,ignoreHashed:r});if(!s){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=`${s}
19
- `,u=test.info();if(u.config.ignoreSnapshots)return {message:()=>"Class tree snapshot check skipped.",pass:!0};let p=q(u.project.snapshotDir,u.titlePath,o,n,r),a=u.config.updateSnapshots;await mkdir(C.dirname(p),{recursive:!0});let c;try{c=await readFile(p,"utf8");}catch(i){if(i.code==="ENOENT"&&a!=="none")return await writeFile(p,l),{message:()=>"Class tree snapshot created.",pass:!0};throw i}if(c!==l){if(a==="all"||a==="changed")return await writeFile(p,l),{message:()=>"Class tree snapshot updated.",pass:!0};try{expect(l).toBe(c);}catch(i){return {message:()=>`Class tree snapshot mismatch at ${p}
10
+ var w=["wcag2a","wcag2aa","wcag21a","wcag21aa","best-practice"],L=["landmark-one-main","page-has-heading-one","region","aria-allowed-role","duplicate-id","scrollable-region-focusable"],O=["aria-valid-attr-value","aria-valid-attr","aria-required-attr"];function A(e,o={}){let{prefix:t="BaselineUI-",ignoreHashed:n=false}=o,s=Array.isArray(t)?t:[t];function r(u){return u.split(/[-_]/).some(p=>p.length>=5&&/^[a-z\d]+$/.test(p)&&/[a-z]/.test(p)&&/\d/.test(p))}function i(u){return [...u.classList].filter(p=>s.some(m=>p.startsWith(m))&&(!n||!r(p)))}function a(u,p=false){let m=[],h=u.shadowRoot?u.shadowRoot.children:u.children;for(let T of h){let x=p||T.getAttribute("aria-hidden")==="true",B=i(T),b=a(T,x);B.length>0?m.push({classes:B,children:b,hidden:x}):m.push(...b);}return m}function d(u,p="",m=false){let h=[];for(let T=0;T<u.length;T++){let x=u[T],B=T===u.length-1,b=m?"":B?"\u2514\u2500\u2500 ":"\u251C\u2500\u2500 ",_=m?"":p+(B?" ":"\u2502 "),$=x.classes.join(", ");h.push(p+b+$+(x.hidden?" [aria-hidden]":""),...d(x.children,_));}return h}let l=i(e),c=a(e);return l.length===0&&c.length===0?"":l.length>0?d([{classes:l,children:c,hidden:false}],"",true).join(`
11
+ `):d(c,"",true).join(`
12
+ `)}var f=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"}},H=async e=>{let o=typeof e.goto=="function",t=!o&&typeof e.owner=="function";if(o||t)return e.locator("body");let n=e;return await n.first().evaluate(r=>r.tagName?.toLowerCase()==="iframe").catch(()=>false)?n.contentFrame().locator("body"):n},z=async e=>{await e.first().evaluate((o,t)=>{if(window.axe)return;let s=document.createElement("script");s.textContent=t,document.head.append(s);},W.source);},V=["document-title","html-has-lang","html-lang-valid","html-xml-lang-mismatch"],G=(e,o=[])=>{let t=new Set([...L,...o,...e.disableRules??[]]);return {runOnly:{type:"tag",values:[...e.tags??w]},rules:Object.fromEntries([...t].map(n=>[n,{enabled:false}]))}},k=e=>{if(e!==void 0)return typeof e=="string"?[e]:[...e]},q=async(e,o,t)=>{let n=await e.count();if(n===0)throw new Error("toBeAccessible: locator resolved to zero elements; nothing to scan.");await z(e);let s=k(o.include),r=k(o.exclude);return n===1?await e.evaluate(async(i,a)=>{let d=window,l={include:[i,...a.userInclude??[]],exclude:a.userExclude??[]};return await d.axe.run(l,a.runOptions)},{userInclude:s,userExclude:r,runOptions:t}):await e.evaluateAll(async(i,a)=>{let d=window,l={include:[...i,...a.userInclude??[]],exclude:a.userExclude??[]};return await d.axe.run(l,a.runOptions)},{userInclude:s,userExclude:r,runOptions:t})},S="BaselineUI-";function E(e){return e.replaceAll(/\W+/g,"-").replaceAll(/^-|-$/g,"")}function J(e){return Array.isArray(e)?e.length===1&&e[0]===S:e===S}function K(e){return (Array.isArray(e)?e:[e]).join("+")}function X(e,o,t,n,s){let r=o.slice(1).map(i=>E(i));return t&&r.push(E(t)),n!==void 0&&!J(n)&&r.push(E(K(n))),s&&r.push("ignoreHashed"),I.join(e,`${r.join("--")}.txt`)}var Q=expect.extend({toBeWithinDelta(e,o,t){return Math.abs(e-o)<=t?{message:()=>`expected ${e} not to be within ${t} of ${o}`,pass:true}:{message:()=>`expected ${e} to be within ${t} of ${o}`,pass:false}},async toBeCalledOnce(e){try{return await expect.poll(()=>e.callCount).toBe(1),{message:()=>"Expected function to be called once.",pass:!0}}catch(o){return {message:()=>f(o),pass:false}}},async toBeCalledTwice(e){try{return await expect.poll(()=>e.callCount).toBe(2),{message:()=>"Expected function to be called twice.",pass:!0}}catch(o){return {message:()=>f(o),pass:false}}},async toBeCalledThrice(e){try{return await expect.poll(()=>e.callCount).toBe(3),{message:()=>"Expected function to be called thrice.",pass:!0}}catch(o){return {message:()=>f(o),pass:false}}},async toBeCalledWith(e,...o){try{return await expect(()=>{for(let[t,n]of o.entries())expect(e.lastCall?.args?.[t]).toStrictEqual(n);}).toPass(),{message:()=>`Expected function to be called with ${o.join(", ")}.`,pass:!0}}catch(t){return {message:()=>f(t),pass:false}}},async toBeCalledWithExactly(e,...o){try{return await expect.poll(()=>e.lastCall?.args).toStrictEqual(o),{message:()=>`Expected function to be called with exactly ${o.join(", ")}.`,pass:!0}}catch(t){return {message:()=>f(t),pass:false}}},toBeCalled(e){try{return expect(e.called).toBeTruthy(),{message:()=>"Expected function to be called.",pass:!0}}catch(o){return {message:()=>f(o),pass:false}}},async toBeCalledTimes(e,o){try{return await expect.poll(()=>e.callCount).toBe(o),{message:()=>`Expected function to be called ${o} times.`,pass:!0}}catch(t){return {message:()=>f(t),pass:false}}},async toBeAccessible(e,o={}){let t=await H(e),n=await t.first().evaluate(c=>{let u=c.ownerDocument?.defaultView;return u!==null&&u!==window.top}).catch(()=>false),s=G(o,n?V:[]),r,i=async()=>{let c=await q(t,o,s),u=o.treatIncompleteAsViolationIds??O,p=[...c.violations,...c.incomplete.filter(m=>u.includes(m.id))];return r={result:c,violations:p},p},a;try{if(o.timeout&&o.timeout>0){let c=expect.poll(i,{timeout:o.timeout});await(this.isNot?c.not.toHaveLength(0):c.toHaveLength(0));}else await i();}catch(c){a=c;}if(!r)throw new Error(`toBeAccessible: axe scan never completed.
13
+ ${f(a)}`);if(r.violations.length===0)return {message:()=>"Expected accessibility violations but found none.",pass:true};this.isNot||await test.info().attach(o.attachmentName??"accessibility-scan-results",{body:JSON.stringify(r.result,null,2),contentType:"application/json"});let d=r.violations.map(c=>{let u=c.nodes.map(p=>Array.isArray(p.target)?p.target.join(" "):String(p.target)).join(`
14
+ `);return ` \u2022 [${c.id}] ${c.help}
15
+ ${u}
16
+ ${c.helpUrl??""}`}).join(`
17
+ `),l=r.violations.length;return {message:()=>`Found ${l} accessibility violation${l===1?"":"s"}:
18
+ ${d}`,pass:false}},async toMatchClassTree(e,o,t={}){let{prefix:n=S,ignoreHashed:s=false}=t,r=await e.evaluate(A,{prefix:n,ignoreHashed:s});if(!r){let i=Array.isArray(n)?n.map(a=>`${a}*`).join(" / "):`${n}*`;return {message:()=>`No ${i} classes found in the DOM tree.`,pass:false}}try{let i=`${r}
19
+ `,a=test.info();if(a.config.ignoreSnapshots)return {message:()=>"Class tree snapshot check skipped.",pass:!0};let l=X(a.project.snapshotDir,a.titlePath,o,n,s),c=a.config.updateSnapshots;await mkdir(I.dirname(l),{recursive:!0});let u;try{u=await readFile(l,"utf8");}catch(p){if(p.code==="ENOENT"&&c!=="none")return await writeFile(l,i),{message:()=>"Class tree snapshot created.",pass:!0};throw p}if(u!==i){if(c==="all"||c==="changed")return await writeFile(l,i),{message:()=>"Class tree snapshot updated.",pass:!0};try{expect(i).toBe(u);}catch(p){return {message:()=>`Class tree snapshot mismatch at ${l}
20
20
 
21
- ${i.message}`,pass:!1}}}return {message:()=>"Class tree matched snapshot.",pass:!0}}catch(l){return {message:()=>y(l),pass:false}}}});function T(e){let o=async()=>{if(await e.getAttribute("aria-expanded")!==null)return e;let r=e.locator("[aria-expanded]").first();return await r.count()>0?r:e},t=async()=>await(await o()).getAttribute("aria-expanded")==="true";return {isOpen:t,open:async(r="click")=>{if(await t())return;if(r==="click"){await e.click();return}await(await o()).press(r);}}}function R({frame:e,root:o}){let t=o??e.locator(".BaselineUI-Editor"),n=t.getByTestId("editor-font-color-button"),r=t.getByTestId("editor-background-color-button"),s=t.getByTestId("editor-add-link-button"),l=t.getByTestId("editor-help-button"),u=t.getByTestId("editor-mention-button"),d=t.getByRole("combobox"),p=e.getByTestId("editor-mention-listbox"),a=e.getByTestId("editor-help-dialog-close-button"),c=e.getByRole("dialog").filter({has:a}),i=e.getByRole("dialog").filter({has:e.getByTestId(/^editor-link-/)});return {root:t,textbox:t.getByRole("textbox"),placeholder:t.getByTestId("editor-placeholder"),toolbar:t.getByRole("toolbar"),boldButton:t.getByTestId("editor-bold-button"),italicButton:t.getByTestId("editor-italic-button"),underlineButton:t.getByTestId("editor-underline-button"),fontColorButton:n,backgroundColorButton:r,openFontColorPicker:T(n).open,openBackgroundColorPicker:T(r).open,addLinkButton:s,linkDialog:i,linkInput:e.getByTestId("editor-link-input"),linkAddButton:e.getByTestId("editor-link-add-button"),linkEditButton:e.getByTestId("editor-link-edit-button"),linkRemoveButton:e.getByTestId("editor-link-remove-button"),linkCancelButton:e.getByTestId("editor-link-cancel-button"),linkSaveButton:e.getByTestId("editor-link-save-button"),openLinkDialog:T(s).open,saveButton:t.getByTestId("editor-save-button"),cancelButton:t.getByTestId("editor-cancel-button"),mentionButton:u,helpButton:l,helpDialog:c,helpDialogCloseButton:a,async openHelpDialog(){await c.isVisible()||await l.click();},mentionInput:d,mentionListbox:p,mentionOption:g=>p.getByRole("option",{name:g}),async startMention(){await d.isVisible()||await u.click();}}}function D({frame:e,trigger:o,variant:t="dropzone"}){let n=e.getByTestId("file-uploader"),r=n.locator('input[type="file"]'),s=o??(t==="button"?n.getByRole("button"):e.getByTestId("file-uploader-dropzone"));return {root:n,button:s,input:r,dropzone:e.getByTestId("file-uploader-dropzone"),errorMessage:n.getByRole("alert"),async upload(l){await r.setInputFiles(l);}}}var v=(e=>{let t=K(e);return e.multiple&&(t=X(t,e.frame)),e.autocomplete&&(t=Q(t,e.frame)),t});function K({frame:e,trigger:o}){let t=o??e.getByTestId("select-button"),n=s=>e.getByRole("option",{name:s}),{open:r}=T(t);return {trigger:t,listbox:e.getByRole("listbox"),option:n,open:r,async selectOption(s){await r(),await n(s).click();}}}function X(e,o){return {...e,selectAllButton:o.getByTestId("select-all-button"),clearButton:o.getByTestId("select-clear-button"),tag:t=>e.trigger.getByRole("row",{name:t}),removeTagButton:t=>e.trigger.getByRole("row",{name:t}).getByRole("button")}}function Q(e,o){let t=o.getByRole("searchbox");return {...e,searchbox:t,async filter(n){await t.fill(n);}}}function Y(e){return {select:((o={})=>v({frame:e,...o})),editor:(o={})=>R({frame:e,...o}),fileUploader:(o={})=>D({frame:e,...o})}}export{E as DEFAULT_CRITICAL_INCOMPLETE_RULES,L as DEFAULT_DISABLED_RULES,w as DEFAULT_WCAG_TAGS,J as expect,Y as testers};
21
+ ${p.message}`,pass:!1}}}return {message:()=>"Class tree matched snapshot.",pass:!0}}catch(i){return {message:()=>f(i),pass:false}}}});function g(e){let o=async()=>{if(await e.getAttribute("aria-expanded")!==null)return e;let s=e.locator("[aria-expanded]").first();return await s.count()>0?s:e},t=async()=>await(await o()).getAttribute("aria-expanded")==="true";return {isOpen:t,open:async(s="click")=>{if(await t())return;if(s==="click"){await e.click();return}await(await o()).press(s);}}}function F({frame:e,trigger:o,toggleButton:t}){let n=o??e.getByRole("combobox",{includeHidden:true}),s=t??e.getByTestId("combobox-toggle-button"),r=l=>e.getByRole("option",{name:l,exact:typeof l=="string"}),{isOpen:i,open:a}=g(n),d=async(l="click")=>{if(!await i()){if(l==="click"){await s.click();return}await a(l);}};return {input:n,toggleButton:s,listbox:e.getByRole("listbox"),option:r,isOpen:i,open:d,async selectOption(l){await d(),await r(l).click();},async filter(l){await n.fill(l);}}}function v({frame:e,root:o}){let t=o??e.locator(".BaselineUI-Editor"),n=t.getByTestId("editor-font-color-button"),s=t.getByTestId("editor-background-color-button"),r=t.getByTestId("editor-add-link-button"),i=t.getByTestId("editor-help-button"),a=t.getByTestId("editor-mention-button"),d=t.getByRole("combobox"),l=e.getByTestId("editor-mention-listbox"),c=e.getByTestId("editor-help-dialog-close-button"),u=e.getByRole("dialog").filter({has:c}),p=e.getByRole("dialog").filter({has:e.getByTestId(/^editor-link-/)});return {root:t,textbox:t.getByRole("textbox"),placeholder:t.getByTestId("editor-placeholder"),toolbar:t.getByRole("toolbar"),boldButton:t.getByTestId("editor-bold-button"),italicButton:t.getByTestId("editor-italic-button"),underlineButton:t.getByTestId("editor-underline-button"),fontColorButton:n,backgroundColorButton:s,openFontColorPicker:g(n).open,openBackgroundColorPicker:g(s).open,addLinkButton:r,linkDialog:p,linkInput:e.getByTestId("editor-link-input"),linkAddButton:e.getByTestId("editor-link-add-button"),linkEditButton:e.getByTestId("editor-link-edit-button"),linkRemoveButton:e.getByTestId("editor-link-remove-button"),linkCancelButton:e.getByTestId("editor-link-cancel-button"),linkSaveButton:e.getByTestId("editor-link-save-button"),openLinkDialog:g(r).open,saveButton:t.getByTestId("editor-save-button"),cancelButton:t.getByTestId("editor-cancel-button"),mentionButton:a,helpButton:i,helpDialog:u,helpDialogCloseButton:c,async openHelpDialog(){await u.isVisible()||await i.click();},mentionInput:d,mentionListbox:l,mentionOption:m=>l.getByRole("option",{name:m}),async startMention(){await d.isVisible()||await a.click();}}}function D({frame:e,trigger:o,variant:t="dropzone"}){let n=e.getByTestId("file-uploader"),s=n.locator('input[type="file"]'),r=o??(t==="button"?n.getByRole("button"):e.getByTestId("file-uploader-dropzone"));return {root:n,button:r,input:s,dropzone:e.getByTestId("file-uploader-dropzone"),errorMessage:n.getByRole("alert"),async upload(i){await s.setInputFiles(i);}}}function P(e,o,t){let n=i=>o.getByRole("menuitem",{name:i}),s=i=>o.getByRole("group",{name:i}),{isOpen:r}=g(t);return {menu:o,item:n,section:s,isOpen:r,submenu(i){let a=n(i),d=e.getByRole("menu",{name:i}),l=P(e,d,a),{open:c}=g(a);return {...l,trigger:a,open:async(u="Enter")=>c(u)}}}}function U({frame:e,trigger:o}){let t=o??e.getByTestId("menu-trigger"),n=e.getByRole("menu").first(),s=P(e,n,t),{open:r,isOpen:i}=g(t);return {...s,trigger:t,open:r,isOpen:i,async close(){await i()&&await t.press("Escape");},async selectItem(a){await r(),await s.item(a).click();}}}var M=(e=>{let t=Y(e);return e.multiple&&(t=Z(t,e.frame)),e.autocomplete&&(t=ee(t,e.frame)),t});function Y({frame:e,trigger:o}){let t=o??e.getByTestId("select-button"),n=r=>e.getByRole("option",{name:r,exact:typeof r=="string"}),{open:s}=g(t);return {trigger:t,listbox:e.getByRole("listbox"),option:n,open:s,async selectOption(r){await s(),await n(r).click();}}}function Z(e,o){return {...e,selectAllButton:o.getByTestId("select-all-button"),clearButton:o.getByTestId("select-clear-button"),tag:t=>e.trigger.getByRole("row",{name:t}),removeTagButton:t=>e.trigger.getByRole("row",{name:t}).getByRole("button")}}function ee(e,o){let t=o.getByRole("searchbox");return {...e,searchbox:t,async filter(n){await t.fill(n);}}}function te(e){return {select:((o={})=>M({frame:e,...o})),comboBox:(o={})=>F({frame:e,...o}),editor:(o={})=>v({frame:e,...o}),fileUploader:(o={})=>D({frame:e,...o}),menu:(o={})=>U({frame:e,...o})}}export{O as DEFAULT_CRITICAL_INCOMPLETE_RULES,L as DEFAULT_DISABLED_RULES,w as DEFAULT_WCAG_TAGS,Q as expect,te as testers};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baseline-ui/test-utils",
3
- "version": "1.2.0",
3
+ "version": "2.1.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.4",
54
54
  "react-frame-component": "^5.3.2",
55
- "@baseline-ui/core": "1.2.0"
55
+ "@baseline-ui/core": "2.1.0"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsup",
package/sbom.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.6.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.6",
5
- "serialNumber": "urn:uuid:c2fae2f8-fe12-4be7-a552-2f21626c22f0",
5
+ "serialNumber": "urn:uuid:b18db2e7-4962-4ee1-9d09-d79342f2c601",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-07-16T13:00:23.963Z",
8
+ "timestamp": "2026-08-13T09:45:54.274Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "pre-build"
@@ -23,9 +23,9 @@
23
23
  "component": {
24
24
  "type": "library",
25
25
  "name": "@baseline-ui/test-utils",
26
- "version": "1.2.0",
27
- "purl": "pkg:npm/@baseline-ui/test-utils@1.2.0",
28
- "bom-ref": "pkg:npm/@baseline-ui/test-utils@1.2.0",
26
+ "version": "2.1.0",
27
+ "purl": "pkg:npm/@baseline-ui/test-utils@2.1.0",
28
+ "bom-ref": "pkg:npm/@baseline-ui/test-utils@2.1.0",
29
29
  "licenses": [
30
30
  {
31
31
  "license": {
@@ -500,7 +500,7 @@
500
500
  ],
501
501
  "dependencies": [
502
502
  {
503
- "ref": "pkg:npm/@baseline-ui/test-utils@1.2.0",
503
+ "ref": "pkg:npm/@baseline-ui/test-utils@2.1.0",
504
504
  "dependsOn": [
505
505
  "pkg:npm/%40neondatabase/serverless@1.1.0",
506
506
  "pkg:npm/%40playwright/test@1.60.0",