@atomic-testing/component-driver-mui-v9 0.0.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.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +76 -0
  3. package/dist/index.cjs +2439 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1391 -0
  6. package/dist/index.d.mts +1391 -0
  7. package/dist/index.mjs +2386 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +48 -0
  10. package/src/components/AccordionDriver.ts +109 -0
  11. package/src/components/AlertDriver.ts +74 -0
  12. package/src/components/AutoCompleteDriver.ts +151 -0
  13. package/src/components/AvatarDriver.ts +51 -0
  14. package/src/components/AvatarGroupDriver.ts +79 -0
  15. package/src/components/BadgeDriver.ts +43 -0
  16. package/src/components/BottomNavigationActionDriver.ts +23 -0
  17. package/src/components/BottomNavigationDriver.ts +138 -0
  18. package/src/components/ButtonDriver.ts +16 -0
  19. package/src/components/CheckboxDriver.ts +98 -0
  20. package/src/components/ChipDriver.ts +53 -0
  21. package/src/components/DialogDriver.ts +122 -0
  22. package/src/components/DrawerDriver.ts +90 -0
  23. package/src/components/FabDriver.ts +11 -0
  24. package/src/components/InputDriver.ts +112 -0
  25. package/src/components/ListDriver.ts +42 -0
  26. package/src/components/ListItemDriver.ts +34 -0
  27. package/src/components/MenuDriver.ts +65 -0
  28. package/src/components/MenuItemDriver.ts +15 -0
  29. package/src/components/OverlayDriver.ts +98 -0
  30. package/src/components/PaginationDriver.ts +117 -0
  31. package/src/components/ProgressDriver.ts +78 -0
  32. package/src/components/RadioDriver.ts +63 -0
  33. package/src/components/RadioGroupDriver.ts +129 -0
  34. package/src/components/RatingDriver.ts +121 -0
  35. package/src/components/SelectDriver.ts +223 -0
  36. package/src/components/SliderDriver.ts +109 -0
  37. package/src/components/SnackbarDriver.ts +68 -0
  38. package/src/components/SpeedDialDriver.ts +83 -0
  39. package/src/components/StepperDriver.ts +109 -0
  40. package/src/components/SwitchDriver.ts +62 -0
  41. package/src/components/TabDriver.ts +39 -0
  42. package/src/components/TableCellDriver.ts +14 -0
  43. package/src/components/TableDriver.ts +148 -0
  44. package/src/components/TablePaginationDriver.ts +110 -0
  45. package/src/components/TableRowDriver.ts +79 -0
  46. package/src/components/TabsDriver.ts +133 -0
  47. package/src/components/TextFieldDriver.ts +155 -0
  48. package/src/components/ToggleButtonDriver.ts +21 -0
  49. package/src/components/ToggleButtonGroupDriver.ts +75 -0
  50. package/src/components/TooltipDriver.ts +82 -0
  51. package/src/errors/MenuItemDisabledError.ts +17 -0
  52. package/src/errors/MenuItemNotFoundError.ts +17 -0
  53. package/src/index.ts +52 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,2439 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _atomic_testing_component_driver_html = require("@atomic-testing/component-driver-html");
