@assure-one/design-system 1.30.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/codemods/0.2.0-radix-migration.mjs +315 -0
- package/codemods/README.md +305 -0
- package/codemods/lib/css-selectors.mjs +33 -0
- package/codemods/lib/css-values.mjs +223 -0
- package/codemods/lib/ds-stylesheet.mjs +168 -0
- package/codemods/lib/environment.mjs +72 -0
- package/codemods/lib/files.mjs +100 -0
- package/codemods/lib/jsx.mjs +0 -0
- package/codemods/lib/ledger.mjs +84 -0
- package/codemods/lib/registry.mjs +30 -0
- package/codemods/lib/report.mjs +119 -0
- package/codemods/lib/runner.mjs +164 -0
- package/codemods/run.mjs +161 -0
- package/codemods/transforms/cm-15-dom-selectors.mjs +573 -0
- package/codemods/transforms/cm-16-globals-css.mjs +487 -0
- package/dist/css/base.css +60 -0
- package/dist/css/legacy-aliases.css +489 -0
- package/dist/css/shadcn.css +155 -0
- package/dist/css/tailwind.css +233 -0
- package/dist/css/tokens.css +439 -0
- package/dist/index.d.ts +327 -33
- package/dist/index.js +1843 -706
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/testing/index.cjs +458 -0
- package/dist/testing/index.d.cts +253 -0
- package/dist/testing/index.d.ts +253 -0
- package/dist/testing/index.js +452 -0
- package/dist/testing/setup.cjs +123 -0
- package/dist/testing/setup.js +121 -0
- package/dist/testing/style-stub.cjs +7 -0
- package/dist/testing/style-stub.js +5 -0
- package/dist/tokens/index.d.ts +114 -96
- package/dist/tokens/index.js +67 -48
- package/dist/tokens/index.js.map +1 -1
- package/package.json +84 -14
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest configuration wrapper that loads the real design system (ADR-014).
|
|
3
|
+
*
|
|
4
|
+
* Jest's CommonJS runtime cannot load the package as published:
|
|
5
|
+
* 1. the `exports` map only has an `import` condition, so the bare specifier
|
|
6
|
+
* does not resolve ("Cannot find module");
|
|
7
|
+
* 2. the build is ESM, and `next/jest` (like plain Jest) skips `node_modules`
|
|
8
|
+
* when transforming;
|
|
9
|
+
* 3. jsdom lacks APIs the Radix-based components call.
|
|
10
|
+
* `withAssureDesignSystem` fixes all three on the final config, so it works on
|
|
11
|
+
* top of `next/jest` (which only lets callers append ignore patterns).
|
|
12
|
+
*
|
|
13
|
+
* Runs in Node (inside a Jest config). No Node built-ins are imported, so the
|
|
14
|
+
* rest of the entry stays safe to load in a browser test runner.
|
|
15
|
+
*/
|
|
16
|
+
/** The subset of a Jest config the wrapper reads or writes. @experimental */
|
|
17
|
+
interface JestConfigLike {
|
|
18
|
+
/** Jest `rootDir`; passed through unchanged. */
|
|
19
|
+
rootDir?: string;
|
|
20
|
+
/** Jest `moduleNameMapper`; the package's own mappings are placed first. */
|
|
21
|
+
moduleNameMapper?: Record<string, string | string[]>;
|
|
22
|
+
/** Jest `transformIgnorePatterns`; each entry gets an exemption for the package build. */
|
|
23
|
+
transformIgnorePatterns?: string[];
|
|
24
|
+
/** Jest `setupFiles`; the shim setup file is prepended. */
|
|
25
|
+
setupFiles?: string[];
|
|
26
|
+
/** Any other Jest option; passed through unchanged. */
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A Jest config value: an object, or a (possibly async) function returning
|
|
31
|
+
* one, as `next/jest` produces.
|
|
32
|
+
*
|
|
33
|
+
* @experimental
|
|
34
|
+
*/
|
|
35
|
+
type JestConfigInput<T extends JestConfigLike = JestConfigLike> = T | (() => T | Promise<T>);
|
|
36
|
+
/** Options for `withAssureDesignSystem`. @experimental */
|
|
37
|
+
interface AssureJestOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Add the jsdom shims (`installDomPolyfills`) to `setupFiles`. Default `true`.
|
|
40
|
+
* They are installed only where the environment lacks them.
|
|
41
|
+
*/
|
|
42
|
+
polyfills?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Signature of `withAssureDesignSystem`. @experimental */
|
|
45
|
+
type WithAssureDesignSystem = <T extends JestConfigLike>(config: JestConfigInput<T>, options?: AssureJestOptions) => () => Promise<T & JestConfigLike>;
|
|
46
|
+
/**
|
|
47
|
+
* Wrap a Jest config so the real `@assure-one/design-system` loads in tests.
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* // jest.config.ts
|
|
51
|
+
* import nextJest from "next/jest.js";
|
|
52
|
+
* import { withAssureDesignSystem } from "@assure-one/design-system/testing";
|
|
53
|
+
*
|
|
54
|
+
* const createJestConfig = nextJest({ dir: "./" });
|
|
55
|
+
* export default withAssureDesignSystem(createJestConfig({ testEnvironment: "jsdom" }));
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* The returned async function resolves `config` and then:
|
|
59
|
+
* - maps `@assure-one/design-system` and `/tokens` to the built files, and
|
|
60
|
+
* `/styles.css` to an empty module. These mappings come before the app's
|
|
61
|
+
* own, so they take precedence over a stub mapping;
|
|
62
|
+
* - exempts the package's build output from every `transformIgnorePatterns`
|
|
63
|
+
* entry, so the app's transformer compiles the ESM build;
|
|
64
|
+
* - prepends the jsdom shim setup file to `setupFiles` (unless
|
|
65
|
+
* `polyfills: false`).
|
|
66
|
+
*
|
|
67
|
+
* @experimental
|
|
68
|
+
*/
|
|
69
|
+
declare const withAssureDesignSystem: WithAssureDesignSystem;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* jsdom gaps that the design system's Radix-based components run into.
|
|
73
|
+
*
|
|
74
|
+
* Each shim is installed only when the environment lacks the API, so an app's
|
|
75
|
+
* own setup (or a newer jsdom) always wins. Nothing is observed or measured:
|
|
76
|
+
* the shims exist so components mount and respond to keyboard and pointer
|
|
77
|
+
* events instead of throwing. One entry is a guard rather than a shim
|
|
78
|
+
* (`topLayerSelectors`, see below).
|
|
79
|
+
*/
|
|
80
|
+
/** Names of the shims `installDomPolyfills` can install. @experimental */
|
|
81
|
+
type DomPolyfill = "ResizeObserver" | "matchMedia" | "PointerEvent" | "hasPointerCapture" | "setPointerCapture" | "releasePointerCapture" | "scrollIntoView" | "DOMRect" | "topLayerSelectors";
|
|
82
|
+
/** Options for `installDomPolyfills`. @experimental */
|
|
83
|
+
interface DomPolyfillOptions {
|
|
84
|
+
/**
|
|
85
|
+
* Answer for `window.matchMedia(query).matches` when the shim is installed.
|
|
86
|
+
* Default: every query is `false` (no reduced motion, no dark scheme, and
|
|
87
|
+
* `min-width` queries do not match).
|
|
88
|
+
*/
|
|
89
|
+
matchMedia?: (query: string) => boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Install the jsdom shims the design system needs and return the names of the
|
|
93
|
+
* ones that were missing. Safe to call more than once and outside a DOM
|
|
94
|
+
* environment (it then does nothing).
|
|
95
|
+
*
|
|
96
|
+
* @experimental
|
|
97
|
+
*/
|
|
98
|
+
declare function installDomPolyfills(options?: DomPolyfillOptions): DomPolyfill[];
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Records the toasts that a real `ToastProvider` shows, including ones that
|
|
102
|
+
* have already been dismissed, so tests can assert on them. It replaces
|
|
103
|
+
* hand-written test-only exports such as `recordedToasts`/`resetToasts`
|
|
104
|
+
* (ADR-014).
|
|
105
|
+
*
|
|
106
|
+
* The recorder reads the rendered toast region. Toast markup is internal to
|
|
107
|
+
* this package; the mapping below is kept in step with `toast.tsx` by this
|
|
108
|
+
* package's tests, so consumers never depend on it.
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* Variant of a recorded toast. `"custom"` is a toast rendered from `content`,
|
|
112
|
+
* whose variant is not shown.
|
|
113
|
+
*
|
|
114
|
+
* @experimental
|
|
115
|
+
*/
|
|
116
|
+
type RecordedToastVariant = "default" | "success" | "destructive" | "warning" | "info" | "loading" | "custom";
|
|
117
|
+
/** A toast as the user saw it, captured by `createToastRecorder`. @experimental */
|
|
118
|
+
interface RecordedToast {
|
|
119
|
+
/** Title text ("" when the toast has none). */
|
|
120
|
+
readonly title: string;
|
|
121
|
+
/** Description text ("" when the toast has none). */
|
|
122
|
+
readonly description: string;
|
|
123
|
+
/** Visual variant, read from the rendered toast. */
|
|
124
|
+
readonly variant: RecordedToastVariant;
|
|
125
|
+
/** Live-region role: `alert` for destructive toasts, `status` for the rest, `group` for `content` toasts. */
|
|
126
|
+
readonly role: "status" | "alert" | "group";
|
|
127
|
+
/** Label of the action button, or `null`. */
|
|
128
|
+
readonly action: string | null;
|
|
129
|
+
/** Title, description and action label, space-separated; for `content` toasts, all their text. */
|
|
130
|
+
readonly text: string;
|
|
131
|
+
/** `true` while the toast is on screen. */
|
|
132
|
+
readonly open: boolean;
|
|
133
|
+
}
|
|
134
|
+
/** Handle returned by `createToastRecorder`. @experimental */
|
|
135
|
+
interface ToastRecorder {
|
|
136
|
+
/** Every toast shown since the recorder started (or since `clear()`), oldest first. Updates to a visible toast are reflected. */
|
|
137
|
+
readonly toasts: readonly RecordedToast[];
|
|
138
|
+
/** The toasts currently on screen. */
|
|
139
|
+
readonly visible: readonly RecordedToast[];
|
|
140
|
+
/** The most recently shown toast. */
|
|
141
|
+
last(): RecordedToast | undefined;
|
|
142
|
+
/** The first recorded toast whose title, description or text matches. */
|
|
143
|
+
find(match: string | RegExp): RecordedToast | undefined;
|
|
144
|
+
/** Forget everything recorded so far. */
|
|
145
|
+
clear(): void;
|
|
146
|
+
/** Stop recording. Recorded toasts stay readable. */
|
|
147
|
+
stop(): void;
|
|
148
|
+
}
|
|
149
|
+
/** Options for `createToastRecorder`. @experimental */
|
|
150
|
+
interface ToastRecorderOptions {
|
|
151
|
+
/** Where to watch. Default `document.body` (toasts render in a fixed region there). */
|
|
152
|
+
root?: Node;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Start recording toasts shown by a `ToastProvider`.
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* const toasts = createToastRecorder();
|
|
159
|
+
* await user.click(screen.getByRole("button", { name: "Save" }));
|
|
160
|
+
* expect(toasts.last()).toMatchObject({ title: "Saved", variant: "success" });
|
|
161
|
+
* toasts.stop();
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* Create it before the action under test. Toasts that auto-dismiss stay in
|
|
165
|
+
* `toasts` with `open: false`.
|
|
166
|
+
*
|
|
167
|
+
* @experimental
|
|
168
|
+
*/
|
|
169
|
+
declare function createToastRecorder(options?: ToastRecorderOptions): ToastRecorder;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Anything that can find an element by role and accessible name, such as
|
|
173
|
+
* Testing Library's `screen` or `within(container)`.
|
|
174
|
+
*
|
|
175
|
+
* @experimental
|
|
176
|
+
*/
|
|
177
|
+
interface RoleQueries {
|
|
178
|
+
/** Return the single element with this role (and accessible name), or throw. */
|
|
179
|
+
getByRole(role: string, options?: {
|
|
180
|
+
name?: string | RegExp;
|
|
181
|
+
}): HTMLElement;
|
|
182
|
+
}
|
|
183
|
+
/** Text matcher: exact text (after trimming and collapsing whitespace) or a pattern. @experimental */
|
|
184
|
+
type TextMatch = string | RegExp;
|
|
185
|
+
/** Options for `selectOption` and `openMenu`. @experimental */
|
|
186
|
+
interface InteractionOptions {
|
|
187
|
+
/** How many render passes to wait for the popup to appear or close. Default 20. */
|
|
188
|
+
maxFlushes?: number;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Open a design-system `Select` and choose an option by its visible text.
|
|
192
|
+
*
|
|
193
|
+
* ```ts
|
|
194
|
+
* await selectOption(screen, "Tax year", "2025"); // trigger found by role "combobox" and name
|
|
195
|
+
* await selectOption(screen.getByRole("combobox"), /2025/);
|
|
196
|
+
* ```
|
|
197
|
+
*
|
|
198
|
+
* The trigger is opened with the keyboard, the option is chosen with Enter,
|
|
199
|
+
* and the helper waits for the list to close. It throws when the trigger or
|
|
200
|
+
* option is disabled, or when no option matches (the error lists the options).
|
|
201
|
+
*
|
|
202
|
+
* @experimental
|
|
203
|
+
*/
|
|
204
|
+
declare function selectOption(trigger: HTMLElement, option: TextMatch, options?: InteractionOptions): Promise<void>;
|
|
205
|
+
/**
|
|
206
|
+
* Open the `Select` whose combobox trigger has the accessible name `label`
|
|
207
|
+
* (found through `queries`, for example Testing Library's `screen`) and choose
|
|
208
|
+
* `option`. See the other overload for details.
|
|
209
|
+
*
|
|
210
|
+
* @experimental
|
|
211
|
+
*/
|
|
212
|
+
declare function selectOption(queries: RoleQueries, label: TextMatch, option: TextMatch, options?: InteractionOptions): Promise<void>;
|
|
213
|
+
/**
|
|
214
|
+
* An open design-system menu, returned by `openMenu`.
|
|
215
|
+
*
|
|
216
|
+
* @experimental
|
|
217
|
+
*/
|
|
218
|
+
interface MenuHandle {
|
|
219
|
+
/** The element with role `menu`. */
|
|
220
|
+
readonly element: HTMLElement;
|
|
221
|
+
/** Every item (`menuitem`, `menuitemcheckbox`, `menuitemradio`) currently in the menu. */
|
|
222
|
+
items(): HTMLElement[];
|
|
223
|
+
/** The item whose text matches; throws with the list of items otherwise. */
|
|
224
|
+
getItem(name: TextMatch): HTMLElement;
|
|
225
|
+
/**
|
|
226
|
+
* Activate an item with Enter, then wait for the menu to close. A menu that
|
|
227
|
+
* stays open (its `onSelect` called `preventDefault()`) is not an error.
|
|
228
|
+
*/
|
|
229
|
+
select(name: TextMatch): Promise<void>;
|
|
230
|
+
/** Close the menu with Escape. */
|
|
231
|
+
close(): Promise<void>;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Open a design-system `DropdownMenu` from its trigger and return a handle to
|
|
235
|
+
* the menu.
|
|
236
|
+
*
|
|
237
|
+
* ```ts
|
|
238
|
+
* const menu = await openMenu(screen, "Actions"); // trigger found by role "button" and name
|
|
239
|
+
* await menu.select("Archive");
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* @experimental
|
|
243
|
+
*/
|
|
244
|
+
declare function openMenu(trigger: HTMLElement, options?: InteractionOptions): Promise<MenuHandle>;
|
|
245
|
+
/**
|
|
246
|
+
* Open the `DropdownMenu` whose trigger button has the accessible name `name`
|
|
247
|
+
* (found through `queries`, for example Testing Library's `screen`).
|
|
248
|
+
*
|
|
249
|
+
* @experimental
|
|
250
|
+
*/
|
|
251
|
+
declare function openMenu(queries: RoleQueries, name: TextMatch, options?: InteractionOptions): Promise<MenuHandle>;
|
|
252
|
+
|
|
253
|
+
export { type AssureJestOptions, type DomPolyfill, type DomPolyfillOptions, type InteractionOptions, type JestConfigInput, type JestConfigLike, type MenuHandle, type RecordedToast, type RecordedToastVariant, type RoleQueries, type TextMatch, type ToastRecorder, type ToastRecorderOptions, type WithAssureDesignSystem, createToastRecorder, installDomPolyfills, openMenu, selectOption, withAssureDesignSystem };
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest configuration wrapper that loads the real design system (ADR-014).
|
|
3
|
+
*
|
|
4
|
+
* Jest's CommonJS runtime cannot load the package as published:
|
|
5
|
+
* 1. the `exports` map only has an `import` condition, so the bare specifier
|
|
6
|
+
* does not resolve ("Cannot find module");
|
|
7
|
+
* 2. the build is ESM, and `next/jest` (like plain Jest) skips `node_modules`
|
|
8
|
+
* when transforming;
|
|
9
|
+
* 3. jsdom lacks APIs the Radix-based components call.
|
|
10
|
+
* `withAssureDesignSystem` fixes all three on the final config, so it works on
|
|
11
|
+
* top of `next/jest` (which only lets callers append ignore patterns).
|
|
12
|
+
*
|
|
13
|
+
* Runs in Node (inside a Jest config). No Node built-ins are imported, so the
|
|
14
|
+
* rest of the entry stays safe to load in a browser test runner.
|
|
15
|
+
*/
|
|
16
|
+
/** The subset of a Jest config the wrapper reads or writes. @experimental */
|
|
17
|
+
interface JestConfigLike {
|
|
18
|
+
/** Jest `rootDir`; passed through unchanged. */
|
|
19
|
+
rootDir?: string;
|
|
20
|
+
/** Jest `moduleNameMapper`; the package's own mappings are placed first. */
|
|
21
|
+
moduleNameMapper?: Record<string, string | string[]>;
|
|
22
|
+
/** Jest `transformIgnorePatterns`; each entry gets an exemption for the package build. */
|
|
23
|
+
transformIgnorePatterns?: string[];
|
|
24
|
+
/** Jest `setupFiles`; the shim setup file is prepended. */
|
|
25
|
+
setupFiles?: string[];
|
|
26
|
+
/** Any other Jest option; passed through unchanged. */
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A Jest config value: an object, or a (possibly async) function returning
|
|
31
|
+
* one, as `next/jest` produces.
|
|
32
|
+
*
|
|
33
|
+
* @experimental
|
|
34
|
+
*/
|
|
35
|
+
type JestConfigInput<T extends JestConfigLike = JestConfigLike> = T | (() => T | Promise<T>);
|
|
36
|
+
/** Options for `withAssureDesignSystem`. @experimental */
|
|
37
|
+
interface AssureJestOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Add the jsdom shims (`installDomPolyfills`) to `setupFiles`. Default `true`.
|
|
40
|
+
* They are installed only where the environment lacks them.
|
|
41
|
+
*/
|
|
42
|
+
polyfills?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** Signature of `withAssureDesignSystem`. @experimental */
|
|
45
|
+
type WithAssureDesignSystem = <T extends JestConfigLike>(config: JestConfigInput<T>, options?: AssureJestOptions) => () => Promise<T & JestConfigLike>;
|
|
46
|
+
/**
|
|
47
|
+
* Wrap a Jest config so the real `@assure-one/design-system` loads in tests.
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* // jest.config.ts
|
|
51
|
+
* import nextJest from "next/jest.js";
|
|
52
|
+
* import { withAssureDesignSystem } from "@assure-one/design-system/testing";
|
|
53
|
+
*
|
|
54
|
+
* const createJestConfig = nextJest({ dir: "./" });
|
|
55
|
+
* export default withAssureDesignSystem(createJestConfig({ testEnvironment: "jsdom" }));
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* The returned async function resolves `config` and then:
|
|
59
|
+
* - maps `@assure-one/design-system` and `/tokens` to the built files, and
|
|
60
|
+
* `/styles.css` to an empty module. These mappings come before the app's
|
|
61
|
+
* own, so they take precedence over a stub mapping;
|
|
62
|
+
* - exempts the package's build output from every `transformIgnorePatterns`
|
|
63
|
+
* entry, so the app's transformer compiles the ESM build;
|
|
64
|
+
* - prepends the jsdom shim setup file to `setupFiles` (unless
|
|
65
|
+
* `polyfills: false`).
|
|
66
|
+
*
|
|
67
|
+
* @experimental
|
|
68
|
+
*/
|
|
69
|
+
declare const withAssureDesignSystem: WithAssureDesignSystem;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* jsdom gaps that the design system's Radix-based components run into.
|
|
73
|
+
*
|
|
74
|
+
* Each shim is installed only when the environment lacks the API, so an app's
|
|
75
|
+
* own setup (or a newer jsdom) always wins. Nothing is observed or measured:
|
|
76
|
+
* the shims exist so components mount and respond to keyboard and pointer
|
|
77
|
+
* events instead of throwing. One entry is a guard rather than a shim
|
|
78
|
+
* (`topLayerSelectors`, see below).
|
|
79
|
+
*/
|
|
80
|
+
/** Names of the shims `installDomPolyfills` can install. @experimental */
|
|
81
|
+
type DomPolyfill = "ResizeObserver" | "matchMedia" | "PointerEvent" | "hasPointerCapture" | "setPointerCapture" | "releasePointerCapture" | "scrollIntoView" | "DOMRect" | "topLayerSelectors";
|
|
82
|
+
/** Options for `installDomPolyfills`. @experimental */
|
|
83
|
+
interface DomPolyfillOptions {
|
|
84
|
+
/**
|
|
85
|
+
* Answer for `window.matchMedia(query).matches` when the shim is installed.
|
|
86
|
+
* Default: every query is `false` (no reduced motion, no dark scheme, and
|
|
87
|
+
* `min-width` queries do not match).
|
|
88
|
+
*/
|
|
89
|
+
matchMedia?: (query: string) => boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Install the jsdom shims the design system needs and return the names of the
|
|
93
|
+
* ones that were missing. Safe to call more than once and outside a DOM
|
|
94
|
+
* environment (it then does nothing).
|
|
95
|
+
*
|
|
96
|
+
* @experimental
|
|
97
|
+
*/
|
|
98
|
+
declare function installDomPolyfills(options?: DomPolyfillOptions): DomPolyfill[];
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Records the toasts that a real `ToastProvider` shows, including ones that
|
|
102
|
+
* have already been dismissed, so tests can assert on them. It replaces
|
|
103
|
+
* hand-written test-only exports such as `recordedToasts`/`resetToasts`
|
|
104
|
+
* (ADR-014).
|
|
105
|
+
*
|
|
106
|
+
* The recorder reads the rendered toast region. Toast markup is internal to
|
|
107
|
+
* this package; the mapping below is kept in step with `toast.tsx` by this
|
|
108
|
+
* package's tests, so consumers never depend on it.
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* Variant of a recorded toast. `"custom"` is a toast rendered from `content`,
|
|
112
|
+
* whose variant is not shown.
|
|
113
|
+
*
|
|
114
|
+
* @experimental
|
|
115
|
+
*/
|
|
116
|
+
type RecordedToastVariant = "default" | "success" | "destructive" | "warning" | "info" | "loading" | "custom";
|
|
117
|
+
/** A toast as the user saw it, captured by `createToastRecorder`. @experimental */
|
|
118
|
+
interface RecordedToast {
|
|
119
|
+
/** Title text ("" when the toast has none). */
|
|
120
|
+
readonly title: string;
|
|
121
|
+
/** Description text ("" when the toast has none). */
|
|
122
|
+
readonly description: string;
|
|
123
|
+
/** Visual variant, read from the rendered toast. */
|
|
124
|
+
readonly variant: RecordedToastVariant;
|
|
125
|
+
/** Live-region role: `alert` for destructive toasts, `status` for the rest, `group` for `content` toasts. */
|
|
126
|
+
readonly role: "status" | "alert" | "group";
|
|
127
|
+
/** Label of the action button, or `null`. */
|
|
128
|
+
readonly action: string | null;
|
|
129
|
+
/** Title, description and action label, space-separated; for `content` toasts, all their text. */
|
|
130
|
+
readonly text: string;
|
|
131
|
+
/** `true` while the toast is on screen. */
|
|
132
|
+
readonly open: boolean;
|
|
133
|
+
}
|
|
134
|
+
/** Handle returned by `createToastRecorder`. @experimental */
|
|
135
|
+
interface ToastRecorder {
|
|
136
|
+
/** Every toast shown since the recorder started (or since `clear()`), oldest first. Updates to a visible toast are reflected. */
|
|
137
|
+
readonly toasts: readonly RecordedToast[];
|
|
138
|
+
/** The toasts currently on screen. */
|
|
139
|
+
readonly visible: readonly RecordedToast[];
|
|
140
|
+
/** The most recently shown toast. */
|
|
141
|
+
last(): RecordedToast | undefined;
|
|
142
|
+
/** The first recorded toast whose title, description or text matches. */
|
|
143
|
+
find(match: string | RegExp): RecordedToast | undefined;
|
|
144
|
+
/** Forget everything recorded so far. */
|
|
145
|
+
clear(): void;
|
|
146
|
+
/** Stop recording. Recorded toasts stay readable. */
|
|
147
|
+
stop(): void;
|
|
148
|
+
}
|
|
149
|
+
/** Options for `createToastRecorder`. @experimental */
|
|
150
|
+
interface ToastRecorderOptions {
|
|
151
|
+
/** Where to watch. Default `document.body` (toasts render in a fixed region there). */
|
|
152
|
+
root?: Node;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Start recording toasts shown by a `ToastProvider`.
|
|
156
|
+
*
|
|
157
|
+
* ```ts
|
|
158
|
+
* const toasts = createToastRecorder();
|
|
159
|
+
* await user.click(screen.getByRole("button", { name: "Save" }));
|
|
160
|
+
* expect(toasts.last()).toMatchObject({ title: "Saved", variant: "success" });
|
|
161
|
+
* toasts.stop();
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* Create it before the action under test. Toasts that auto-dismiss stay in
|
|
165
|
+
* `toasts` with `open: false`.
|
|
166
|
+
*
|
|
167
|
+
* @experimental
|
|
168
|
+
*/
|
|
169
|
+
declare function createToastRecorder(options?: ToastRecorderOptions): ToastRecorder;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Anything that can find an element by role and accessible name, such as
|
|
173
|
+
* Testing Library's `screen` or `within(container)`.
|
|
174
|
+
*
|
|
175
|
+
* @experimental
|
|
176
|
+
*/
|
|
177
|
+
interface RoleQueries {
|
|
178
|
+
/** Return the single element with this role (and accessible name), or throw. */
|
|
179
|
+
getByRole(role: string, options?: {
|
|
180
|
+
name?: string | RegExp;
|
|
181
|
+
}): HTMLElement;
|
|
182
|
+
}
|
|
183
|
+
/** Text matcher: exact text (after trimming and collapsing whitespace) or a pattern. @experimental */
|
|
184
|
+
type TextMatch = string | RegExp;
|
|
185
|
+
/** Options for `selectOption` and `openMenu`. @experimental */
|
|
186
|
+
interface InteractionOptions {
|
|
187
|
+
/** How many render passes to wait for the popup to appear or close. Default 20. */
|
|
188
|
+
maxFlushes?: number;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Open a design-system `Select` and choose an option by its visible text.
|
|
192
|
+
*
|
|
193
|
+
* ```ts
|
|
194
|
+
* await selectOption(screen, "Tax year", "2025"); // trigger found by role "combobox" and name
|
|
195
|
+
* await selectOption(screen.getByRole("combobox"), /2025/);
|
|
196
|
+
* ```
|
|
197
|
+
*
|
|
198
|
+
* The trigger is opened with the keyboard, the option is chosen with Enter,
|
|
199
|
+
* and the helper waits for the list to close. It throws when the trigger or
|
|
200
|
+
* option is disabled, or when no option matches (the error lists the options).
|
|
201
|
+
*
|
|
202
|
+
* @experimental
|
|
203
|
+
*/
|
|
204
|
+
declare function selectOption(trigger: HTMLElement, option: TextMatch, options?: InteractionOptions): Promise<void>;
|
|
205
|
+
/**
|
|
206
|
+
* Open the `Select` whose combobox trigger has the accessible name `label`
|
|
207
|
+
* (found through `queries`, for example Testing Library's `screen`) and choose
|
|
208
|
+
* `option`. See the other overload for details.
|
|
209
|
+
*
|
|
210
|
+
* @experimental
|
|
211
|
+
*/
|
|
212
|
+
declare function selectOption(queries: RoleQueries, label: TextMatch, option: TextMatch, options?: InteractionOptions): Promise<void>;
|
|
213
|
+
/**
|
|
214
|
+
* An open design-system menu, returned by `openMenu`.
|
|
215
|
+
*
|
|
216
|
+
* @experimental
|
|
217
|
+
*/
|
|
218
|
+
interface MenuHandle {
|
|
219
|
+
/** The element with role `menu`. */
|
|
220
|
+
readonly element: HTMLElement;
|
|
221
|
+
/** Every item (`menuitem`, `menuitemcheckbox`, `menuitemradio`) currently in the menu. */
|
|
222
|
+
items(): HTMLElement[];
|
|
223
|
+
/** The item whose text matches; throws with the list of items otherwise. */
|
|
224
|
+
getItem(name: TextMatch): HTMLElement;
|
|
225
|
+
/**
|
|
226
|
+
* Activate an item with Enter, then wait for the menu to close. A menu that
|
|
227
|
+
* stays open (its `onSelect` called `preventDefault()`) is not an error.
|
|
228
|
+
*/
|
|
229
|
+
select(name: TextMatch): Promise<void>;
|
|
230
|
+
/** Close the menu with Escape. */
|
|
231
|
+
close(): Promise<void>;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Open a design-system `DropdownMenu` from its trigger and return a handle to
|
|
235
|
+
* the menu.
|
|
236
|
+
*
|
|
237
|
+
* ```ts
|
|
238
|
+
* const menu = await openMenu(screen, "Actions"); // trigger found by role "button" and name
|
|
239
|
+
* await menu.select("Archive");
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* @experimental
|
|
243
|
+
*/
|
|
244
|
+
declare function openMenu(trigger: HTMLElement, options?: InteractionOptions): Promise<MenuHandle>;
|
|
245
|
+
/**
|
|
246
|
+
* Open the `DropdownMenu` whose trigger button has the accessible name `name`
|
|
247
|
+
* (found through `queries`, for example Testing Library's `screen`).
|
|
248
|
+
*
|
|
249
|
+
* @experimental
|
|
250
|
+
*/
|
|
251
|
+
declare function openMenu(queries: RoleQueries, name: TextMatch, options?: InteractionOptions): Promise<MenuHandle>;
|
|
252
|
+
|
|
253
|
+
export { type AssureJestOptions, type DomPolyfill, type DomPolyfillOptions, type InteractionOptions, type JestConfigInput, type JestConfigLike, type MenuHandle, type RecordedToast, type RecordedToastVariant, type RoleQueries, type TextMatch, type ToastRecorder, type ToastRecorderOptions, type WithAssureDesignSystem, createToastRecorder, installDomPolyfills, openMenu, selectOption, withAssureDesignSystem };
|