3
+ let _atomic_testing_core = require("@atomic-testing/core");
4
+ //#region src/components/AccordionDriver.ts
5
+ const parts$13 = {
6
+ /**
7
+ * The clickable area to expand/collapse the accordion.
8
+ */
9
+ disclosure: {
10
+ locator: (0, _atomic_testing_core.byCssClass)("MuiAccordionSummary-root"),
11
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
12
+ },
13
+ summary: {
14
+ locator: (0, _atomic_testing_core.byCssClass)("MuiAccordionSummary-content"),
15
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
16
+ },
17
+ content: {
18
+ locator: (0, _atomic_testing_core.byRole)("region"),
19
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
20
+ }
21
+ };
22
+ /**
23
+ * Driver for Material UI v9 Accordion component.
24
+ * @see https://mui.com/material-ui/react-accordion/
25
+ */
26
+ var AccordionDriver = class extends _atomic_testing_core.ComponentDriver {
27
+ constructor(locator, interactor, option) {
28
+ super(locator, interactor, {
29
+ ...option,
30
+ parts: parts$13
31
+ });
32
+ }
33
+ /**
34
+ * Get the title/summary of the accordion.
35
+ * @returns The title/summary of the accordion.
36
+ */
37
+ async getSummary() {
38
+ return await this.parts.summary.getText() ?? null;
39
+ }
40
+ /**
41
+ * Whether the accordion is expanded.
42
+ * @returns True if the accordion is expanded, false if collapsed.
43
+ */
44
+ async isExpanded() {
45
+ await this.enforcePartExistence("disclosure");
46
+ return await this.parts.disclosure.getAttribute("aria-expanded") === "true";
47
+ }
48
+ /**
49
+ * Whether the accordion is disabled.
50
+ * @returns True if the accordion is disabled, false if enabled.
51
+ */
52
+ async isDisabled() {
53
+ await this.enforcePartExistence("disclosure");
54
+ return await this.parts.disclosure.getAttribute("disabled") != null;
55
+ }
56
+ async click() {
57
+ await this.parts.disclosure.click();
58
+ }
59
+ /**
60
+ * Expand the accordion.
61
+ */
62
+ async expand() {
63
+ if (!await this.isExpanded()) {
64
+ await this.parts.disclosure.click();
65
+ await _atomic_testing_core.timingUtil.waitUntil({
66
+ probeFn: () => this.isExpanded(),
67
+ terminateCondition: true,
68
+ timeoutMs: 1e3
69
+ });
70
+ }
71
+ }
72
+ /**
73
+ * Collapse the accordion.
74
+ */
75
+ async collapse() {
76
+ if (await this.isExpanded()) {
77
+ await this.parts.disclosure.click();
78
+ await _atomic_testing_core.timingUtil.waitUntil({
79
+ probeFn: () => this.isExpanded(),
80
+ terminateCondition: false,
81
+ timeoutMs: 1e3
82
+ });
83
+ }
84
+ }
85
+ get driverName() {
86
+ return "MuiV9AccordionDriver";
87
+ }
88
+ };
89
+ //#endregion
90
+ //#region src/components/AlertDriver.ts
91
+ const parts$12 = {
92
+ title: {
93
+ locator: (0, _atomic_testing_core.byCssClass)("MuiAlertTitle-root"),
94
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
95
+ },
96
+ message: {
97
+ locator: (0, _atomic_testing_core.byCssClass)("MuiAlert-message"),
98
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
99
+ }
100
+ };
101
+ const alertSeverityEvaluators = [
102
+ {
103
+ value: "error",
104
+ pattern: /MuiAlert-.*Error/
105
+ },
106
+ {
107
+ value: "warning",
108
+ pattern: /MuiAlert-.*Warning/
109
+ },
110
+ {
111
+ value: "info",
112
+ pattern: /MuiAlert-.*Info/
113
+ },
114
+ {
115
+ value: "success",
116
+ pattern: /MuiAlert-.*Success/
117
+ }
118
+ ];
119
+ /**
120
+ * Driver for Material UI v9 Alert component.
121
+ * @see https://mui.com/material-ui/react-alert/
122
+ */
123
+ var AlertDriver = class extends _atomic_testing_core.ContainerDriver {
124
+ constructor(locator, interactor, option) {
125
+ super(locator, interactor, {
126
+ ...option,
127
+ parts: parts$12,
128
+ content: option?.content ?? {}
129
+ });
130
+ }
131
+ async getTitle() {
132
+ return await this.parts.title.getText() ?? null;
133
+ }
134
+ async getMessage() {
135
+ return await this.parts.message.getText() ?? null;
136
+ }
137
+ async getSeverity() {
138
+ const cssClassString = await this.interactor.getAttribute(this.locator, "class");
139
+ if (cssClassString != null) {
140
+ const cssClasses = cssClassString.split(/\s+/);
141
+ for (const cssClassName of cssClasses) for (const evaluator of alertSeverityEvaluators) if (evaluator.pattern.test(cssClassName)) return evaluator.value;
142
+ }
143
+ return null;
144
+ }
145
+ get driverName() {
146
+ return "MuiV9AlertDriver";
147
+ }
148
+ };
149
+ //#endregion
150
+ //#region src/components/AvatarDriver.ts
151
+ const imageLocator = (0, _atomic_testing_core.byCssSelector)("img.MuiAvatar-img");
152
+ /**
153
+ * Driver for the Material UI v9 Avatar component.
154
+ *
155
+ * An Avatar renders a `.MuiAvatar-root`; with a `src` it contains an
156
+ * `img.MuiAvatar-img`, otherwise it shows letter initials (or an icon). The
157
+ * driver reads the image's `alt`, the presence of the image, and the letter
158
+ * initials accordingly.
159
+ * @see https://mui.com/material-ui/react-avatar/
160
+ */
161
+ var AvatarDriver = class extends _atomic_testing_core.ComponentDriver {
162
+ get imageLocator() {
163
+ return _atomic_testing_core.locatorUtil.append(this.locator, imageLocator);
164
+ }
165
+ /**
166
+ * Whether the avatar renders an image (vs letter/icon fallback).
167
+ */
168
+ async hasImage() {
169
+ return this.interactor.exists(this.imageLocator);
170
+ }
171
+ /**
172
+ * The image's alt text, or `undefined` when the avatar has no image.
173
+ */
174
+ async getAltText() {
175
+ if (!await this.hasImage()) return;
176
+ return await this.interactor.getAttribute(this.imageLocator, "alt") ?? void 0;
177
+ }
178
+ /**
179
+ * The letter initials of a text avatar, or `undefined` for an image or icon
180
+ * avatar (which render no text).
181
+ */
182
+ async getInitials() {
183
+ if (await this.hasImage()) return;
184
+ const text = (await this.getText())?.trim();
185
+ return text ? text : void 0;
186
+ }
187
+ get driverName() {
188
+ return "MuiV9AvatarDriver";
189
+ }
190
+ };
191
+ //#endregion
192
+ //#region src/components/AvatarGroupDriver.ts
193
+ const avatarItemLocator = (0, _atomic_testing_core.byCssSelector)(".MuiAvatarGroup-avatar");
194
+ const surplusPattern = /^\+\d+$/;
195
+ const defaultAvatarGroupDriverOption = {
196
+ itemClass: AvatarDriver,
197
+ itemLocator: avatarItemLocator
198
+ };
199
+ /**
200
+ * Driver for the Material UI v9 AvatarGroup component.
201
+ *
202
+ * AvatarGroup renders up to `max` avatars plus a surplus "+N" avatar when there
203
+ * are more. This is a {@link ListComponentDriver} over the rendered avatars
204
+ * (surplus included via `getItems`), with helpers to count the real avatars and
205
+ * read the surplus label.
206
+ * @see https://mui.com/material-ui/react-avatar/#grouped
207
+ */
208
+ var AvatarGroupDriver = class extends _atomic_testing_core.ListComponentDriver {
209
+ constructor(locator, interactor, option = {}) {
210
+ super(locator, interactor, {
211
+ ...defaultAvatarGroupDriverOption,
212
+ ...option
213
+ });
214
+ }
215
+ /**
216
+ * The number of real avatars shown, excluding the surplus "+N" indicator.
217
+ */
218
+ async getVisibleCount() {
219
+ let count = 0;
220
+ for await (const item of _atomic_testing_core.listHelper.getListItemIterator(this, this.getItemLocator(), AvatarDriver)) {
221
+ const text = (await item.getText())?.trim();
222
+ if (text == null || !surplusPattern.test(text)) count++;
223
+ }
224
+ return count;
225
+ }
226
+ /**
227
+ * The surplus indicator's label (e.g. "+3"), or `undefined` when every avatar
228
+ * fits within `max` and no surplus is shown.
229
+ */
230
+ async getSurplusLabel() {
231
+ for await (const item of _atomic_testing_core.listHelper.getListItemIterator(this, this.getItemLocator(), AvatarDriver)) {
232
+ const text = (await item.getText())?.trim();
233
+ if (text != null && surplusPattern.test(text)) return text;
234
+ }
235
+ }
236
+ get driverName() {
237
+ return "MuiV9AvatarGroupDriver";
238
+ }
239
+ };
240
+ //#endregion
241
+ //#region src/components/AutoCompleteDriver.ts
242
+ const parts$11 = {
243
+ input: {
244
+ locator: (0, _atomic_testing_core.byRole)("combobox"),
245
+ driver: _atomic_testing_component_driver_html.HTMLTextInputDriver
246
+ },
247
+ dropdown: {
248
+ locator: (0, _atomic_testing_core.byLinkedElement)("Root").onLinkedElement((0, _atomic_testing_core.byRole)("combobox")).extractAttribute("aria-controls").toMatchMyAttribute("id"),
249
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
250
+ }
251
+ };
252
+ const optionLocator$1 = (0, _atomic_testing_core.byRole)("option");
253
+ const noOptionsLocator = (0, _atomic_testing_core.byCssClass)("MuiAutocomplete-noOptions");
254
+ const loadingLocator = (0, _atomic_testing_core.byCssClass)("MuiAutocomplete-loading");
255
+ const defaultAutoCompleteDriverOption = { matchType: "exact" };
256
+ var AutoCompleteDriver = class extends _atomic_testing_core.ComponentDriver {
257
+ constructor(locator, interactor, option) {
258
+ super(locator, interactor, {
259
+ ...option,
260
+ parts: parts$11
261
+ });
262
+ this._option = {};
263
+ this._option = option ?? {};
264
+ }
265
+ /**
266
+ * Get the display of the autocomplete
267
+ */
268
+ async getValue() {
269
+ return await this.parts.input.getValue() ?? null;
270
+ }
271
+ /**
272
+ * Set the value of the autocomplete, how selection happens
273
+ * depends on the option assigned to AutoCompleteDriver
274
+ * By default, when the option has matchType set to exact, only option with matching text would be selected
275
+ * When the option has matchType set to first-available, the first option would be selected regardless of the text
276
+ *
277
+ * Option of auto complete can be set at the time of part definition, for example
278
+ * ```
279
+ * {
280
+ * myAutoComplete: {
281
+ * locator: byCssSelector('my-auto-complete'),
282
+ * driver: AutoCompleteDriver,
283
+ * option: {
284
+ * matchType: 'first-available',
285
+ * },
286
+ * },
287
+ * }
288
+ * ```
289
+ *
290
+ * @param value
291
+ * @returns
292
+ */
293
+ async setValue(value) {
294
+ await this.parts.input.setValue(value ?? "");
295
+ if (value === null) return true;
296
+ const option = _atomic_testing_core.locatorUtil.append(this.parts.dropdown.locator, optionLocator$1);
297
+ let index = 0;
298
+ const matchType = this._option?.matchType ?? defaultAutoCompleteDriverOption.matchType;
299
+ for await (const optionDriver of _atomic_testing_core.listHelper.getListItemIterator(this, option, _atomic_testing_component_driver_html.HTMLButtonDriver)) {
300
+ const optionValue = await optionDriver.getText();
301
+ if (matchType === "exact" && optionValue?.trim() === value || matchType === "first-available" && index === 0) {
302
+ await optionDriver.click();
303
+ return true;
304
+ }
305
+ index++;
306
+ }
307
+ return false;
308
+ }
309
+ async isDisabled() {
310
+ return this.parts.input.isDisabled();
311
+ }
312
+ async isReadonly() {
313
+ return this.parts.input.isReadonly();
314
+ }
315
+ /**
316
+ * Whether the popup is currently showing its loading indicator (the
317
+ * `loadingText`). Only meaningful while the popup is open — open it first
318
+ * (e.g. by typing into the input), since MUI renders nothing otherwise.
319
+ */
320
+ async isLoading() {
321
+ return this.interactor.exists(loadingLocator);
322
+ }
323
+ /**
324
+ * Whether the popup is currently showing its "no options" message. Same
325
+ * open-the-popup-first caveat as {@link AutoCompleteDriver.isLoading}.
326
+ */
327
+ async hasNoOptions() {
328
+ return this.interactor.exists(noOptionsLocator);
329
+ }
330
+ get driverName() {
331
+ return "MuiV9AutoCompleteDriver";
332
+ }
333
+ };
334
+ //#endregion
335
+ //#region src/components/BadgeDriver.ts
336
+ const parts$10 = { contentDisplay: {
337
+ locator: (0, _atomic_testing_core.byCssClass)("MuiBadge-badge"),
338
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
339
+ } };
340
+ /**
341
+ * Driver for Material UI v9 Badge component.
342
+ * @see https://mui.com/material-ui/react-badge/
343
+ */
344
+ var BadgeDriver = class extends _atomic_testing_core.ComponentDriver {
345
+ constructor(locator, interactor, option) {
346
+ super(locator, interactor, {
347
+ ...option,
348
+ parts: parts$10
349
+ });
350
+ }
351
+ /**
352
+ * Get the content of the badge.
353
+ * @returns The content of the badge.
354
+ */
355
+ async getContent() {
356
+ await this.enforcePartExistence("contentDisplay");
357
+ return await this.parts.contentDisplay.getText() ?? null;
358
+ }
359
+ get driverName() {
360
+ return "MuiV9BadgeDriver";
361
+ }
362
+ };
363
+ //#endregion
364
+ //#region src/components/BottomNavigationActionDriver.ts
365
+ /**
366
+ * Driver for a single Material UI v9 BottomNavigationAction.
367
+ *
368
+ * Each action renders as a `<button>` (no explicit ARIA role); MUI marks the
369
+ * active one with the `Mui-selected` state class, so selection is read from the
370
+ * class while `getText`/`click`/`isDisabled` come from {@link HTMLButtonDriver}
371
+ * and the base `ComponentDriver`.
372
+ * @see https://mui.com/material-ui/react-bottom-navigation/
373
+ */
374
+ var BottomNavigationActionDriver = class extends _atomic_testing_component_driver_html.HTMLButtonDriver {
375
+ /**
376
+ * Whether this action is selected (MUI applies the `Mui-selected` state class).
377
+ */
378
+ async isSelected() {
379
+ return this.interactor.hasCssClass(this.locator, "Mui-selected");
380
+ }
381
+ get driverName() {
382
+ return "MuiV9BottomNavigationActionDriver";
383
+ }
384
+ };
385
+ //#endregion
386
+ //#region src/components/BottomNavigationDriver.ts
387
+ /**
388
+ * BottomNavigation actions are direct-child `<button>` siblings of the root (no
389
+ * ARIA role). They are located by their `MuiBottomNavigationAction-root` class
390
+ * rather than a bare `button` tag, so positional enumeration is not thrown off by
391
+ * any incidental nested button inside an action.
392
+ */
393
+ const defaultBottomNavigationDriverOption = {
394
+ itemClass: BottomNavigationActionDriver,
395
+ itemLocator: (0, _atomic_testing_core.byCssSelector)(".MuiBottomNavigationAction-root")
396
+ };
397
+ /**
398
+ * Driver for the Material UI v9 BottomNavigation component.
399
+ *
400
+ * A {@link ListComponentDriver} over the action buttons, exposing the selected
401
+ * index/label, selection by index/label, and per-action info, plus per-action
402
+ * {@link BottomNavigationActionDriver} instances via `getItems`/`getItemByIndex`/
403
+ * `getItemByLabel`.
404
+ * @see https://mui.com/material-ui/react-bottom-navigation/
405
+ */
406
+ var BottomNavigationDriver = class extends _atomic_testing_core.ListComponentDriver {
407
+ constructor(locator, interactor, option = {}) {
408
+ super(locator, interactor, {
409
+ ...defaultBottomNavigationDriverOption,
410
+ ...option
411
+ });
412
+ }
413
+ /**
414
+ * Every action with its label and selected state, in order.
415
+ */
416
+ async getActions() {
417
+ const actions = [];
418
+ for await (const item of _atomic_testing_core.listHelper.getListItemIterator(this, this.getItemLocator(), BottomNavigationActionDriver)) actions.push({
419
+ label: (await item.getText())?.trim(),
420
+ selected: await item.isSelected()
421
+ });
422
+ return actions;
423
+ }
424
+ /**
425
+ * Zero-based index of the selected action, or `-1` when none is selected.
426
+ */
427
+ async getSelectedIndex() {
428
+ let index = 0;
429
+ for await (const item of _atomic_testing_core.listHelper.getListItemIterator(this, this.getItemLocator(), BottomNavigationActionDriver)) {
430
+ if (await item.isSelected()) return index;
431
+ index++;
432
+ }
433
+ return -1;
434
+ }
435
+ /**
436
+ * Label of the selected action, or `null` when none is selected. Returns `null`
437
+ * (not `undefined`) to match the sibling `TabsDriver.getSelectedLabel` contract.
438
+ */
439
+ async getSelectedLabel() {
440
+ for await (const item of _atomic_testing_core.listHelper.getListItemIterator(this, this.getItemLocator(), BottomNavigationActionDriver)) if (await item.isSelected()) return (await item.getText())?.trim() ?? null;
441
+ return null;
442
+ }
443
+ /**
444
+ * Select the action at the given zero-based index.
445
+ * @returns `false` when the index is out of range.
446
+ */
447
+ async selectByIndex(index) {
448
+ const item = await this.getItemByIndex(index);
449
+ if (item == null) return false;
450
+ await item.click();
451
+ return true;
452
+ }
453
+ /**
454
+ * Select the first action whose visible label equals `label`.
455
+ * @returns `false` when no action matches.
456
+ */
457
+ async selectByLabel(label) {
458
+ const item = await this.getItemByLabel(label);
459
+ if (item == null) return false;
460
+ await item.click();
461
+ return true;
462
+ }
463
+ get driverName() {
464
+ return "MuiV9BottomNavigationDriver";
465
+ }
466
+ };
467
+ //#endregion
468
+ //#region src/components/ButtonDriver.ts
469
+ /**
470
+ * Driver for Material UI v9 Button component.
471
+ * @see https://mui.com/material-ui/react-button/
472
+ */
473
+ var ButtonDriver = class extends _atomic_testing_component_driver_html.HTMLButtonDriver {
474
+ async getValue() {
475
+ return await this.interactor.getAttribute(this.locator, "value") ?? null;
476
+ }
477
+ get driverName() {
478
+ return "MuiV9ButtonDriver";
479
+ }
480
+ };
481
+ //#endregion
482
+ //#region src/components/CheckboxDriver.ts
483
+ const checkboxPart = { checkbox: {
484
+ locator: (0, _atomic_testing_core.byTagName)("input"),
485
+ driver: _atomic_testing_component_driver_html.HTMLCheckboxDriver
486
+ } };
487
+ var CheckboxDriver = class extends _atomic_testing_core.ComponentDriver {
488
+ constructor(locator, interactor, option) {
489
+ super(locator, interactor, {
490
+ ...option,
491
+ parts: checkboxPart
492
+ });
493
+ }
494
+ isSelected() {
495
+ return this.parts.checkbox.isSelected();
496
+ }
497
+ async setSelected(selected) {
498
+ if (await this.isIndeterminate() && selected === false) await this.parts.checkbox.setSelected(true);
499
+ await this.parts.checkbox.setSelected(selected);
500
+ }
501
+ getValue() {
502
+ return this.parts.checkbox.getValue();
503
+ }
504
+ async isIndeterminate() {
505
+ return await this.interactor.getAttribute(this.parts.checkbox.locator, "data-indeterminate") === "true";
506
+ }
507
+ isDisabled() {
508
+ return this.parts.checkbox.isDisabled();
509
+ }
510
+ isReadonly() {
511
+ return this.parts.checkbox.isReadonly();
512
+ }
513
+ /**
514
+ * Get the text of the label associated with the checkbox, or `undefined` when the checkbox
515
+ * is rendered without one (e.g. a bare `<Checkbox>` outside of a `FormControlLabel`).
516
+ *
517
+ * MUI's `FormControlLabel` does not expose a `for`/`id` or `aria-labelledby` association; it
518
+ * labels the control implicitly by wrapping it in a `<label>` and rendering the text as a
519
+ * sibling. The label therefore lives outside this driver's own subtree, so we re-root at the
520
+ * enclosing `<label>` — matched against this checkbox via `:has()`, while preserving the
521
+ * surrounding scope — and read its text. This resolves to a single CSS selector, so it behaves
522
+ * identically across every interactor (DOM/React/Vue and Playwright).
523
+ */
524
+ async getLabel() {
525
+ const labelLocator = this.getEnclosingLabelLocator();
526
+ return await this.interactor.exists(labelLocator) ? this.interactor.getText(labelLocator) : void 0;
527
+ }
528
+ /**
529
+ * Build a locator for the `<label>` that encloses this checkbox, scoped to the same ancestor
530
+ * context as the checkbox itself so that sibling checkboxes are never mismatched.
531
+ */
532
+ getEnclosingLabelLocator() {
533
+ const chain = _atomic_testing_core.locatorUtil.toChain(this.locator);
534
+ const selfSelector = chain[chain.length - 1].selector;
535
+ return _atomic_testing_core.locatorUtil.append(chain.slice(0, -1), (0, _atomic_testing_core.byCssSelector)(`label:has(${selfSelector})`));
536
+ }
537
+ get driverName() {
538
+ return "MuiV9CheckboxDriver";
539
+ }
540
+ };
541
+ //#endregion
542
+ //#region src/components/ChipDriver.ts
543
+ const parts$9 = {
544
+ contentDisplay: {
545
+ locator: (0, _atomic_testing_core.byCssClass)("MuiChip-label"),
546
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
547
+ },
548
+ removeButton: {
549
+ locator: (0, _atomic_testing_core.byDataTestId)("CancelIcon"),
550
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
551
+ }
552
+ };
553
+ /**
554
+ * Driver for Material UI v9 Chip component.
555
+ * @see https://mui.com/material-ui/react-chip/
556
+ */
557
+ var ChipDriver = class extends _atomic_testing_core.ComponentDriver {
558
+ constructor(locator, interactor, option) {
559
+ super(locator, interactor, {
560
+ ...option,
561
+ parts: parts$9
562
+ });
563
+ }
564
+ /**
565
+ * Get the label content of the chip.
566
+ * @returns The label text content of the chip.
567
+ */
568
+ async getLabel() {
569
+ await this.enforcePartExistence("contentDisplay");
570
+ return await this.parts.contentDisplay.getText() ?? null;
571
+ }
572
+ async clickRemove() {
573
+ await this.enforcePartExistence("removeButton");
574
+ await this.parts.removeButton.click();
575
+ }
576
+ get driverName() {
577
+ return "MuiV9ChipDriver";
578
+ }
579
+ };
580
+ //#endregion
581
+ //#region src/components/DialogDriver.ts
582
+ const parts$8 = {
583
+ title: {
584
+ locator: (0, _atomic_testing_core.byCssClass)("MuiDialogTitle-root"),
585
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
586
+ },
587
+ dialogContainer: {
588
+ locator: (0, _atomic_testing_core.byRole)("presentation"),
589
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
590
+ }
591
+ };
592
+ const dialogRootLocator = (0, _atomic_testing_core.byRole)("presentation", "Root");
593
+ const defaultTransitionDuration$1 = 250;
594
+ var DialogDriver = class extends _atomic_testing_core.ContainerDriver {
595
+ constructor(locator, interactor, option) {
596
+ super(locator, interactor, {
597
+ ...option,
598
+ parts: parts$8,
599
+ content: option?.content ?? {}
600
+ });
601
+ }
602
+ overriddenParentLocator() {
603
+ return dialogRootLocator;
604
+ }
605
+ overrideLocatorRelativePosition() {
606
+ return "Same";
607
+ }
608
+ async getTitle() {
609
+ await this.enforcePartExistence("title");
610
+ return await this.parts.title.getText() ?? null;
611
+ }
612
+ /**
613
+ * Dismiss the dialog by clicking outside its content, then wait for it to close.
614
+ *
615
+ * MUI's "backdrop click" is handled on the `.MuiDialog-container` surface (which
616
+ * overlays the visual `.MuiBackdrop-root`), firing `onClose` only when the click
617
+ * target is the container itself. The click therefore lands on the container near
618
+ * its top-left corner to avoid the centered paper. Whether it actually closes
619
+ * depends on the consumer's `onClose` handling (MUI reports a `"backdropClick"`
620
+ * reason); the returned boolean reflects the observed close, not merely the click.
621
+ *
622
+ * @param timeoutMs How long to wait for the close transition to finish
623
+ * @returns true if the dialog closed
624
+ */
625
+ async closeByBackdropClick(timeoutMs = defaultTransitionDuration$1) {
626
+ await this.enforcePartExistence("dialogContainer");
627
+ const cornerClick = { position: {
628
+ x: 5,
629
+ y: 5
630
+ } };
631
+ await this.parts.dialogContainer.mouseDown(cornerClick);
632
+ await this.parts.dialogContainer.mouseUp(cornerClick);
633
+ await this.parts.dialogContainer.click(cornerClick);
634
+ return this.waitForClose(timeoutMs);
635
+ }
636
+ /**
637
+ * Wait for dialog to open
638
+ * @param timeoutMs
639
+ * @returns true open has performed successfully
640
+ */
641
+ async waitForOpen(timeoutMs = defaultTransitionDuration$1) {
642
+ return await this.interactor.waitUntil({
643
+ probeFn: () => this.isOpen(),
644
+ terminateCondition: true,
645
+ timeoutMs
646
+ }) === true;
647
+ }
648
+ /**
649
+ * Wait for dialog to close
650
+ * @param timeoutMs
651
+ * @returns true open has performed successfully
652
+ */
653
+ async waitForClose(timeoutMs = defaultTransitionDuration$1) {
654
+ return await this.interactor.waitUntil({
655
+ probeFn: () => this.isOpen(),
656
+ terminateCondition: false,
657
+ timeoutMs
658
+ }) === false;
659
+ }
660
+ /**
661
+ * Check if the dialog box is open. Caution, because of animation, upon an open/close action is performed
662
+ * use waitForOpen() or waitForClose() before using isOpen() would result a more accurate open state of the dialog
663
+ * @returns true if dialog box is open
664
+ */
665
+ async isOpen() {
666
+ if (!await this.exists()) return false;
667
+ return await this.interactor.isVisible(this.parts.dialogContainer.locator);
668
+ }
669
+ get driverName() {
670
+ return "MuiV9DialogDriver";
671
+ }
672
+ };
673
+ //#endregion
674
+ //#region src/components/OverlayDriver.ts
675
+ const backdropLocator = (0, _atomic_testing_core.byCssClass)("MuiBackdrop-root");
676
+ const defaultTransitionDuration = 250;
677
+ /**
678
+ * Shared base for MUI portal-rendered overlays (Drawer today; Dialog, Menu,
679
+ * Popover and SpeedDial are prospective consumers). It owns the open/close
680
+ * lifecycle that {@link DialogDriver} first proved out: `isOpen` derived from the
681
+ * visible surface, `waitForOpen`/`waitForClose` spanning the transition, and
682
+ * `closeByBackdrop`.
683
+ *
684
+ * Subclasses supply {@link getSurfaceLocator} (the element whose visibility means
685
+ * "open") and, when the overlay is portal-rendered, override
686
+ * `overriddenParentLocator()`/`overrideLocatorRelativePosition()` to re-root.
687
+ *
688
+ * `closeByEscape` is intentionally absent: keyboard dismissal needs a key-press
689
+ * primitive the `Interactor` interface does not yet expose, which would have to be
690
+ * added to every interactor (DOM/React/Vue/Playwright). It is deferred rather than
691
+ * partially implemented.
692
+ */
693
+ var OverlayDriver = class extends _atomic_testing_core.ContainerDriver {
694
+ /**
695
+ * Locator of the dismissible backdrop. Defaults to MUI's `.MuiBackdrop-root`.
696
+ */
697
+ getBackdropLocator() {
698
+ return _atomic_testing_core.locatorUtil.append(this.locator, backdropLocator);
699
+ }
700
+ /**
701
+ * Whether the overlay is mounted and its surface is visible. Because of open/close
702
+ * transitions, prefer `waitForOpen()`/`waitForClose()` immediately after an action.
703
+ */
704
+ async isOpen() {
705
+ if (!await this.exists()) return false;
706
+ return this.interactor.isVisible(this.getSurfaceLocator());
707
+ }
708
+ /**
709
+ * Wait until the overlay is open.
710
+ * @returns true once open within the timeout.
711
+ */
712
+ async waitForOpen(timeoutMs = defaultTransitionDuration) {
713
+ return await this.interactor.waitUntil({
714
+ probeFn: () => this.isOpen(),
715
+ terminateCondition: true,
716
+ timeoutMs
717
+ }) === true;
718
+ }
719
+ /**
720
+ * Wait until the overlay is closed.
721
+ * @returns true once closed within the timeout.
722
+ */
723
+ async waitForClose(timeoutMs = defaultTransitionDuration) {
724
+ if (await this.interactor.waitUntil({
725
+ probeFn: () => this.isOpen(),
726
+ terminateCondition: false,
727
+ timeoutMs
728
+ }) === false) return true;
729
+ return !await this.isOpen();
730
+ }
731
+ /**
732
+ * Dismiss by clicking the backdrop, then wait for the close transition. Whether
733
+ * it actually closes depends on the consumer honoring the backdrop dismissal; the
734
+ * returned boolean reflects the observed close, not merely the click.
735
+ */
736
+ async closeByBackdrop(timeoutMs = defaultTransitionDuration) {
737
+ const backdrop = this.getBackdropLocator();
738
+ if (await this.interactor.exists(backdrop)) await this.interactor.click(backdrop);
739
+ return this.waitForClose(timeoutMs);
740
+ }
741
+ };
742
+ //#endregion
743
+ //#region src/components/DrawerDriver.ts
744
+ const drawerParts = { paper: {
745
+ locator: (0, _atomic_testing_core.byCssClass)("MuiDrawer-paper"),
746
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
747
+ } };
748
+ const anchorClassByAnchor = {
749
+ left: "MuiDrawer-anchorLeft",
750
+ right: "MuiDrawer-anchorRight",
751
+ top: "MuiDrawer-anchorTop",
752
+ bottom: "MuiDrawer-anchorBottom"
753
+ };
754
+ const drawerRootLocator = (0, _atomic_testing_core.byRole)("presentation", "Root");
755
+ /**
756
+ * Driver for the Material UI v9 Drawer (and SwipeableDrawer) component.
757
+ *
758
+ * A temporary Drawer is a portal-rendered Modal: its root `role="presentation"`
759
+ * (`.MuiDrawer-root`) carries the anchor class and holds a `.MuiBackdrop-root` plus
760
+ * the `.MuiDrawer-paper` panel. Open/close/backdrop behavior is inherited from
761
+ * {@link OverlayDriver}; this driver adds the anchor read and the portal re-rooting.
762
+ * @see https://mui.com/material-ui/react-drawer/
763
+ */
764
+ var DrawerDriver = class extends OverlayDriver {
765
+ constructor(locator, interactor, option) {
766
+ super(locator, interactor, {
767
+ ...option,
768
+ parts: drawerParts,
769
+ content: option?.content ?? {}
770
+ });
771
+ }
772
+ overriddenParentLocator() {
773
+ return drawerRootLocator;
774
+ }
775
+ overrideLocatorRelativePosition() {
776
+ return "Same";
777
+ }
778
+ getSurfaceLocator() {
779
+ return this.parts.paper.locator;
780
+ }
781
+ /**
782
+ * The side the drawer is anchored to, read from the portal root's anchor class, or
783
+ * `undefined` when the drawer is closed/unmounted.
784
+ *
785
+ * The anchor class sits on the `role="presentation"` root itself, so it is read
786
+ * directly off {@link drawerRootLocator} rather than through a part (parts resolve
787
+ * as descendants of that root, but the class is on the root).
788
+ */
789
+ async getAnchor() {
790
+ if (!await this.interactor.exists(drawerRootLocator)) return;
791
+ for (const anchor of Object.keys(anchorClassByAnchor)) if (await this.interactor.hasCssClass(drawerRootLocator, anchorClassByAnchor[anchor])) return anchor;
792
+ }
793
+ get driverName() {
794
+ return "MuiV9DrawerDriver";
795
+ }
796
+ };
797
+ //#endregion
798
+ //#region src/components/FabDriver.ts
799
+ /**
800
+ * Driver for Material UI v9 Floating Action Button component.
801
+ * @see https://mui.com/material-ui/react-floating-action-button/
802
+ */
803
+ var FabDriver = class extends ButtonDriver {
804
+ get driverName() {
805
+ return "MuiV9FabDriver";
806
+ }
807
+ };
808
+ //#endregion
809
+ //#region src/components/InputDriver.ts
810
+ const parts$7 = {
811
+ singlelineInput: {
812
+ locator: (0, _atomic_testing_core.byCssSelector)("input:not([aria-hidden])"),
813
+ driver: _atomic_testing_component_driver_html.HTMLTextInputDriver
814
+ },
815
+ multilineInput: {
816
+ locator: (0, _atomic_testing_core.byCssSelector)("textarea:not([aria-hidden])"),
817
+ driver: _atomic_testing_component_driver_html.HTMLTextInputDriver
818
+ }
819
+ };
820
+ /**
821
+ * A driver for the Material UI v9 Input, FilledInput, OutlinedInput, and StandardInput components.
822
+ */
823
+ var InputDriver = class extends _atomic_testing_core.ComponentDriver {
824
+ constructor(locator, interactor, option) {
825
+ super(locator, interactor, {
826
+ ...option,
827
+ parts: parts$7
828
+ });
829
+ }
830
+ async getInputType() {
831
+ if (await this.interactor.exists(this.parts.singlelineInput.locator)) return "singleLine";
832
+ if (await this.interactor.exists(this.parts.multilineInput.locator)) return "multiline";
833
+ throw new Error("Unable to determine input type in TextFieldInput");
834
+ }
835
+ /**
836
+ * Retrieve the current value of the input element, handling both single line
837
+ * and multiline configurations.
838
+ */
839
+ async getValue() {
840
+ switch (await this.getInputType()) {
841
+ case "singleLine": return this.parts.singlelineInput.getValue();
842
+ case "multiline": return this.parts.multilineInput.getValue();
843
+ }
844
+ }
845
+ /**
846
+ * Set the value of the underlying input element.
847
+ *
848
+ * @param value The text to assign to the input.
849
+ */
850
+ async setValue(value) {
851
+ switch (await this.getInputType()) {
852
+ case "singleLine": return this.parts.singlelineInput.setValue(value);
853
+ case "multiline": return this.parts.multilineInput.setValue(value);
854
+ }
855
+ }
856
+ /**
857
+ * Determine whether the input element is disabled.
858
+ */
859
+ async isDisabled() {
860
+ switch (await this.getInputType()) {
861
+ case "singleLine": return this.parts.singlelineInput.isDisabled();
862
+ case "multiline": return this.parts.multilineInput.isDisabled();
863
+ }
864
+ }
865
+ /**
866
+ * Determine whether the input element is read only.
867
+ */
868
+ async isReadonly() {
869
+ switch (await this.getInputType()) {
870
+ case "singleLine": return this.parts.singlelineInput.isReadonly();
871
+ case "multiline": return this.parts.multilineInput.isReadonly();
872
+ }
873
+ }
874
+ /**
875
+ * Identifier for this driver.
876
+ */
877
+ get driverName() {
878
+ return "MuiV9InputDriver";
879
+ }
880
+ };
881
+ //#endregion
882
+ //#region src/errors/MenuItemDisabledError.ts
883
+ const MenuItemDisabledErrorId = "MenuItemDisabledError";
884
+ function getErrorMessage$1(label) {
885
+ return `The menu item with label: ${label} is disabled`;
886
+ }
887
+ var MenuItemDisabledError = class extends _atomic_testing_core.ErrorBase {
888
+ constructor(label, driver) {
889
+ super(getErrorMessage$1(label), driver);
890
+ this.label = label;
891
+ this.driver = driver;
892
+ this.name = MenuItemDisabledErrorId;
893
+ }
894
+ };
895
+ //#endregion
896
+ //#region src/components/ListItemDriver.ts
897
+ /**
898
+ * @internal
899
+ */
900
+ var ListItemDriver = class extends _atomic_testing_core.ComponentDriver {
901
+ async label() {
902
+ return (await this.getText())?.trim() || null;
903
+ }
904
+ async isSelected() {
905
+ return await this.interactor.hasCssClass(this.locator, "Mui-selected");
906
+ }
907
+ async isDisabled() {
908
+ return await this.interactor.getAttribute(this.locator, "aria-disabled") === "true";
909
+ }
910
+ async click() {
911
+ if (await this.isDisabled()) throw new MenuItemDisabledError(await this.label() ?? "", this);
912
+ await this.interactor.click(this.locator);
913
+ }
914
+ get driverName() {
915
+ return "MuiV9ListItemDriver";
916
+ }
917
+ };
918
+ //#endregion
919
+ //#region src/components/ListDriver.ts
920
+ const defaultListDriverOption = {
921
+ itemClass: ListItemDriver,
922
+ itemLocator: (0, _atomic_testing_core.byRole)("option")
923
+ };
924
+ var ListDriver = class extends _atomic_testing_core.ListComponentDriver {
925
+ constructor(locator, interactor, option = { ...defaultListDriverOption }) {
926
+ super(locator, interactor, option);
927
+ }
928
+ async getSelected() {
929
+ for await (const item of _atomic_testing_core.listHelper.getListItemIterator(this, this.getItemLocator(), ListItemDriver)) if (await item.isSelected()) return item;
930
+ return null;
931
+ }
932
+ get driverName() {
933
+ return "MuiV9ListDriver";
934
+ }
935
+ };
936
+ //#endregion
937
+ //#region src/errors/MenuItemNotFoundError.ts
938
+ const MenuItemNotFoundErrorId = "MenuItemNotFoundError";
939
+ function getErrorMessage(label) {
940
+ return `Cannot find menu item with label: ${label}`;
941
+ }
942
+ var MenuItemNotFoundError = class extends _atomic_testing_core.ErrorBase {
943
+ constructor(label, driver) {
944
+ super(getErrorMessage(label), driver);
945
+ this.label = label;
946
+ this.driver = driver;
947
+ this.name = MenuItemNotFoundErrorId;
948
+ }
949
+ };
950
+ //#endregion
951
+ //#region src/components/MenuItemDriver.ts
952
+ /**
953
+ * @internal
954
+ */
955
+ var MenuItemDriver = class extends ListItemDriver {
956
+ async value() {
957
+ return await this.interactor.getAttribute(this.locator, "data-value") ?? null;
958
+ }
959
+ get driverName() {
960
+ return "MuiV9MenuItemDriver";
961
+ }
962
+ };
963
+ //#endregion
964
+ //#region src/components/MenuDriver.ts
965
+ const parts$6 = { menu: {
966
+ locator: (0, _atomic_testing_core.byRole)("menu"),
967
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
968
+ } };
969
+ const menuRootLocator = (0, _atomic_testing_core.byRole)("presentation", "Root");
970
+ const menuItemLocator = (0, _atomic_testing_core.byRole)("menuitem");
971
+ var MenuDriver = class extends _atomic_testing_core.ComponentDriver {
972
+ constructor(locator, interactor, option) {
973
+ super(locator, interactor, {
974
+ ...option,
975
+ parts: parts$6
976
+ });
977
+ }
978
+ overriddenParentLocator() {
979
+ return menuRootLocator;
980
+ }
981
+ overrideLocatorRelativePosition() {
982
+ return "Same";
983
+ }
984
+ async getMenuItemByLabel(label) {
985
+ for await (const item of _atomic_testing_core.listHelper.getListItemIterator(this, menuItemLocator, MenuItemDriver)) if (await item.label() === label) return item;
986
+ return null;
987
+ }
988
+ async selectByLabel(label) {
989
+ const item = await this.getMenuItemByLabel(label);
990
+ if (item) await item.click();
991
+ else throw new MenuItemNotFoundError(label, this);
992
+ }
993
+ get driverName() {
994
+ return "MuiV9MenuDriver";
995
+ }
996
+ };
997
+ //#endregion
998
+ //#region src/components/PaginationDriver.ts
999
+ const pageItemLocator = (0, _atomic_testing_core.byCssSelector)(".MuiPaginationItem-page");
1000
+ const selectedPageLocator = (0, _atomic_testing_core.byCssSelector)("[aria-current]");
1001
+ const firstButtonLocator = (0, _atomic_testing_core.byAttribute)("aria-label", "Go to first page");
1002
+ const previousButtonLocator$1 = (0, _atomic_testing_core.byAttribute)("aria-label", "Go to previous page");
1003
+ const nextButtonLocator$1 = (0, _atomic_testing_core.byAttribute)("aria-label", "Go to next page");
1004
+ const lastButtonLocator = (0, _atomic_testing_core.byAttribute)("aria-label", "Go to last page");
1005
+ /**
1006
+ * Driver for the Material UI v9 Pagination component.
1007
+ *
1008
+ * Pagination renders a `<nav>` whose `MuiPaginationItem` buttons are each wrapped
1009
+ * in their own `<li>` (so they are not siblings — positional `:nth-of-type`
1010
+ * iteration does not apply). Page controls are read by their accessible name
1011
+ * instead: every page button's aria-label ends in its page number ("Go to page N",
1012
+ * or "page N" once selected), and first/previous/next/last carry stable
1013
+ * aria-labels and become `disabled` at the bounds.
1014
+ * @see https://mui.com/material-ui/react-pagination/
1015
+ */
1016
+ var PaginationDriver = class extends _atomic_testing_core.ComponentDriver {
1017
+ get pageItemsLocator() {
1018
+ return _atomic_testing_core.locatorUtil.append(this.locator, pageItemLocator);
1019
+ }
1020
+ /**
1021
+ * The selected page number, or `-1` when no page is marked current.
1022
+ */
1023
+ async getSelectedPage() {
1024
+ const locator = _atomic_testing_core.locatorUtil.append(this.locator, selectedPageLocator);
1025
+ if (!await this.interactor.exists(locator)) return -1;
1026
+ const text = await this.interactor.getText(locator);
1027
+ const page = Number.parseInt(text?.trim() ?? "", 10);
1028
+ return Number.isNaN(page) ? -1 : page;
1029
+ }
1030
+ /**
1031
+ * Total number of pages, taken from the highest numbered page control. MUI
1032
+ * always renders the upper boundary page (boundaryCount >= 1), so this is exact
1033
+ * even when middle pages collapse into an ellipsis.
1034
+ */
1035
+ async getPageCount() {
1036
+ const labels = await this.interactor.getAttribute(this.pageItemsLocator, "aria-label", true);
1037
+ let max = 0;
1038
+ for (const label of labels) {
1039
+ const match = label?.match(/(\d+)\s*$/);
1040
+ if (match != null) {
1041
+ const page = Number.parseInt(match[1], 10);
1042
+ if (page > max) max = page;
1043
+ }
1044
+ }
1045
+ return max;
1046
+ }
1047
+ /**
1048
+ * Click the numbered control for `page`. A no-op (returns `true`) when `page` is
1049
+ * already selected.
1050
+ * @returns `false` when that page is not currently rendered (e.g. hidden behind
1051
+ * an ellipsis) or is disabled.
1052
+ */
1053
+ async goToPage(page) {
1054
+ if (await this.getSelectedPage() === page) return true;
1055
+ const locator = _atomic_testing_core.locatorUtil.append(this.locator, (0, _atomic_testing_core.byAttribute)("aria-label", `Go to page ${page}`));
1056
+ if (!await this.interactor.exists(locator) || await this.interactor.isDisabled(locator)) return false;
1057
+ await this.interactor.click(locator);
1058
+ return true;
1059
+ }
1060
+ /**
1061
+ * Click a navigation control unless it is absent or disabled (at a bound).
1062
+ * @returns whether the click was performed.
1063
+ */
1064
+ async clickNavButton(navLocator) {
1065
+ const locator = _atomic_testing_core.locatorUtil.append(this.locator, navLocator);
1066
+ if (!await this.interactor.exists(locator) || await this.interactor.isDisabled(locator)) return false;
1067
+ await this.interactor.click(locator);
1068
+ return true;
1069
+ }
1070
+ /** Go to the next page. Returns `false` when on the last page or the control is hidden. */
1071
+ async next() {
1072
+ return this.clickNavButton(nextButtonLocator$1);
1073
+ }
1074
+ /** Go to the previous page. Returns `false` when on the first page or the control is hidden. */
1075
+ async previous() {
1076
+ return this.clickNavButton(previousButtonLocator$1);
1077
+ }
1078
+ /** Jump to the first page. Returns `false` when already there or the control is hidden (needs `showFirstButton`). */
1079
+ async first() {
1080
+ return this.clickNavButton(firstButtonLocator);
1081
+ }
1082
+ /** Jump to the last page. Returns `false` when already there or the control is hidden (needs `showLastButton`). */
1083
+ async last() {
1084
+ return this.clickNavButton(lastButtonLocator);
1085
+ }
1086
+ get driverName() {
1087
+ return "MuiV9PaginationDriver";
1088
+ }
1089
+ };
1090
+ //#endregion
1091
+ //#region src/components/ProgressDriver.ts
1092
+ const parts$5 = { choices: {
1093
+ locator: (0, _atomic_testing_core.byInputType)("radio"),
1094
+ driver: _atomic_testing_component_driver_html.HTMLRadioButtonGroupDriver
1095
+ } };
1096
+ var ProgressDriver = class extends _atomic_testing_core.ComponentDriver {
1097
+ constructor(locator, interactor, option) {
1098
+ super(locator, interactor, {
1099
+ ...option,
1100
+ parts: parts$5
1101
+ });
1102
+ }
1103
+ async getValue() {
1104
+ const rawValue = await this.getAttribute("aria-valuenow");
1105
+ const numValue = Number(rawValue);
1106
+ if (rawValue == null || isNaN(numValue)) return null;
1107
+ return Number(rawValue);
1108
+ }
1109
+ async getType() {
1110
+ if ((await this.getAttribute("class"))?.includes("MuiCircularProgress-root")) return "circular";
1111
+ return "linear";
1112
+ }
1113
+ async isDeterminate() {
1114
+ return await this.getValue() != null;
1115
+ }
1116
+ async setValue(value) {
1117
+ const currentValue = await this.getValue();
1118
+ if (value === currentValue) return true;
1119
+ const valueToClick = value == null ? currentValue : value;
1120
+ const targetLocator = _atomic_testing_core.locatorUtil.append(this.parts.choices.locator, (0, _atomic_testing_core.byValue)(valueToClick.toString(), "Same"));
1121
+ const targetExists = await this.interactor.exists(targetLocator);
1122
+ if (targetExists) {
1123
+ const id = await this.interactor.getAttribute(targetLocator, "id");
1124
+ const labelLocator = _atomic_testing_core.locatorUtil.append(this.locator, (0, _atomic_testing_core.byCssSelector)(`label[for="${id}"]`));
1125
+ await this.interactor.click(labelLocator);
1126
+ }
1127
+ return targetExists;
1128
+ }
1129
+ get driverName() {
1130
+ return "MuiV9ProgressDriver";
1131
+ }
1132
+ };
1133
+ //#endregion
1134
+ //#region src/components/RadioDriver.ts
1135
+ const inputLocator = (0, _atomic_testing_core.byCssSelector)("input");
1136
+ const checkedInputLocator = (0, _atomic_testing_core.byCssSelector)("input:checked");
1137
+ /**
1138
+ * Driver for a single Material UI v9 Radio option (a `FormControlLabel` wrapping a
1139
+ * `Radio`). Its label text is the `FormControlLabel`'s text; selected/value/disabled
1140
+ * state is read from the underlying radio `<input>`.
1141
+ *
1142
+ * Used as the item driver of {@link RadioGroupDriver}, but also usable on its own
1143
+ * when a single radio option is addressed directly. It declares no parts (so it
1144
+ * composes as a list item) and reads the input via descendant locators.
1145
+ * @see https://mui.com/material-ui/react-radio-button/
1146
+ */
1147
+ var RadioDriver = class extends _atomic_testing_core.ComponentDriver {
1148
+ get input() {
1149
+ return _atomic_testing_core.locatorUtil.append(this.locator, inputLocator);
1150
+ }
1151
+ /**
1152
+ * The option's visible label, or `undefined` when it renders without text.
1153
+ */
1154
+ async getLabel() {
1155
+ return (await this.getText())?.trim() || void 0;
1156
+ }
1157
+ /**
1158
+ * The option's `value` attribute.
1159
+ */
1160
+ async getValue() {
1161
+ return await this.interactor.getAttribute(this.input, "value") ?? null;
1162
+ }
1163
+ /**
1164
+ * Whether this option is the selected one in its group.
1165
+ */
1166
+ isSelected() {
1167
+ return this.interactor.exists(_atomic_testing_core.locatorUtil.append(this.locator, checkedInputLocator));
1168
+ }
1169
+ /**
1170
+ * Whether this option is disabled.
1171
+ */
1172
+ isDisabled() {
1173
+ return this.interactor.isDisabled(this.input);
1174
+ }
1175
+ /**
1176
+ * Select this option by clicking its radio input. No-op effect when already selected.
1177
+ */
1178
+ async select() {
1179
+ await this.interactor.click(this.input);
1180
+ }
1181
+ get driverName() {
1182
+ return "MuiV9RadioDriver";
1183
+ }
1184
+ };
1185
+ //#endregion
1186
+ //#region src/components/RadioGroupDriver.ts
1187
+ /**
1188
+ * Radio options are located by their `FormControlLabel` root, which wraps the
1189
+ * radio `<input>` and renders the label text — the unit a {@link RadioDriver}
1190
+ * drives.
1191
+ */
1192
+ const defaultRadioGroupDriverOption = {
1193
+ itemClass: RadioDriver,
1194
+ itemLocator: (0, _atomic_testing_core.byCssSelector)(".MuiFormControlLabel-root")
1195
+ };
1196
+ /**
1197
+ * Driver for the Material UI v9 RadioGroup component.
1198
+ *
1199
+ * `<RadioGroup>` renders a `role="radiogroup"` whose options are `FormControlLabel`s
1200
+ * wrapping a radio `<input>`. This driver is a {@link ListComponentDriver} over those
1201
+ * options, so per-option {@link RadioDriver} instances are available via
1202
+ * `getItems`/`getItemByIndex`/`getItemByLabel`, on top of the group-level value and
1203
+ * label helpers. The selected value is read from the checked `<input>` and selection
1204
+ * is made by value or label.
1205
+ * @see https://mui.com/material-ui/react-radio-button/
1206
+ */
1207
+ var RadioGroupDriver = class extends _atomic_testing_core.ListComponentDriver {
1208
+ constructor(locator, interactor, option = {}) {
1209
+ super(locator, interactor, {
1210
+ ...defaultRadioGroupDriverOption,
1211
+ ...option
1212
+ });
1213
+ }
1214
+ /**
1215
+ * The `value` of the selected option, or `null` when none is selected.
1216
+ */
1217
+ async getValue() {
1218
+ const checked = _atomic_testing_core.locatorUtil.append(this.locator, (0, _atomic_testing_core.byCssSelector)("input:checked"));
1219
+ return await this.interactor.getAttribute(checked, "value") ?? null;
1220
+ }
1221
+ /**
1222
+ * Select the option whose radio `<input>` has the given `value`.
1223
+ * @returns `false` when no option has that value, or when `value` is `null`.
1224
+ */
1225
+ async setValue(value) {
1226
+ if (value == null) return false;
1227
+ const input = _atomic_testing_core.locatorUtil.append(this.locator, (0, _atomic_testing_core.byCssSelector)(`input[value="${_atomic_testing_core.escapeUtil.escapeValue(value)}"]`));
1228
+ if (!await this.interactor.exists(input)) return false;
1229
+ await this.interactor.click(input);
1230
+ return true;
1231
+ }
1232
+ /**
1233
+ * The visible label of every option, in DOM order.
1234
+ */
1235
+ async getOptions() {
1236
+ const items = await this.getItems();
1237
+ const labels = [];
1238
+ for (const item of items) labels.push(await item.getLabel() ?? "");
1239
+ return labels;
1240
+ }
1241
+ /**
1242
+ * The label of the selected option, or `null` when none is selected.
1243
+ */
1244
+ async getSelectedLabel() {
1245
+ const items = await this.getItems();
1246
+ for (const item of items) if (await item.isSelected()) return await item.getLabel() ?? null;
1247
+ return null;
1248
+ }
1249
+ /**
1250
+ * Select the first option whose visible label equals `label`.
1251
+ * @returns `false` when no option matches.
1252
+ */
1253
+ async selectByLabel(label) {
1254
+ const option = await this.getItemByLabel(label);
1255
+ if (option == null) return false;
1256
+ await option.select();
1257
+ return true;
1258
+ }
1259
+ /**
1260
+ * Whether the option with the given label is disabled.
1261
+ * @returns `false` when no option matches.
1262
+ */
1263
+ async isOptionDisabled(label) {
1264
+ const option = await this.getItemByLabel(label);
1265
+ return option == null ? false : option.isDisabled();
1266
+ }
1267
+ get driverName() {
1268
+ return "MuiV9RadioGroupDriver";
1269
+ }
1270
+ };
1271
+ //#endregion
1272
+ //#region src/components/RatingDriver.ts
1273
+ const parts$4 = { choices: {
1274
+ locator: (0, _atomic_testing_core.byInputType)("radio"),
1275
+ driver: _atomic_testing_component_driver_html.HTMLRadioButtonGroupDriver
1276
+ } };
1277
+ /**
1278
+ * MUI marks the disabled/read-only state with a class on the Rating root span
1279
+ * (`Mui-disabled` / `Mui-readOnly`), not on the visually-hidden radio inputs that
1280
+ * the composed {@link HTMLRadioButtonGroupDriver} can see — so these states are
1281
+ * only observable from the root element.
1282
+ */
1283
+ const disabledClassName = "Mui-disabled";
1284
+ const readOnlyClassName = "Mui-readOnly";
1285
+ const filledIconClassName = "MuiRating-iconFilled";
1286
+ var RatingDriver = class extends _atomic_testing_core.ComponentDriver {
1287
+ constructor(locator, interactor, option) {
1288
+ super(locator, interactor, {
1289
+ ...option,
1290
+ parts: parts$4
1291
+ });
1292
+ }
1293
+ async getValue() {
1294
+ if (await this.interactor.hasCssClass(this.locator, readOnlyClassName)) return this.getReadOnlyValue();
1295
+ await this.enforcePartExistence("choices");
1296
+ const value = await this.parts.choices.getValue();
1297
+ if (value == null || value === "") return null;
1298
+ return parseFloat(value);
1299
+ }
1300
+ /**
1301
+ * Read the value of a read-only Rating.
1302
+ *
1303
+ * Primary source is the root's `aria-label`, which MUI populates with the accessible
1304
+ * name (e.g. `"2.5 Stars"`) — accessibility-first and precision-accurate. When a
1305
+ * caller supplies a custom, non-numeric `aria-label`, fall back to counting the
1306
+ * filled star icons; that count is exact for whole-star ratings.
1307
+ */
1308
+ async getReadOnlyValue() {
1309
+ const label = await this.interactor.getAttribute(this.locator, "aria-label");
1310
+ if (label != null) {
1311
+ const parsed = parseFloat(label);
1312
+ if (!Number.isNaN(parsed)) return parsed;
1313
+ }
1314
+ const filledLocator = _atomic_testing_core.locatorUtil.append(this.locator, (0, _atomic_testing_core.byCssClass)(filledIconClassName));
1315
+ const filledIcons = await this.interactor.getAttribute(filledLocator, "class", true);
1316
+ return filledIcons.length > 0 ? filledIcons.length : null;
1317
+ }
1318
+ /**
1319
+ * Whether the Rating is disabled. `disabled` is reflected as the `Mui-disabled`
1320
+ * class on the root span (the composed radio-group driver exposes no `isDisabled`).
1321
+ */
1322
+ async isDisabled() {
1323
+ return this.interactor.hasCssClass(this.locator, disabledClassName);
1324
+ }
1325
+ async setValue(value) {
1326
+ const currentValue = await this.getValue();
1327
+ if (value === currentValue) return true;
1328
+ const valueToClick = value ?? currentValue;
1329
+ if (valueToClick == null) return true;
1330
+ const targetLocator = _atomic_testing_core.locatorUtil.append(this.parts.choices.locator, (0, _atomic_testing_core.byValue)(valueToClick.toString(), "Same"));
1331
+ const targetExists = await this.interactor.exists(targetLocator);
1332
+ if (targetExists) {
1333
+ const id = await this.interactor.getAttribute(targetLocator, "id");
1334
+ const labelLocator = _atomic_testing_core.locatorUtil.append(this.locator, (0, _atomic_testing_core.byCssSelector)(`label[for="${id}"]`));
1335
+ await this.interactor.click(labelLocator);
1336
+ }
1337
+ return targetExists;
1338
+ }
1339
+ get driverName() {
1340
+ return "MuiV9RatingDriver";
1341
+ }
1342
+ };
1343
+ //#endregion
1344
+ //#region src/components/SelectDriver.ts
1345
+ const selectPart = {
1346
+ trigger: {
1347
+ locator: (0, _atomic_testing_core.byRole)("combobox"),
1348
+ driver: _atomic_testing_component_driver_html.HTMLButtonDriver
1349
+ },
1350
+ dropdown: {
1351
+ locator: (0, _atomic_testing_core.byCssSelector)("[role=presentation] [role=listbox]", "Root"),
1352
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
1353
+ },
1354
+ input: {
1355
+ locator: (0, _atomic_testing_core.byTagName)("input"),
1356
+ driver: _atomic_testing_component_driver_html.HTMLTextInputDriver
1357
+ },
1358
+ nativeSelect: {
1359
+ locator: (0, _atomic_testing_core.byTagName)("select"),
1360
+ driver: _atomic_testing_component_driver_html.HTMLSelectDriver
1361
+ }
1362
+ };
1363
+ const optionLocator = (0, _atomic_testing_core.byRole)("option");
1364
+ var SelectDriver = class extends _atomic_testing_core.ComponentDriver {
1365
+ constructor(locator, interactor, option) {
1366
+ super(locator, interactor, {
1367
+ ...option,
1368
+ parts: selectPart
1369
+ });
1370
+ }
1371
+ async isNative() {
1372
+ const nativeSelectExists = await this.interactor.exists(this.parts.nativeSelect.locator);
1373
+ return Promise.resolve(nativeSelectExists);
1374
+ }
1375
+ async getValue() {
1376
+ if (await this.isNative()) return await this.parts.nativeSelect.getValue();
1377
+ await this.enforcePartExistence("input");
1378
+ return await this.parts.input.getValue() ?? null;
1379
+ }
1380
+ async setValue(value) {
1381
+ let success = false;
1382
+ if (await this.isNative()) {
1383
+ success = await this.parts.nativeSelect.setValue(value);
1384
+ return success;
1385
+ }
1386
+ await this.openDropdown();
1387
+ await this.enforcePartExistence("dropdown");
1388
+ const optionSelector = (0, _atomic_testing_core.byAttribute)("data-value", value);
1389
+ const optionLocator = _atomic_testing_core.locatorUtil.append(this.parts.dropdown.locator, optionSelector);
1390
+ if (await this.interactor.exists(optionLocator)) {
1391
+ await this.interactor.click(optionLocator);
1392
+ success = true;
1393
+ }
1394
+ return success;
1395
+ }
1396
+ /**
1397
+ * Select menu item by its label, if it exists
1398
+ * Limitation, this method will not work if the dropdown is a native select.
1399
+ * @param label
1400
+ * @returns
1401
+ */
1402
+ async getMenuItemByLabel(label, option) {
1403
+ if (!option?.skipDropdownCheck) await this.openDropdown();
1404
+ for await (const item of _atomic_testing_core.listHelper.getListItemIterator(this, optionLocator, MenuItemDriver)) if (await item.label() === label) return item;
1405
+ return null;
1406
+ }
1407
+ /**
1408
+ * Selects an option by its label
1409
+ * @param label
1410
+ * @returns
1411
+ */
1412
+ async selectByLabel(label) {
1413
+ if (await this.isNative()) {
1414
+ await this.parts.nativeSelect.selectByLabel(label);
1415
+ return;
1416
+ }
1417
+ await this.enforcePartExistence("trigger");
1418
+ await this.parts.trigger.click();
1419
+ await this.enforcePartExistence("dropdown");
1420
+ const item = await this.getMenuItemByLabel(label, { skipDropdownCheck: true });
1421
+ if (item) await item.click();
1422
+ else throw new MenuItemNotFoundError(label, this);
1423
+ }
1424
+ async getSelectedLabel() {
1425
+ if (await this.isNative()) return await this.parts.nativeSelect.getSelectedLabel();
1426
+ await this.enforcePartExistence("trigger");
1427
+ return await this.parts.trigger.getText() ?? null;
1428
+ }
1429
+ async exists() {
1430
+ if (await this.interactor.exists(this.parts.trigger.locator)) return true;
1431
+ return await this.interactor.exists(this.parts.nativeSelect.locator);
1432
+ }
1433
+ /**
1434
+ * Check if the dropdown is open, or if it is a native select, it is always open because there is no known way check its open state
1435
+ * @returns For native dropdown it is always true. For custom dropdown, it is true if the dropdown is open.
1436
+ */
1437
+ async isDropdownOpen() {
1438
+ if (await this.isNative()) return true;
1439
+ else return this.parts.dropdown.exists();
1440
+ }
1441
+ async openDropdown() {
1442
+ if (await this.isDropdownOpen()) return;
1443
+ await this.parts.trigger.click();
1444
+ }
1445
+ async closeDropdown() {
1446
+ if (!await this.isDropdownOpen()) return;
1447
+ await this.parts.trigger.click();
1448
+ }
1449
+ async isDisabled() {
1450
+ if (await this.isNative()) return this.parts.nativeSelect.isDisabled();
1451
+ else {
1452
+ await this.enforcePartExistence("trigger");
1453
+ return await this.interactor.getAttribute(this.parts.trigger.locator, "aria-disabled") === "true";
1454
+ }
1455
+ }
1456
+ async isReadonly() {
1457
+ if (await this.isNative()) return this.parts.nativeSelect.isReadonly();
1458
+ else return false;
1459
+ }
1460
+ get driverName() {
1461
+ return "MuiV9SelectDriver";
1462
+ }
1463
+ };
1464
+ //#endregion
1465
+ //#region src/components/SliderDriver.ts
1466
+ const parts$3 = { input: {
1467
+ locator: (0, _atomic_testing_core.byCssSelector)("input[type=\"range\"][data-index=\"0\"]"),
1468
+ driver: _atomic_testing_component_driver_html.HTMLTextInputDriver
1469
+ } };
1470
+ var SliderDriver = class extends _atomic_testing_core.ComponentDriver {
1471
+ constructor(locator, interactor, option) {
1472
+ super(locator, interactor, {
1473
+ ...option,
1474
+ parts: parts$3
1475
+ });
1476
+ }
1477
+ /**
1478
+ * Return the first occurrence of the Slider input
1479
+ * @returns
1480
+ */
1481
+ async getValue() {
1482
+ return (await this.getRangeValues(1))[0];
1483
+ }
1484
+ /**
1485
+ * Set slider's range value. Do not use as it will throw an error
1486
+ * @param values
1487
+ * @see https://github.com/atomic-testing/atomic-testing/issues/73
1488
+ */
1489
+ async setValue(value) {
1490
+ return await this.setRangeValues([value]);
1491
+ }
1492
+ async getRangeValues(count) {
1493
+ await this.enforcePartExistence("input");
1494
+ const result = [];
1495
+ let index = 0;
1496
+ let done = false;
1497
+ while (!done) {
1498
+ const locator = _atomic_testing_core.locatorUtil.append(this.locator, this.getInputLocator(index));
1499
+ if (await this.interactor.exists(locator)) {
1500
+ index++;
1501
+ done = count != null && index >= count;
1502
+ const value = await this.interactor.getAttribute(locator, "value");
1503
+ result.push(parseFloat(value));
1504
+ } else done = true;
1505
+ }
1506
+ return result;
1507
+ }
1508
+ getInputLocator(index) {
1509
+ return (0, _atomic_testing_core.byCssSelector)(`input[type="range"][data-index="${index}"]`);
1510
+ }
1511
+ /**
1512
+ * Set slider's range values. Do not use as it will throw an error
1513
+ * @param values
1514
+ * @see https://github.com/atomic-testing/atomic-testing/issues/73
1515
+ */
1516
+ async setRangeValues(_values) {
1517
+ await this.enforcePartExistence("input");
1518
+ throw new Error("setRangeValue is not supported.");
1519
+ }
1520
+ async isDisabled() {
1521
+ await this.enforcePartExistence("input");
1522
+ return await this.parts.input.isDisabled();
1523
+ }
1524
+ get driverName() {
1525
+ return "MuiV9SliderDriver";
1526
+ }
1527
+ };
1528
+ //#endregion
1529
+ //#region src/components/SnackbarDriver.ts
1530
+ const parts$2 = {
1531
+ contentDisplay: {
1532
+ locator: (0, _atomic_testing_core.byCssClass)("MuiSnackbarContent-message"),
1533
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
1534
+ },
1535
+ actionArea: {
1536
+ locator: (0, _atomic_testing_core.byCssClass)("MuiSnackbarContent-action"),
1537
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
1538
+ }
1539
+ };
1540
+ /**
1541
+ * Driver for Material UI v9 Snackbar component.
1542
+ * @see https://mui.com/material-ui/react-snackbar/
1543
+ */
1544
+ var SnackbarDriver = class extends _atomic_testing_core.ComponentDriver {
1545
+ constructor(locator, interactor, option) {
1546
+ super(locator, interactor, {
1547
+ ...option,
1548
+ parts: parts$2
1549
+ });
1550
+ }
1551
+ /**
1552
+ * Get the label content of the snackbar.
1553
+ * @returns The label text content of the snackbar.
1554
+ */
1555
+ async getLabel() {
1556
+ await this.enforcePartExistence("contentDisplay");
1557
+ return await this.parts.contentDisplay.getText() ?? null;
1558
+ }
1559
+ /**
1560
+ * Get a driver instance of a component in the action area of the snackbar.
1561
+ * @param locator
1562
+ * @param driverClass
1563
+ * @returns
1564
+ */
1565
+ async getActionComponent(locator, driverClass) {
1566
+ await this.enforcePartExistence("actionArea");
1567
+ const componentLocator = _atomic_testing_core.locatorUtil.append(this.parts.actionArea.locator, locator);
1568
+ if (await this.interactor.exists(componentLocator)) return new driverClass(componentLocator, this.interactor, this.commutableOption);
1569
+ return null;
1570
+ }
1571
+ get driverName() {
1572
+ return "MuiV9SnackbarDriver";
1573
+ }
1574
+ };
1575
+ //#endregion
1576
+ //#region src/components/SpeedDialDriver.ts
1577
+ const fabLocator = (0, _atomic_testing_core.byCssSelector)(".MuiSpeedDial-fab");
1578
+ const actionLocator = (0, _atomic_testing_core.byCssSelector)(".MuiSpeedDialAction-fab");
1579
+ /**
1580
+ * Driver for the Material UI v9 SpeedDial component.
1581
+ *
1582
+ * SpeedDial renders a trigger FAB (`.MuiSpeedDial-fab`, `aria-expanded` reflects
1583
+ * open state) and a set of action FABs (`.MuiSpeedDialAction-fab`, each
1584
+ * aria-labelled with its name). The actions stay mounted but are only revealed
1585
+ * when open, so open state is read from the FAB's `aria-expanded` rather than
1586
+ * action presence.
1587
+ * @see https://mui.com/material-ui/react-speed-dial/
1588
+ */
1589
+ var SpeedDialDriver = class extends _atomic_testing_core.ComponentDriver {
1590
+ get fab() {
1591
+ return _atomic_testing_core.locatorUtil.append(this.locator, fabLocator);
1592
+ }
1593
+ /**
1594
+ * Whether the speed dial is open (its FAB reports `aria-expanded="true"`).
1595
+ */
1596
+ async isOpen() {
1597
+ return await this.interactor.getAttribute(this.fab, "aria-expanded") === "true";
1598
+ }
1599
+ /**
1600
+ * Open the speed dial by hovering its FAB. No-op when already open.
1601
+ *
1602
+ * Hover (`onMouseEnter`) is the trigger that opens consistently across MUI
1603
+ * versions and browsers — clicking only focuses-then-opens in Chromium, and
1604
+ * focus opens in v7 but not v5, whereas hover opens everywhere.
1605
+ */
1606
+ async open() {
1607
+ if (!await this.isOpen()) await this.interactor.hover(this.fab);
1608
+ }
1609
+ /**
1610
+ * Close the speed dial by moving the pointer off its FAB. No-op when already closed.
1611
+ */
1612
+ async close() {
1613
+ if (await this.isOpen()) await this.interactor.mouseLeave(this.fab);
1614
+ }
1615
+ /**
1616
+ * The labels of every action, in order (read from each action FAB's aria-label).
1617
+ */
1618
+ async getActionLabels() {
1619
+ return (await this.interactor.getAttribute(_atomic_testing_core.locatorUtil.append(this.locator, actionLocator), "aria-label", true)).filter((label) => label != null);
1620
+ }
1621
+ /**
1622
+ * Trigger the action with the given label, opening the dial first if needed.
1623
+ * @returns `false` when no action has that label.
1624
+ */
1625
+ async triggerActionByLabel(label) {
1626
+ await this.open();
1627
+ const actionByLabel = (0, _atomic_testing_core.byCssSelector)(`.MuiSpeedDialAction-fab[aria-label="${_atomic_testing_core.escapeUtil.escapeValue(label)}"]`);
1628
+ const locator = _atomic_testing_core.locatorUtil.append(this.locator, actionByLabel);
1629
+ if (!await this.interactor.exists(locator)) return false;
1630
+ await this.interactor.click(locator);
1631
+ return true;
1632
+ }
1633
+ get driverName() {
1634
+ return "MuiV9SpeedDialDriver";
1635
+ }
1636
+ };
1637
+ //#endregion
1638
+ //#region src/components/StepperDriver.ts
1639
+ const stepLabelLocator = (0, _atomic_testing_core.byCssSelector)(".MuiStepLabel-label");
1640
+ /**
1641
+ * Locator for the label of the step at `index`. In Material UI v9 the connector
1642
+ * is rendered *inside* each `.MuiStep-root` (in v7 it was an interleaved sibling),
1643
+ * so the step elements are now consecutive siblings and the step at `index` is the
1644
+ * `index+1`-th element of its type; nth-of-type addresses it directly.
1645
+ */
1646
+ function stepLabelAt(index) {
1647
+ return (0, _atomic_testing_core.byCssSelector)(`.MuiStep-root:nth-of-type(${index + 1}) .MuiStepLabel-label`);
1648
+ }
1649
+ function stepButtonAt(index) {
1650
+ return (0, _atomic_testing_core.byCssSelector)(`.MuiStep-root:nth-of-type(${index + 1}) .MuiStepButton-root`);
1651
+ }
1652
+ /**
1653
+ * Driver for the Material UI v9 Stepper component.
1654
+ *
1655
+ * Each step's state lives on its `.MuiStepLabel-label` (`Mui-active`,
1656
+ * `Mui-completed`, `Mui-disabled`); reading every label's class in one pass gives
1657
+ * the step states and count in document order. Steps are addressed positionally
1658
+ * for label text and clicks via {@link stepLabelAt}/{@link stepButtonAt}.
1659
+ *
1660
+ * Supported layouts: the default horizontal stepper and the vertical orientation.
1661
+ * In v9 every `.MuiStep-root` is a consecutive sibling (the connector lives inside
1662
+ * the step), and every step renders a text label, so positional addressing lines up
1663
+ * with the document-order class list. The unsupported case is icon-only steps, which
1664
+ * render no `.MuiStepLabel-label`; under those the document-order class list has fewer
1665
+ * entries than steps and the two desynchronize. Robustly supporting that needs an
1666
+ * interactor primitive to address the n-th of non-sibling matches
1667
+ * (CSS `:nth-child(of S)` is unsupported in jsdom), which is out of this driver's scope.
1668
+ * @see https://mui.com/material-ui/react-stepper/
1669
+ */
1670
+ var StepperDriver = class extends _atomic_testing_core.ComponentDriver {
1671
+ async getStepClassList() {
1672
+ return this.interactor.getAttribute(_atomic_testing_core.locatorUtil.append(this.locator, stepLabelLocator), "class", true);
1673
+ }
1674
+ /**
1675
+ * The number of steps.
1676
+ */
1677
+ async getStepCount() {
1678
+ return (await this.getStepClassList()).length;
1679
+ }
1680
+ /**
1681
+ * Zero-based index of the active step, or `-1` when none is active.
1682
+ */
1683
+ async getActiveStepIndex() {
1684
+ return (await this.getStepClassList()).findIndex((c) => c.split(/\s+/).includes("Mui-active"));
1685
+ }
1686
+ /**
1687
+ * Every step with its label and active/completed/disabled state, in order.
1688
+ */
1689
+ async getSteps() {
1690
+ const classes = await this.getStepClassList();
1691
+ const steps = [];
1692
+ for (let index = 0; index < classes.length; index++) {
1693
+ const stateClasses = classes[index].split(/\s+/);
1694
+ const label = await this.interactor.getText(_atomic_testing_core.locatorUtil.append(this.locator, stepLabelAt(index)));
1695
+ steps.push({
1696
+ label: label?.trim(),
1697
+ active: stateClasses.includes("Mui-active"),
1698
+ completed: stateClasses.includes("Mui-completed"),
1699
+ disabled: stateClasses.includes("Mui-disabled")
1700
+ });
1701
+ }
1702
+ return steps;
1703
+ }
1704
+ /**
1705
+ * Navigate to the step at `index` by clicking its control (requires a clickable
1706
+ * `StepButton`, e.g. a non-linear stepper).
1707
+ * @returns `false` when the step is out of range, has no button, or is disabled.
1708
+ */
1709
+ async goToStep(index) {
1710
+ if (index < 0 || index >= await this.getStepCount()) return false;
1711
+ const button = _atomic_testing_core.locatorUtil.append(this.locator, stepButtonAt(index));
1712
+ if (!await this.interactor.exists(button) || await this.interactor.isDisabled(button)) return false;
1713
+ await this.interactor.click(button);
1714
+ return true;
1715
+ }
1716
+ get driverName() {
1717
+ return "MuiV9StepperDriver";
1718
+ }
1719
+ };
1720
+ //#endregion
1721
+ //#region src/components/SwitchDriver.ts
1722
+ const parts$1 = { input: {
1723
+ locator: (0, _atomic_testing_core.byAttribute)("type", "checkbox"),
1724
+ driver: _atomic_testing_component_driver_html.HTMLCheckboxDriver
1725
+ } };
1726
+ var SwitchDriver = class extends _atomic_testing_core.ComponentDriver {
1727
+ constructor(locator, interactor, option) {
1728
+ super(locator, interactor, {
1729
+ ...option,
1730
+ parts: parts$1
1731
+ });
1732
+ }
1733
+ async exists() {
1734
+ return this.interactor.exists(this.parts.input.locator);
1735
+ }
1736
+ async isSelected() {
1737
+ await this.enforcePartExistence("input");
1738
+ return this.parts.input.isSelected();
1739
+ }
1740
+ async setSelected(selected) {
1741
+ await this.enforcePartExistence("input");
1742
+ await this.parts.input.setSelected(selected);
1743
+ }
1744
+ async getValue() {
1745
+ await this.enforcePartExistence("input");
1746
+ return this.parts.input.getValue();
1747
+ }
1748
+ async isDisabled() {
1749
+ await this.enforcePartExistence("input");
1750
+ return this.parts.input.isDisabled();
1751
+ }
1752
+ async isReadonly() {
1753
+ return Promise.resolve(false);
1754
+ }
1755
+ get driverName() {
1756
+ return "MuiV9SwitchDriver";
1757
+ }
1758
+ };
1759
+ //#endregion
1760
+ //#region src/components/TableCellDriver.ts
1761
+ /**
1762
+ * Driver for a single Material UI v9 TableCell (`<td>`/`<th>`, `.MuiTableCell-root`).
1763
+ *
1764
+ * A cell's content is plain text, so it relies on the inherited `getText()`/`exists()`;
1765
+ * it exists as a distinct type so {@link TableRowDriver} can expose typed cell drivers.
1766
+ * @see https://mui.com/material-ui/react-table/
1767
+ */
1768
+ var TableCellDriver = class extends _atomic_testing_core.ComponentDriver {
1769
+ get driverName() {
1770
+ return "MuiV9TableCellDriver";
1771
+ }
1772
+ };
1773
+ //#endregion
1774
+ //#region src/components/TableRowDriver.ts
1775
+ /**
1776
+ * Cells are located by `.MuiTableCell-root`, covering both `<td>` body cells and
1777
+ * `<th>` header cells.
1778
+ */
1779
+ const defaultTableRowDriverOption = {
1780
+ itemClass: TableCellDriver,
1781
+ itemLocator: (0, _atomic_testing_core.byCssSelector)(".MuiTableCell-root")
1782
+ };
1783
+ /**
1784
+ * Driver for a single Material UI v9 TableRow (`.MuiTableRow-root`).
1785
+ *
1786
+ * A {@link ListComponentDriver} over the row's cells, exposing per-cell
1787
+ * {@link TableCellDriver}s (via `getItems`/`getItemByIndex`) plus convenience reads
1788
+ * of the cell texts.
1789
+ *
1790
+ * Cells are addressed positionally via `:nth-of-type`, which counts per element type.
1791
+ * This assumes a row's cells are homogeneous (all `<td>`, or all `<th>` for a header
1792
+ * row) — the common MUI rendering. A row mixing a leading `<th scope="row">` with
1793
+ * `<td>` data cells would desynchronize the index (the `<td>`s start at type-index 1
1794
+ * after the `<th>`); such rows are out of scope.
1795
+ * @see https://mui.com/material-ui/react-table/
1796
+ */
1797
+ var TableRowDriver = class extends _atomic_testing_core.ListComponentDriver {
1798
+ constructor(locator, interactor, option = {}) {
1799
+ super(locator, interactor, {
1800
+ ...option,
1801
+ ...defaultTableRowDriverOption
1802
+ });
1803
+ }
1804
+ /**
1805
+ * The number of cells in the row.
1806
+ */
1807
+ async getCellCount() {
1808
+ return this.getItemCount();
1809
+ }
1810
+ /**
1811
+ * The cell driver at the given zero-based column index, or `null` when out of range.
1812
+ */
1813
+ async getCell(index) {
1814
+ return this.getItemByIndex(index);
1815
+ }
1816
+ /**
1817
+ * The text of every cell, in column order.
1818
+ */
1819
+ async getCellTexts() {
1820
+ const cells = await this.getItems();
1821
+ const texts = [];
1822
+ for (const cell of cells) texts.push((await cell.getText())?.trim() ?? "");
1823
+ return texts;
1824
+ }
1825
+ get driverName() {
1826
+ return "MuiV9TableRowDriver";
1827
+ }
1828
+ };
1829
+ //#endregion
1830
+ //#region src/components/TableDriver.ts
1831
+ const headerRowLocator = (0, _atomic_testing_core.byCssSelector)(".MuiTableHead-root .MuiTableRow-root");
1832
+ /**
1833
+ * Body rows are located under `.MuiTableBody-root`, so header rows are never
1834
+ * mistaken for data rows.
1835
+ */
1836
+ const defaultTableDriverOption = {
1837
+ itemClass: TableRowDriver,
1838
+ itemLocator: (0, _atomic_testing_core.byCssSelector)(".MuiTableBody-root .MuiTableRow-root")
1839
+ };
1840
+ function headerCellAt(columnIndex) {
1841
+ return (0, _atomic_testing_core.byCssSelector)(`.MuiTableHead-root .MuiTableRow-root .MuiTableCell-root:nth-of-type(${columnIndex + 1})`);
1842
+ }
1843
+ /**
1844
+ * Driver for the Material UI v9 Table component.
1845
+ *
1846
+ * A {@link ListComponentDriver} over the data rows (`.MuiTableBody-root > .MuiTableRow-root`),
1847
+ * exposing per-row {@link TableRowDriver}s plus header reads and column sort state.
1848
+ * Locators key off MUI's structural classes (`MuiTableHead/Body/Row/Cell-root`),
1849
+ * which are stable across v9. Sort state is read from the header cell's `aria-sort`
1850
+ * and driven by clicking its `TableSortLabel`.
1851
+ * @see https://mui.com/material-ui/react-table/
1852
+ */
1853
+ var TableDriver = class extends _atomic_testing_core.ListComponentDriver {
1854
+ constructor(locator, interactor, option = {}) {
1855
+ super(locator, interactor, {
1856
+ ...defaultTableDriverOption,
1857
+ ...option
1858
+ });
1859
+ }
1860
+ /**
1861
+ * The number of data (body) rows.
1862
+ */
1863
+ async getRowCount() {
1864
+ return this.getItemCount();
1865
+ }
1866
+ /**
1867
+ * The data-row driver at the given zero-based index, or `null` when out of range.
1868
+ */
1869
+ async getRow(index) {
1870
+ return this.getItemByIndex(index);
1871
+ }
1872
+ /**
1873
+ * The header row as a {@link TableRowDriver}, or `null` when the table has no header.
1874
+ */
1875
+ async getHeaderRow() {
1876
+ const locator = _atomic_testing_core.locatorUtil.append(this.locator, headerRowLocator);
1877
+ if (!await this.interactor.exists(locator)) return null;
1878
+ return new TableRowDriver(locator, this.interactor);
1879
+ }
1880
+ /**
1881
+ * The number of columns, derived from the header cell count (0 when no header).
1882
+ */
1883
+ async getColumnCount() {
1884
+ const header = await this.getHeaderRow();
1885
+ return header == null ? 0 : header.getCellCount();
1886
+ }
1887
+ /**
1888
+ * The header cell texts, in column order (empty when no header).
1889
+ */
1890
+ async getHeaderTexts() {
1891
+ const header = await this.getHeaderRow();
1892
+ return header == null ? [] : header.getCellTexts();
1893
+ }
1894
+ /**
1895
+ * The text of the data cell at the given row/column, or `undefined` when either
1896
+ * index is out of range.
1897
+ */
1898
+ async getCellText(rowIndex, columnIndex) {
1899
+ const row = await this.getRow(rowIndex);
1900
+ if (row == null) return;
1901
+ const cell = await row.getCell(columnIndex);
1902
+ if (cell == null) return;
1903
+ return (await cell.getText())?.trim();
1904
+ }
1905
+ /**
1906
+ * The sort direction applied to the column at `columnIndex`, read from the header
1907
+ * cell's `aria-sort`, or `undefined` when that column is not the sorted one.
1908
+ */
1909
+ async getSortDirection(columnIndex) {
1910
+ const cell = headerCellAt(columnIndex);
1911
+ const fullLocator = _atomic_testing_core.locatorUtil.append(this.locator, cell);
1912
+ if (!await this.interactor.exists(fullLocator)) return;
1913
+ const ariaSort = await this.interactor.getAttribute(fullLocator, "aria-sort");
1914
+ return ariaSort === "ascending" || ariaSort === "descending" ? ariaSort : void 0;
1915
+ }
1916
+ /**
1917
+ * Toggle/apply sorting on the column at `columnIndex` by clicking its
1918
+ * `TableSortLabel`.
1919
+ * @returns `false` when the column has no sort control.
1920
+ */
1921
+ async sortByColumn(columnIndex) {
1922
+ const sortLabel = _atomic_testing_core.locatorUtil.append(this.locator, (0, _atomic_testing_core.byCssSelector)(`.MuiTableHead-root .MuiTableRow-root .MuiTableCell-root:nth-of-type(${columnIndex + 1}) .MuiTableSortLabel-root`));
1923
+ if (!await this.interactor.exists(sortLabel)) return false;
1924
+ await this.interactor.click(sortLabel);
1925
+ return true;
1926
+ }
1927
+ get driverName() {
1928
+ return "MuiV9TableDriver";
1929
+ }
1930
+ };
1931
+ //#endregion
1932
+ //#region src/components/TablePaginationDriver.ts
1933
+ const previousButtonLocator = (0, _atomic_testing_core.byAttribute)("aria-label", "Go to previous page");
1934
+ const nextButtonLocator = (0, _atomic_testing_core.byAttribute)("aria-label", "Go to next page");
1935
+ const displayedRowsLocator = (0, _atomic_testing_core.byCssSelector)(".MuiTablePagination-displayedRows");
1936
+ /**
1937
+ * Driver for the Material UI v9 TablePagination component.
1938
+ *
1939
+ * TablePagination is a composite: a "rows per page" MUI Select (a portal-backed
1940
+ * `role="combobox"`), an aria-labelled previous/next pair that disables at the
1941
+ * bounds, and a `.MuiTablePagination-displayedRows` label ("1–5 of 13"). The
1942
+ * rows-per-page control is delegated to {@link SelectDriver} rather than
1943
+ * reimplemented — the driver's own root contains exactly one combobox/input, so
1944
+ * SelectDriver scoped to that root resolves it unambiguously.
1945
+ * @see https://mui.com/material-ui/react-pagination/#table-pagination
1946
+ */
1947
+ var TablePaginationDriver = class extends _atomic_testing_core.ComponentDriver {
1948
+ get rowsPerPageSelect() {
1949
+ return new SelectDriver(this.locator, this.interactor);
1950
+ }
1951
+ /**
1952
+ * The current rows-per-page value (read from the select's hidden input), or
1953
+ * `-1` when it cannot be parsed.
1954
+ */
1955
+ async getRowsPerPage() {
1956
+ const value = await this.rowsPerPageSelect.getValue();
1957
+ const parsed = Number.parseInt(value ?? "", 10);
1958
+ return Number.isNaN(parsed) ? -1 : parsed;
1959
+ }
1960
+ /**
1961
+ * Choose a rows-per-page value by opening the select and picking the option.
1962
+ * @returns `false` when no such option exists.
1963
+ */
1964
+ async setRowsPerPage(rowsPerPage) {
1965
+ return this.rowsPerPageSelect.setValue(String(rowsPerPage));
1966
+ }
1967
+ /**
1968
+ * The raw "displayed rows" label (e.g. "1–5 of 13"), or `undefined` when absent.
1969
+ * Returned verbatim because its exact format (separator, "of") is locale-defined.
1970
+ */
1971
+ async getDisplayedRowsText() {
1972
+ const locator = _atomic_testing_core.locatorUtil.append(this.locator, displayedRowsLocator);
1973
+ if (!await this.interactor.exists(locator)) return;
1974
+ return (await this.interactor.getText(locator))?.trim();
1975
+ }
1976
+ /** Whether the previous-page control is disabled (i.e. on the first page). */
1977
+ async isPreviousDisabled() {
1978
+ return this.isNavDisabled(previousButtonLocator);
1979
+ }
1980
+ /** Whether the next-page control is disabled (i.e. on the last page). */
1981
+ async isNextDisabled() {
1982
+ return this.isNavDisabled(nextButtonLocator);
1983
+ }
1984
+ /**
1985
+ * An absent control counts as disabled — both to stay consistent with
1986
+ * {@link clickNavButton} (which reports a no-op for a missing control) and
1987
+ * because Playwright's `isDisabled` throws on a zero-match locator rather than
1988
+ * returning false the way jsdom does.
1989
+ */
1990
+ async isNavDisabled(navLocator) {
1991
+ const locator = _atomic_testing_core.locatorUtil.append(this.locator, navLocator);
1992
+ if (!await this.interactor.exists(locator)) return true;
1993
+ return this.interactor.isDisabled(locator);
1994
+ }
1995
+ /**
1996
+ * Advance to the next page unless the control is disabled (last page).
1997
+ * @returns whether the click was performed.
1998
+ */
1999
+ async nextPage() {
2000
+ return this.clickNavButton(nextButtonLocator);
2001
+ }
2002
+ /**
2003
+ * Go back to the previous page unless the control is disabled (first page).
2004
+ * @returns whether the click was performed.
2005
+ */
2006
+ async previousPage() {
2007
+ return this.clickNavButton(previousButtonLocator);
2008
+ }
2009
+ async clickNavButton(navLocator) {
2010
+ const locator = _atomic_testing_core.locatorUtil.append(this.locator, navLocator);
2011
+ if (!await this.interactor.exists(locator) || await this.interactor.isDisabled(locator)) return false;
2012
+ await this.interactor.click(locator);
2013
+ return true;
2014
+ }
2015
+ get driverName() {
2016
+ return "MuiV9TablePaginationDriver";
2017
+ }
2018
+ };
2019
+ //#endregion
2020
+ //#region src/components/TabDriver.ts
2021
+ /**
2022
+ * Driver for a single Material UI v9 Tab.
2023
+ *
2024
+ * A `<Tab>` renders as a `<button role="tab">`; MUI marks the active one with
2025
+ * `aria-selected="true"` and renders a real `disabled` button when disabled, so
2026
+ * selection and disabled state are read straight off the accessible attributes
2027
+ * (`isDisabled` is inherited from {@link HTMLButtonDriver}; `getText`/`click` from
2028
+ * the base `ComponentDriver`).
2029
+ *
2030
+ * Unlike a toggle button this is intentionally not an `IToggleDriver`: a tab can
2031
+ * be selected but not toggled off (selecting another tab deselects it), so only
2032
+ * `isSelected`/`select` are exposed.
2033
+ * @see https://mui.com/material-ui/react-tabs/
2034
+ */
2035
+ var TabDriver = class extends _atomic_testing_component_driver_html.HTMLButtonDriver {
2036
+ /**
2037
+ * Whether this tab is the selected one, i.e. MUI set `aria-selected="true"`.
2038
+ */
2039
+ async isSelected() {
2040
+ return await this.interactor.getAttribute(this.locator, "aria-selected") === "true";
2041
+ }
2042
+ /**
2043
+ * Activate this tab by clicking it, unless it is already selected. A selected
2044
+ * tab cannot be toggled off, so this is a no-op when already active.
2045
+ */
2046
+ async select() {
2047
+ if (!await this.isSelected()) await this.interactor.click(this.locator);
2048
+ }
2049
+ get driverName() {
2050
+ return "MuiV9TabDriver";
2051
+ }
2052
+ };
2053
+ //#endregion
2054
+ //#region src/components/TabsDriver.ts
2055
+ /**
2056
+ * Tabs are located by their accessible `role="tab"` children rather than by MUI
2057
+ * class names, so the driver is resilient to MUI styling/version changes.
2058
+ */
2059
+ const defaultTabsDriverOption = {
2060
+ itemClass: TabDriver,
2061
+ itemLocator: (0, _atomic_testing_core.byRole)("tab")
2062
+ };
2063
+ /**
2064
+ * Driver for the Material UI v9 Tabs component.
2065
+ *
2066
+ * `<Tabs>` renders a `role="tablist"` whose `role="tab"` buttons carry the
2067
+ * selected (`aria-selected`) and disabled state. This driver is a
2068
+ * {@link ListComponentDriver} over those tabs, exposing both group-level helpers
2069
+ * (selected index/label, select by index/label) and per-tab {@link TabDriver}
2070
+ * instances via `getItems`/`getItemByIndex`/`getItemByLabel`.
2071
+ * @see https://mui.com/material-ui/react-tabs/
2072
+ */
2073
+ var TabsDriver = class extends _atomic_testing_core.ListComponentDriver {
2074
+ constructor(locator, interactor, option = {}) {
2075
+ super(locator, interactor, {
2076
+ ...defaultTabsDriverOption,
2077
+ ...option
2078
+ });
2079
+ }
2080
+ /**
2081
+ * The visible label of every tab, in DOM order.
2082
+ */
2083
+ async getTabLabels() {
2084
+ const labels = [];
2085
+ for await (const tab of _atomic_testing_core.listHelper.getListItemIterator(this, this.getItemLocator(), TabDriver)) {
2086
+ const text = await tab.getText();
2087
+ labels.push(text?.trim() ?? "");
2088
+ }
2089
+ return labels;
2090
+ }
2091
+ /**
2092
+ * The number of tabs in the group.
2093
+ */
2094
+ async getTabCount() {
2095
+ return this.getItemCount();
2096
+ }
2097
+ /**
2098
+ * Zero-based index of the selected tab, or `-1` when no tab is selected
2099
+ * (e.g. `<Tabs value={false}>`).
2100
+ */
2101
+ async getSelectedIndex() {
2102
+ let index = 0;
2103
+ for await (const tab of _atomic_testing_core.listHelper.getListItemIterator(this, this.getItemLocator(), TabDriver)) {
2104
+ if (await tab.isSelected()) return index;
2105
+ index++;
2106
+ }
2107
+ return -1;
2108
+ }
2109
+ /**
2110
+ * Label of the selected tab, or `null` when no tab is selected. Returns `null`
2111
+ * (not `undefined`) to match the sibling `SelectDriver.getSelectedLabel` contract.
2112
+ */
2113
+ async getSelectedLabel() {
2114
+ for await (const tab of _atomic_testing_core.listHelper.getListItemIterator(this, this.getItemLocator(), TabDriver)) if (await tab.isSelected()) return (await tab.getText())?.trim() ?? null;
2115
+ return null;
2116
+ }
2117
+ /**
2118
+ * Select the tab at the given zero-based index.
2119
+ * @returns `false` when the index is out of range.
2120
+ */
2121
+ async selectByIndex(index) {
2122
+ const tab = await this.getItemByIndex(index);
2123
+ if (tab == null) return false;
2124
+ await tab.click();
2125
+ return true;
2126
+ }
2127
+ /**
2128
+ * Select the first tab whose visible label equals `label`.
2129
+ * @returns `false` when no tab matches.
2130
+ */
2131
+ async selectByLabel(label) {
2132
+ const tab = await this.getItemByLabel(label);
2133
+ if (tab == null) return false;
2134
+ await tab.click();
2135
+ return true;
2136
+ }
2137
+ /**
2138
+ * Whether the tab at the given index is disabled.
2139
+ * @returns `false` when the index is out of range.
2140
+ */
2141
+ async isTabDisabled(index) {
2142
+ const tab = await this.getItemByIndex(index);
2143
+ return tab == null ? false : tab.isDisabled();
2144
+ }
2145
+ get driverName() {
2146
+ return "MuiV9TabsDriver";
2147
+ }
2148
+ };
2149
+ //#endregion
2150
+ //#region src/components/TextFieldDriver.ts
2151
+ const parts = {
2152
+ label: {
2153
+ locator: (0, _atomic_testing_core.byCssSelector)(">.MuiInputLabel-root"),
2154
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
2155
+ },
2156
+ helperText: {
2157
+ locator: (0, _atomic_testing_core.byCssSelector)(">p"),
2158
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
2159
+ },
2160
+ singlelineInput: {
2161
+ locator: (0, _atomic_testing_core.byCssSelector)("input:not([aria-hidden])"),
2162
+ driver: _atomic_testing_component_driver_html.HTMLTextInputDriver
2163
+ },
2164
+ multilineInput: {
2165
+ locator: (0, _atomic_testing_core.byCssSelector)("textarea:not([aria-hidden])"),
2166
+ driver: _atomic_testing_component_driver_html.HTMLTextInputDriver
2167
+ },
2168
+ selectInput: {
2169
+ locator: (0, _atomic_testing_core.byCssSelector)(">.MuiInputBase-root"),
2170
+ driver: SelectDriver
2171
+ },
2172
+ richSelectInputDetect: {
2173
+ locator: (0, _atomic_testing_core.byRole)("combobox"),
2174
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
2175
+ },
2176
+ nativeSelectInputDetect: {
2177
+ locator: (0, _atomic_testing_core.byTagName)("SELECT"),
2178
+ driver: _atomic_testing_component_driver_html.HTMLElementDriver
2179
+ }
2180
+ };
2181
+ /**
2182
+ * A driver for the Material UI v9 TextField component with single line or multiline text input.
2183
+ */
2184
+ var TextFieldDriver = class extends _atomic_testing_core.ComponentDriver {
2185
+ constructor(locator, interactor, option) {
2186
+ super(locator, interactor, {
2187
+ ...option,
2188
+ parts
2189
+ });
2190
+ }
2191
+ async getInputType() {
2192
+ return await Promise.all([
2193
+ this.parts.singlelineInput.exists(),
2194
+ this.parts.richSelectInputDetect.exists(),
2195
+ this.parts.nativeSelectInputDetect.exists(),
2196
+ this.parts.multilineInput.exists()
2197
+ ]).then(([singlelineExists, richSelectExists, nativeSelectExists, multilineExists]) => {
2198
+ if (singlelineExists) return "singleLine";
2199
+ if (richSelectExists || nativeSelectExists) return "select";
2200
+ if (multilineExists) return "multiline";
2201
+ throw new Error("Unable to determine input type in TextFieldInput");
2202
+ });
2203
+ }
2204
+ async getValue() {
2205
+ switch (await this.getInputType()) {
2206
+ case "singleLine": return this.parts.singlelineInput.getValue();
2207
+ case "select": return this.parts.selectInput.getValue();
2208
+ case "multiline": return this.parts.multilineInput.getValue();
2209
+ }
2210
+ }
2211
+ async setValue(value) {
2212
+ switch (await this.getInputType()) {
2213
+ case "singleLine": return this.parts.singlelineInput.setValue(value);
2214
+ case "select": return this.parts.selectInput.setValue(value);
2215
+ case "multiline": return this.parts.multilineInput.setValue(value);
2216
+ }
2217
+ }
2218
+ async getLabel() {
2219
+ return this.parts.label.getText();
2220
+ }
2221
+ async getHelperText() {
2222
+ if (await this.interactor.exists(this.parts.helperText.locator)) return this.parts.helperText.getText();
2223
+ }
2224
+ async isDisabled() {
2225
+ switch (await this.getInputType()) {
2226
+ case "singleLine": return this.parts.singlelineInput.isDisabled();
2227
+ case "select": return this.parts.selectInput.isDisabled();
2228
+ case "multiline": return this.parts.multilineInput.isDisabled();
2229
+ }
2230
+ }
2231
+ async isReadonly() {
2232
+ switch (await this.getInputType()) {
2233
+ case "singleLine": return this.parts.singlelineInput.isReadonly();
2234
+ case "select": return this.interactor.hasCssClass(this.parts.selectInput.locator, "MuiInputBase-readOnly");
2235
+ case "multiline": return this.parts.multilineInput.isReadonly();
2236
+ }
2237
+ }
2238
+ get driverName() {
2239
+ return "MuiV9TextFieldDriver";
2240
+ }
2241
+ };
2242
+ //#endregion
2243
+ //#region src/components/ToggleButtonDriver.ts
2244
+ var ToggleButtonDriver = class extends ButtonDriver {
2245
+ async isSelected() {
2246
+ return await this.interactor.getAttribute(this.locator, "aria-pressed") === "true";
2247
+ }
2248
+ async setSelected(targetState) {
2249
+ if (await this.isSelected() !== targetState) await this.interactor.click(this.locator);
2250
+ }
2251
+ get driverName() {
2252
+ return "MuiV9ToggleButtonDriver";
2253
+ }
2254
+ };
2255
+ //#endregion
2256
+ //#region src/components/ToggleButtonGroupDriver.ts
2257
+ const toggleButtonLocator = (0, _atomic_testing_core.byTagName)("button");
2258
+ var ToggleButtonGroupDriver = class extends _atomic_testing_core.ComponentDriver {
2259
+ constructor(..._args) {
2260
+ super(..._args);
2261
+ this.itemLocator = _atomic_testing_core.locatorUtil.append(this.locator, toggleButtonLocator);
2262
+ }
2263
+ /**
2264
+ * Get all the selected toggle buttons' values.
2265
+ * @returns
2266
+ */
2267
+ async getValue() {
2268
+ const result = [];
2269
+ for await (const itemDriver of _atomic_testing_core.listHelper.getListItemIterator(this, this.itemLocator, ToggleButtonDriver)) if (await itemDriver.isSelected()) {
2270
+ const value = await itemDriver.getValue();
2271
+ if (value != null) result.push(value);
2272
+ }
2273
+ return result;
2274
+ }
2275
+ /**
2276
+ * Toggle all the toggle buttons such that only those with value in the given array are selected.
2277
+ * @param value Always true
2278
+ * @returns
2279
+ */
2280
+ async setValue(value) {
2281
+ const valueSet = new Set(value);
2282
+ for await (const itemDriver of _atomic_testing_core.listHelper.getListItemIterator(this, this.itemLocator, ToggleButtonDriver)) {
2283
+ const value = await itemDriver.getValue();
2284
+ await itemDriver.setSelected(valueSet.has(value));
2285
+ }
2286
+ return true;
2287
+ }
2288
+ get driverName() {
2289
+ return "MuiV9ToggleButtonGroupDriver";
2290
+ }
2291
+ };
2292
+ /**
2293
+ * A toggle button group driver that only allows a single selection.
2294
+ *
2295
+ * INTENTIONAL @ts-ignore comments below: This class intentionally narrows the return type
2296
+ * from `readonly string[]` to `string | null` for exclusive selection mode. TypeScript
2297
+ * correctly flags this as a type incompatibility because the subclass changes the interface
2298
+ * contract. However, this design is intentional as exclusive and multi-select toggle groups
2299
+ * have fundamentally different value semantics, and we want the type system to reflect
2300
+ * `string | null` for exclusive mode rather than forcing consumers to work with arrays.
2301
+ */
2302
+ var ExclusiveToggleButtonGroupDriver = class extends ToggleButtonGroupDriver {
2303
+ async getValue() {
2304
+ return (await super.getValue())?.[0] ?? null;
2305
+ }
2306
+ async setValue(value) {
2307
+ if (value === null) return super.setValue([]);
2308
+ else return super.setValue([value]);
2309
+ }
2310
+ get driverName() {
2311
+ return "MuiV9ExclusiveToggleButtonGroupDriver";
2312
+ }
2313
+ };
2314
+ //#endregion
2315
+ //#region src/components/TooltipDriver.ts
2316
+ const tooltipLocator = (0, _atomic_testing_core.byRole)("tooltip", "Root");
2317
+ const defaultRevealTimeout = 250;
2318
+ /**
2319
+ * Driver for the Material UI v9 Tooltip component.
2320
+ *
2321
+ * The driver is rooted at the **trigger** element. MUI shows the tooltip on
2322
+ * hover/focus and renders it into a portal (a `role="tooltip"` popper) outside the
2323
+ * trigger's subtree, so the text is read from the document root via
2324
+ * {@link tooltipLocator} after revealing it. The tooltip unmounts when not shown,
2325
+ * so presence of that element doubles as the visible state.
2326
+ *
2327
+ * Note: hover reveal depends on the tooltip's `enterDelay`; with a non-zero delay
2328
+ * the reveal is timer-bound. {@link getTitle} waits for the tooltip to appear.
2329
+ * @see https://mui.com/material-ui/react-tooltip/
2330
+ */
2331
+ var TooltipDriver = class extends _atomic_testing_core.ComponentDriver {
2332
+ /**
2333
+ * Reveal the tooltip by hovering its trigger, waiting until it is shown (or the
2334
+ * timeout elapses, e.g. when the trigger has no tooltip).
2335
+ */
2336
+ async show(timeoutMs = defaultRevealTimeout) {
2337
+ await this.interactor.hover(this.locator);
2338
+ await this.interactor.waitUntil({
2339
+ probeFn: () => this.isVisible(),
2340
+ terminateCondition: true,
2341
+ timeoutMs
2342
+ });
2343
+ }
2344
+ /**
2345
+ * Hide the tooltip by moving the pointer off its trigger, waiting until it is gone.
2346
+ */
2347
+ async hide(timeoutMs = defaultRevealTimeout) {
2348
+ await this.interactor.mouseLeave(this.locator);
2349
+ await this.interactor.waitUntil({
2350
+ probeFn: () => this.isVisible(),
2351
+ terminateCondition: false,
2352
+ timeoutMs
2353
+ });
2354
+ }
2355
+ /**
2356
+ * Whether a tooltip is currently shown.
2357
+ */
2358
+ async isVisible() {
2359
+ if (!await this.interactor.exists(tooltipLocator)) return false;
2360
+ return this.interactor.isVisible(tooltipLocator);
2361
+ }
2362
+ /**
2363
+ * Reveal the tooltip, read its text, then restore the un-hovered state. Returns
2364
+ * `undefined` when the trigger has no tooltip (none appears within the timeout).
2365
+ *
2366
+ * The hover is undone before returning so a subsequent read on a *different*
2367
+ * trigger isn't shadowed by this still-open tooltip: MUI renders all tooltips into
2368
+ * one portal and provides no per-trigger DOM link, and jsdom's synthetic hover does
2369
+ * not fire `mouseout` on the previously-hovered sibling.
2370
+ *
2371
+ * @param timeoutMs How long to wait for the tooltip to appear after hovering.
2372
+ */
2373
+ async getTitle(timeoutMs = defaultRevealTimeout) {
2374
+ await this.show(timeoutMs);
2375
+ if (!await this.interactor.exists(tooltipLocator)) return;
2376
+ const text = (await this.interactor.getText(tooltipLocator))?.trim();
2377
+ await this.hide(timeoutMs);
2378
+ return text;
2379
+ }
2380
+ get driverName() {
2381
+ return "MuiV9TooltipDriver";
2382
+ }
2383
+ };
2384
+ //#endregion
2385
+ exports.AccordionDriver = AccordionDriver;
2386
+ exports.AlertDriver = AlertDriver;
2387
+ exports.AutoCompleteDriver = AutoCompleteDriver;
2388
+ exports.AvatarDriver = AvatarDriver;
2389
+ exports.AvatarGroupDriver = AvatarGroupDriver;
2390
+ exports.BadgeDriver = BadgeDriver;
2391
+ exports.BottomNavigationActionDriver = BottomNavigationActionDriver;
2392
+ exports.BottomNavigationDriver = BottomNavigationDriver;
2393
+ exports.ButtonDriver = ButtonDriver;
2394
+ exports.CheckboxDriver = CheckboxDriver;
2395
+ exports.ChipDriver = ChipDriver;
2396
+ exports.DialogDriver = DialogDriver;
2397
+ exports.DrawerDriver = DrawerDriver;
2398
+ exports.ExclusiveToggleButtonGroupDriver = ExclusiveToggleButtonGroupDriver;
2399
+ exports.FabDriver = FabDriver;
2400
+ exports.InputDriver = InputDriver;
2401
+ exports.ListDriver = ListDriver;
2402
+ exports.ListItemDriver = ListItemDriver;
2403
+ exports.MenuDriver = MenuDriver;
2404
+ exports.MenuItemDisabledError = MenuItemDisabledError;
2405
+ exports.MenuItemDisabledErrorId = MenuItemDisabledErrorId;
2406
+ exports.MenuItemDriver = MenuItemDriver;
2407
+ exports.MenuItemNotFoundError = MenuItemNotFoundError;
2408
+ exports.MenuItemNotFoundErrorId = MenuItemNotFoundErrorId;
2409
+ exports.OverlayDriver = OverlayDriver;
2410
+ exports.PaginationDriver = PaginationDriver;
2411
+ exports.ProgressDriver = ProgressDriver;
2412
+ exports.RadioDriver = RadioDriver;
2413
+ exports.RadioGroupDriver = RadioGroupDriver;
2414
+ exports.RatingDriver = RatingDriver;
2415
+ exports.SelectDriver = SelectDriver;
2416
+ exports.SliderDriver = SliderDriver;
2417
+ exports.SnackbarDriver = SnackbarDriver;
2418
+ exports.SpeedDialDriver = SpeedDialDriver;
2419
+ exports.StepperDriver = StepperDriver;
2420
+ exports.SwitchDriver = SwitchDriver;
2421
+ exports.TabDriver = TabDriver;
2422
+ exports.TableCellDriver = TableCellDriver;
2423
+ exports.TableDriver = TableDriver;
2424
+ exports.TablePaginationDriver = TablePaginationDriver;
2425
+ exports.TableRowDriver = TableRowDriver;
2426
+ exports.TabsDriver = TabsDriver;
2427
+ exports.TextFieldDriver = TextFieldDriver;
2428
+ exports.ToggleButtonDriver = ToggleButtonDriver;
2429
+ exports.ToggleButtonGroupDriver = ToggleButtonGroupDriver;
2430
+ exports.TooltipDriver = TooltipDriver;
2431
+ exports.defaultAutoCompleteDriverOption = defaultAutoCompleteDriverOption;
2432
+ exports.defaultAvatarGroupDriverOption = defaultAvatarGroupDriverOption;
2433
+ exports.defaultRadioGroupDriverOption = defaultRadioGroupDriverOption;
2434
+ exports.defaultTableDriverOption = defaultTableDriverOption;
2435
+ exports.defaultTableRowDriverOption = defaultTableRowDriverOption;
2436
+ exports.defaultTabsDriverOption = defaultTabsDriverOption;
2437
+ exports.drawerParts = drawerParts;
2438
+
2439
+ //# sourceMappingURL=index.cjs.map