UNPKG

@atomic-testing/component-driver-mui-v6

Version:

Atomic Testing Component driver to help drive Material UI V6 components

1 lines 72.6 kB
{"version":3,"file":"index.mjs","names":["parts","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","parts","alertSeverityEvaluators: AlertSeverityEvaluator[]","locator: PartLocator","interactor: Interactor","option?: Partial<IContainerDriverOption>","parts","optionLocator","defaultAutoCompleteDriverOption: AutoCompleteDriverSpecificOption","locator: PartLocator","interactor: Interactor","option?: Partial<AutoCompleteDriverOption>","value: string | null","matchType: AutoCompleteMatchType","parts","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","selected: boolean","parts","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","parts","dialogRootLocator: PartLocator","locator: PartLocator","interactor: Interactor","option?: Partial<IContainerDriverOption>","timeoutMs: number","parts","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","value: string | null","getErrorMessage","label: string","driver: ComponentDriver<any>","defaultListDriverOption: ListComponentDriverSpecificOption<ListItemDriver>","locator: PartLocator","interactor: Interactor","option: ListComponentDriverSpecificOption<ItemT> & Partial<IComponentDriverOption<any>>","label: string","driver: ComponentDriver<any>","parts","menuRootLocator: PartLocator","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","label: string","parts","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","value: number | null","parts","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","value: number | null","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","value: string | null","optionLocator","label: string","option?: MenuItemGetOption","parts","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","value: number","count?: number","result: number[]","index: number","_values: readonly number[]","parts","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","driverClass: ComponentDriverCtor<ItemClass>","parts","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","selected: boolean","locator: PartLocator","interactor: Interactor","option?: Partial<IComponentDriverOption>","value: string | null","targetState: boolean","result: string[]","value: readonly string[]","value","value: string | null"],"sources":["../src/components/AccordionDriver.ts","../src/components/AlertDriver.ts","../src/components/AutoCompleteDriver.ts","../src/components/BadgeDriver.ts","../src/components/ButtonDriver.ts","../src/components/CheckboxDriver.ts","../src/components/ChipDriver.ts","../src/components/DialogDriver.ts","../src/components/FabDriver.ts","../src/components/InputDriver.ts","../src/errors/MenuItemDisabledError.ts","../src/components/ListItemDriver.ts","../src/components/ListDriver.ts","../src/errors/MenuItemNotFoundError.ts","../src/components/MenuItemDriver.ts","../src/components/MenuDriver.ts","../src/components/ProgressDriver.ts","../src/components/RatingDriver.ts","../src/components/SelectDriver.ts","../src/components/SliderDriver.ts","../src/components/SnackbarDriver.ts","../src/components/SwitchDriver.ts","../src/components/TextFieldDriver.ts","../src/components/ToggleButtonDriver.ts","../src/components/ToggleButtonGroupDriver.ts"],"sourcesContent":["import { HTMLElementDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssClass,\n byRole,\n ComponentDriver,\n IComponentDriverOption,\n Interactor,\n PartLocator,\n ScenePart,\n timingUtil,\n} from '@atomic-testing/core';\n\nexport const parts = {\n /**\n * The clickable area to expand/collapse the accordion.\n */\n disclosure: {\n locator: byCssClass('MuiAccordionSummary-root'),\n driver: HTMLElementDriver,\n },\n summary: {\n locator: byCssClass('MuiAccordionSummary-content'),\n driver: HTMLElementDriver,\n },\n content: {\n locator: byRole('region'),\n driver: HTMLElementDriver,\n },\n} satisfies ScenePart;\n\n/**\n * Driver for Material UI v6 Accordion component.\n * @see https://mui.com/material-ui/react-accordion/\n */\nexport class AccordionDriver extends ComponentDriver<typeof parts> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts: parts,\n });\n }\n\n /**\n * Get the title/summary of the accordion.\n * @returns The title/summary of the accordion.\n */\n async getSummary(): Promise<string | null> {\n const title = await this.parts.summary.getText();\n return title ?? null;\n }\n\n /**\n * Whether the accordion is expanded.\n * @returns True if the accordion is expanded, false if collapsed.\n */\n async isExpanded(): Promise<boolean> {\n await this.enforcePartExistence('disclosure');\n const expanded = await this.parts.disclosure.getAttribute('aria-expanded');\n return expanded === 'true';\n }\n\n /**\n * Whether the accordion is disabled.\n * @returns True if the accordion is disabled, false if enabled.\n */\n async isDisabled(): Promise<boolean> {\n await this.enforcePartExistence('disclosure');\n const disabled = await this.parts.disclosure.getAttribute('disabled');\n return disabled != null;\n }\n\n /**\n * Expand the accordion.\n */\n async expand(): Promise<void> {\n const expanded = await this.isExpanded();\n if (!expanded) {\n await this.parts.disclosure.click();\n await timingUtil.waitUntil({\n probeFn: () => this.isExpanded(),\n terminateCondition: true,\n timeoutMs: 1000,\n });\n }\n }\n\n /**\n * Collapse the accordion.\n */\n async collapse(): Promise<void> {\n const expanded = await this.isExpanded();\n if (expanded) {\n await this.parts.disclosure.click();\n await timingUtil.waitUntil({\n probeFn: () => this.isExpanded(),\n terminateCondition: false,\n timeoutMs: 1000,\n });\n }\n }\n\n override get driverName(): string {\n return 'MuiV6AccordionDriver';\n }\n}\n","import { HTMLElementDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssClass,\n ContainerDriver,\n IContainerDriverOption,\n Interactor,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nexport const parts = {\n title: {\n locator: byCssClass('MuiAlertTitle-root'),\n driver: HTMLElementDriver,\n },\n message: {\n locator: byCssClass('MuiAlert-message'),\n driver: HTMLElementDriver,\n },\n} satisfies ScenePart;\n\ninterface AlertSeverityEvaluator {\n value: string;\n pattern: RegExp;\n}\nconst alertSeverityEvaluators: AlertSeverityEvaluator[] = [\n { value: 'error', pattern: /MuiAlert-.*Error/ },\n { value: 'warning', pattern: /MuiAlert-.*Warning/ },\n { value: 'info', pattern: /MuiAlert-.*Info/ },\n { value: 'success', pattern: /MuiAlert-.*Success/ },\n];\n\n/**\n * Driver for Material UI v6 Alert component.\n * @see https://mui.com/material-ui/react-alert/\n */\nexport class AlertDriver<ContentT extends ScenePart = {}> extends ContainerDriver<ContentT, typeof parts> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IContainerDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts: parts,\n content: (option?.content ?? {}) as ContentT,\n });\n }\n\n async getTitle(): Promise<string | null> {\n const title = await this.parts.title.getText();\n return title ?? null;\n }\n\n async getMessage(): Promise<string | null> {\n const message = await this.parts.message.getText();\n return message ?? null;\n }\n\n async getSeverity(): Promise<string | null> {\n const cssClassString = await this.interactor.getAttribute(this.locator, 'class');\n if (cssClassString != null) {\n const cssClasses = cssClassString.split(/\\s+/);\n for (const cssClassName of cssClasses) {\n for (const evaluator of alertSeverityEvaluators) {\n if (evaluator.pattern.test(cssClassName)) {\n return evaluator.value;\n }\n }\n }\n }\n return null;\n }\n\n override get driverName(): string {\n return 'MuiV6AlertDriver';\n }\n}\n","import { HTMLButtonDriver, HTMLElementDriver, HTMLTextInputDriver } from '@atomic-testing/component-driver-html';\nimport {\n byLinkedElement,\n byRole,\n ComponentDriver,\n IComponentDriverOption,\n IInputDriver,\n Interactor,\n listHelper,\n locatorUtil,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nexport const parts = {\n input: {\n locator: byRole('combobox'),\n driver: HTMLTextInputDriver,\n },\n dropdown: {\n locator: byLinkedElement('Root')\n .onLinkedElement(byRole('combobox'))\n .extractAttribute('aria-controls')\n .toMatchMyAttribute('id'),\n driver: HTMLElementDriver,\n },\n} satisfies ScenePart;\n\nconst optionLocator = byRole('option');\n\n/**\n * The match type of the autocomplete, default to 'exact'\n * 'exact': The value must match exactly to one of the options\n * 'first-available': The value will be set to the first available option\n */\nexport type AutoCompleteMatchType = 'exact' | 'first-available';\n\nexport interface AutoCompleteDriverSpecificOption {\n matchType: AutoCompleteMatchType;\n}\n\nexport interface AutoCompleteDriverOption extends IComponentDriverOption, AutoCompleteDriverSpecificOption {}\n\nexport const defaultAutoCompleteDriverOption: AutoCompleteDriverSpecificOption = {\n matchType: 'exact',\n};\n\nexport class AutoCompleteDriver extends ComponentDriver<typeof parts> implements IInputDriver<string | null> {\n private _option: Partial<AutoCompleteDriverOption> = {};\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<AutoCompleteDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts,\n });\n\n this._option = option ?? {};\n }\n\n /**\n * Get the display of the autocomplete\n */\n async getValue(): Promise<string | null> {\n const value = await this.parts.input.getValue();\n return value ?? null;\n }\n\n /**\n * Set the value of the autocomplete, how selection happens\n * depends on the option assigned to AutoCompleteDriver\n * By default, when the option has matchType set to exact, only option with matching text would be selected\n * When the option has matchType set to first-available, the first option would be selected regardless of the text\n *\n * Option of auto complete can be set at the time of part definition, for example\n * ```\n * {\n * myAutoComplete: {\n * locator: byCssSelector('my-auto-complete'),\n * driver: AutoCompleteDriver,\n * option: {\n * matchType: 'first-available',\n * },\n * },\n * }\n * ```\n *\n * @param value\n * @returns\n */\n async setValue(value: string | null): Promise<boolean> {\n await this.parts.input.setValue(value ?? '');\n\n if (value === null) {\n return true;\n }\n\n const option = locatorUtil.append(this.parts.dropdown.locator, optionLocator);\n let index = 0;\n const matchType: AutoCompleteMatchType = this._option?.matchType ?? defaultAutoCompleteDriverOption.matchType;\n for await (const optionDriver of listHelper.getListItemIterator(this, option, HTMLButtonDriver)) {\n const optionValue = await optionDriver.getText();\n const isMatched =\n (matchType === 'exact' && optionValue?.trim() === value) || (matchType === 'first-available' && index === 0);\n if (isMatched) {\n await optionDriver.click();\n return true;\n }\n\n index++;\n }\n\n return false;\n }\n\n async isDisabled(): Promise<boolean> {\n return this.parts.input.isDisabled();\n }\n\n async isReadonly(): Promise<boolean> {\n return this.parts.input.isReadonly();\n }\n\n get driverName(): string {\n return 'MuiV6AutoCompleteDriver';\n }\n}\n","import { HTMLElementDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssClass,\n ComponentDriver,\n IComponentDriverOption,\n Interactor,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nexport const parts = {\n contentDisplay: {\n locator: byCssClass('MuiBadge-badge'),\n driver: HTMLElementDriver,\n },\n} satisfies ScenePart;\n\n/**\n * Driver for Material UI v6 Badge component.\n * @see https://mui.com/material-ui/react-badge/\n */\nexport class BadgeDriver extends ComponentDriver<typeof parts> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts: parts,\n });\n }\n\n /**\n * Get the content of the badge.\n * @returns The content of the badge.\n */\n async getContent(): Promise<string | null> {\n await this.enforcePartExistence('contentDisplay');\n const content = await this.parts.contentDisplay.getText();\n return content ?? null;\n }\n\n override get driverName(): string {\n return 'MuiV6BadgeDriver';\n }\n}\n","import { HTMLButtonDriver } from '@atomic-testing/component-driver-html';\n\n/**\n * Driver for Material UI v6 Button component.\n * @see https://mui.com/material-ui/react-button/\n */\nexport class ButtonDriver extends HTMLButtonDriver {\n async getValue(): Promise<string | null> {\n const val = await this.interactor.getAttribute(this.locator, 'value');\n return val ?? null;\n }\n\n override get driverName(): string {\n return 'MuiV6ButtonDriver';\n }\n}\n","import { HTMLCheckboxDriver } from '@atomic-testing/component-driver-html';\nimport {\n byTagName,\n ComponentDriver,\n IComponentDriverOption,\n IFormFieldDriver,\n Interactor,\n IToggleDriver,\n PartLocator,\n ScenePart,\n ScenePartDriver,\n} from '@atomic-testing/core';\n\nexport const checkboxPart = {\n checkbox: {\n locator: byTagName('input'),\n driver: HTMLCheckboxDriver,\n },\n} satisfies ScenePart;\n\nexport type CheckboxScenePart = typeof checkboxPart;\nexport type CheckboxScenePartDriver = ScenePartDriver<CheckboxScenePart>;\n\nexport class CheckboxDriver\n extends ComponentDriver<CheckboxScenePart>\n implements IFormFieldDriver<string | null>, IToggleDriver\n{\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts: checkboxPart,\n });\n }\n isSelected(): Promise<boolean> {\n return this.parts.checkbox.isSelected();\n }\n async setSelected(selected: boolean): Promise<void> {\n const isIndeterminate = await this.isIndeterminate();\n if (isIndeterminate && selected === false) {\n // if the checkbox is indeterminate and we want to set it to false, we need to click it twice\n // this is done through setting it to true first, then to false\n await this.parts.checkbox.setSelected(true);\n }\n\n await this.parts.checkbox.setSelected(selected);\n }\n\n getValue(): Promise<string | null> {\n return this.parts.checkbox.getValue();\n }\n\n async isIndeterminate(): Promise<boolean> {\n const indeterminate = await this.interactor.getAttribute(this.parts.checkbox.locator, 'data-indeterminate');\n return indeterminate === 'true';\n }\n\n isDisabled(): Promise<boolean> {\n return this.parts.checkbox.isDisabled();\n }\n\n isReadonly(): Promise<boolean> {\n return this.parts.checkbox.isReadonly();\n }\n\n get driverName(): string {\n return 'MuiV6CheckboxDriver';\n }\n}\n","import { HTMLElementDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssClass,\n byDataTestId,\n ComponentDriver,\n IComponentDriverOption,\n Interactor,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nexport const parts = {\n contentDisplay: {\n locator: byCssClass('MuiChip-label'),\n driver: HTMLElementDriver,\n },\n removeButton: {\n locator: byDataTestId('CancelIcon'),\n driver: HTMLElementDriver,\n },\n} satisfies ScenePart;\n\n/**\n * Driver for Material UI v6 Chip component.\n * @see https://mui.com/material-ui/react-chip/\n */\nexport class ChipDriver extends ComponentDriver<typeof parts> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts: parts,\n });\n }\n\n /**\n * Get the label content of the chip.\n * @returns The label text content of the chip.\n */\n async getLabel(): Promise<string | null> {\n await this.enforcePartExistence('contentDisplay');\n const content = await this.parts.contentDisplay.getText();\n return content ?? null;\n }\n\n async clickRemove(): Promise<void> {\n await this.enforcePartExistence('removeButton');\n await this.parts.removeButton.click();\n }\n\n override get driverName(): string {\n return 'MuiV6ChipDriver';\n }\n}\n","import { HTMLElementDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssClass,\n byRole,\n ContainerDriver,\n IContainerDriverOption,\n Interactor,\n type LocatorRelativePosition,\n Optional,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nexport const parts = {\n title: {\n locator: byCssClass('MuiDialogTitle-root'),\n driver: HTMLElementDriver,\n },\n dialogContainer: {\n locator: byRole('presentation'),\n driver: HTMLElementDriver,\n },\n} satisfies ScenePart;\n\nconst dialogRootLocator: PartLocator = byRole('presentation', 'Root');\n\nconst defaultTransitionDuration = 250;\n\nexport class DialogDriver<ContentT extends ScenePart> extends ContainerDriver<ContentT, typeof parts> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IContainerDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts: parts,\n content: (option?.content ?? {}) as ContentT,\n });\n }\n\n override overriddenParentLocator(): Optional<PartLocator> {\n return dialogRootLocator;\n }\n\n override overrideLocatorRelativePosition(): Optional<LocatorRelativePosition> {\n return 'Same';\n }\n\n async getTitle(): Promise<string | null> {\n await this.enforcePartExistence('title');\n const title = await this.parts.title.getText();\n return title ?? null;\n }\n\n /**\n * Wait for dialog to open\n * @param timeoutMs\n * @returns true open has performed successfully\n */\n async waitForOpen(timeoutMs: number = defaultTransitionDuration): Promise<boolean> {\n const isOpened = await this.interactor.waitUntil({\n probeFn: () => this.isOpen(),\n terminateCondition: true,\n timeoutMs,\n });\n return isOpened === true;\n }\n\n /**\n * Wait for dialog to close\n * @param timeoutMs\n * @returns true open has performed successfully\n */\n async waitForClose(timeoutMs: number = defaultTransitionDuration): Promise<boolean> {\n const isOpened = await this.interactor.waitUntil({\n probeFn: () => this.isOpen(),\n terminateCondition: false,\n timeoutMs,\n });\n return isOpened === false;\n }\n\n /**\n * Check if the dialog box is open. Caution, because of animation, upon an open/close action is performed\n * use waitForOpen() or waitForClose() before using isOpen() would result a more accurate open state of the dialog\n * @returns true if dialog box is open\n */\n async isOpen(): Promise<boolean> {\n const exists = await this.exists();\n if (!exists) {\n return false;\n }\n const isVisible = await this.interactor.isVisible(this.parts.dialogContainer.locator);\n return isVisible;\n }\n\n get driverName(): string {\n return 'MuiV6DialogDriver';\n }\n}\n","import { ButtonDriver } from './ButtonDriver';\n\n/**\n * Driver for Material UI v6 Floating Action Button component.\n * @see https://mui.com/material-ui/react-floating-action-button/\n */\nexport class FabDriver extends ButtonDriver {\n override get driverName(): string {\n return 'MuiV6FabDriver';\n }\n}\n","import { HTMLTextInputDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssSelector,\n ComponentDriver,\n IComponentDriverOption,\n IInputDriver,\n Interactor,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nexport const parts = {\n singlelineInput: {\n locator: byCssSelector('input:not([aria-hidden])'),\n driver: HTMLTextInputDriver,\n },\n multilineInput: {\n locator: byCssSelector('textarea:not([aria-hidden])'),\n driver: HTMLTextInputDriver,\n },\n} satisfies ScenePart;\n\ntype TextFieldInputType = 'singleLine' | 'multiline';\n\n/**\n * A driver for the Material UI v6 Input, FilledInput, OutlinedInput, and StandardInput components.\n */\nexport class InputDriver extends ComponentDriver<typeof parts> implements IInputDriver<string | null> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts,\n });\n }\n\n private async getInputType(): Promise<TextFieldInputType> {\n // TODO: Detection of both input types can be done in parallel.\n const textInputExists = await this.interactor.exists(this.parts.singlelineInput.locator);\n if (textInputExists) {\n return 'singleLine';\n }\n\n const multilineExists = await this.interactor.exists(this.parts.multilineInput.locator);\n if (multilineExists) {\n return 'multiline';\n }\n\n throw new Error('Unable to determine input type in TextFieldInput');\n }\n\n /**\n * Retrieve the current value of the input element, handling both single line\n * and multiline configurations.\n */\n async getValue(): Promise<string | null> {\n const inputType = await this.getInputType();\n switch (inputType) {\n case 'singleLine':\n return this.parts.singlelineInput.getValue();\n case 'multiline':\n return this.parts.multilineInput.getValue();\n }\n }\n\n /**\n * Set the value of the underlying input element.\n *\n * @param value The text to assign to the input.\n */\n async setValue(value: string | null): Promise<boolean> {\n const inputType = await this.getInputType();\n switch (inputType) {\n case 'singleLine':\n return this.parts.singlelineInput.setValue(value);\n case 'multiline':\n return this.parts.multilineInput.setValue(value);\n }\n }\n\n /**\n * Determine whether the input element is disabled.\n */\n async isDisabled(): Promise<boolean> {\n const inputType = await this.getInputType();\n switch (inputType) {\n case 'singleLine':\n return this.parts.singlelineInput.isDisabled();\n case 'multiline':\n return this.parts.multilineInput.isDisabled();\n }\n }\n\n /**\n * Determine whether the input element is read only.\n */\n async isReadonly(): Promise<boolean> {\n const inputType = await this.getInputType();\n switch (inputType) {\n case 'singleLine':\n return this.parts.singlelineInput.isReadonly();\n case 'multiline':\n return this.parts.multilineInput.isReadonly();\n }\n }\n\n /**\n * Identifier for this driver.\n */\n get driverName(): string {\n return 'MuiV6InputDriver';\n }\n}\n","import { ComponentDriver, ErrorBase } from '@atomic-testing/core';\n\nexport const MenuItemDisabledErrorId = 'MenuItemDisabledError';\n\nfunction getErrorMessage(label: string): string {\n return `The menu item with label: ${label} is disabled`;\n}\n\nexport class MenuItemDisabledError extends ErrorBase {\n constructor(\n public readonly label: string,\n public readonly driver: ComponentDriver<any>\n ) {\n super(getErrorMessage(label), driver);\n this.name = MenuItemDisabledErrorId;\n }\n}\n","import { ComponentDriver } from '@atomic-testing/core';\n\nimport { MenuItemDisabledError } from '../errors/MenuItemDisabledError';\n\n/**\n * @internal\n */\nexport class ListItemDriver extends ComponentDriver {\n async label(): Promise<string | null> {\n const label = await this.getText();\n return label?.trim() || null;\n }\n\n async isSelected(): Promise<boolean> {\n return await this.interactor.hasCssClass(this.locator, 'Mui-selected');\n }\n\n async isDisabled(): Promise<boolean> {\n const disabledVal = await this.interactor.getAttribute(this.locator, 'aria-disabled');\n return disabledVal === 'true';\n }\n\n async click(): Promise<void> {\n if (await this.isDisabled()) {\n const label = await this.label();\n throw new MenuItemDisabledError(label ?? '', this);\n }\n await this.interactor.click(this.locator);\n }\n\n get driverName(): string {\n return 'MuiV6ListItemDriver';\n }\n}\n","import {\n byRole,\n IComponentDriverOption,\n Interactor,\n ListComponentDriver,\n ListComponentDriverSpecificOption,\n listHelper,\n PartLocator,\n} from '@atomic-testing/core';\n\nimport { ListItemDriver } from './ListItemDriver';\n\nexport const defaultListDriverOption: ListComponentDriverSpecificOption<ListItemDriver> = {\n itemClass: ListItemDriver,\n itemLocator: byRole('option'),\n};\n\nexport class ListDriver<ItemT extends ListItemDriver = ListItemDriver> extends ListComponentDriver<ItemT> {\n constructor(\n locator: PartLocator,\n interactor: Interactor,\n // @ts-ignore\n option: ListComponentDriverSpecificOption<ItemT> & Partial<IComponentDriverOption<any>> = defaultListDriverOption\n ) {\n super(locator, interactor, option);\n }\n\n async getSelected(): Promise<ListItemDriver | null> {\n for await (const item of listHelper.getListItemIterator(this, this.getItemLocator(), ListItemDriver)) {\n if (await item.isSelected()) {\n return item;\n }\n }\n return null;\n }\n\n override get driverName(): string {\n return 'MuiV6ListDriver';\n }\n}\n","import { ComponentDriver, ErrorBase } from '@atomic-testing/core';\n\nexport const MenuItemNotFoundErrorId = 'MenuItemNotFoundError';\n\nfunction getErrorMessage(label: string): string {\n return `Cannot find menu item with label: ${label}`;\n}\n\nexport class MenuItemNotFoundError extends ErrorBase {\n constructor(\n public readonly label: string,\n public readonly driver: ComponentDriver<any>\n ) {\n super(getErrorMessage(label), driver);\n this.name = MenuItemNotFoundErrorId;\n }\n}\n","import { ListItemDriver } from './ListItemDriver';\n\n/**\n * @internal\n */\nexport class MenuItemDriver extends ListItemDriver {\n async value(): Promise<string | null> {\n const value = await this.interactor.getAttribute(this.locator, 'data-value');\n return value ?? null;\n }\n\n override get driverName(): string {\n return 'MuiV6MenuItemDriver';\n }\n}\n","import { HTMLElementDriver } from '@atomic-testing/component-driver-html';\nimport {\n byRole,\n ComponentDriver,\n IComponentDriverOption,\n Interactor,\n listHelper,\n type LocatorRelativePosition,\n Optional,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nimport { MenuItemNotFoundError } from '../errors/MenuItemNotFoundError';\n\nimport { MenuItemDriver } from './MenuItemDriver';\n\nexport const parts = {\n menu: {\n locator: byRole('menu'),\n driver: HTMLElementDriver,\n },\n} satisfies ScenePart;\n\nconst menuRootLocator: PartLocator = byRole('presentation', 'Root');\nconst menuItemLocator = byRole('menuitem');\n\nexport class MenuDriver extends ComponentDriver<typeof parts> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts,\n });\n }\n\n override overriddenParentLocator(): Optional<PartLocator> {\n return menuRootLocator;\n }\n\n override overrideLocatorRelativePosition(): Optional<LocatorRelativePosition> {\n return 'Same';\n }\n\n async getMenuItemByLabel(label: string): Promise<MenuItemDriver | null> {\n for await (const item of listHelper.getListItemIterator(this, menuItemLocator, MenuItemDriver)) {\n const itemLabel = await item.label();\n if (itemLabel === label) {\n return item;\n }\n }\n return null;\n }\n\n async selectByLabel(label: string): Promise<void> {\n const item = await this.getMenuItemByLabel(label);\n if (item) {\n await item.click();\n } else {\n throw new MenuItemNotFoundError(label, this);\n }\n }\n\n get driverName(): string {\n return 'MuiV6MenuDriver';\n }\n}\n","import { HTMLRadioButtonGroupDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssSelector,\n byInputType,\n byValue,\n ComponentDriver,\n IComponentDriverOption,\n IInputDriver,\n Interactor,\n locatorUtil,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nexport const parts = {\n choices: {\n locator: byInputType('radio'),\n driver: HTMLRadioButtonGroupDriver,\n },\n} satisfies ScenePart;\n\nexport class ProgressDriver extends ComponentDriver<typeof parts> implements IInputDriver<number | null> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts,\n });\n }\n\n async getValue(): Promise<number | null> {\n const rawValue = await this.getAttribute('aria-valuenow');\n const numValue = Number(rawValue);\n if (rawValue == null || isNaN(numValue)) {\n return null;\n }\n return Number(rawValue);\n }\n\n async getType(): Promise<'linear' | 'circular'> {\n const cssClasses = await this.getAttribute('class');\n if (cssClasses?.includes('MuiCircularProgress-root')) {\n return 'circular';\n }\n return 'linear';\n }\n\n async isDeterminate(): Promise<boolean> {\n const val = await this.getValue();\n return val != null;\n }\n\n //TODO: Buffer value can be extracted from style=\"transform: translateX(-15%);\" actual value would be 100 - 15 = 85\n // <span class=\"MuiLinearProgress-bar MuiLinearProgress-bar2 MuiLinearProgress-colorPrimary MuiLinearProgress-bar2Buffer css-1v1662g-MuiLinearProgress-bar2\" style=\"transform: translateX(-15%);\"></span>\n\n async setValue(value: number | null): Promise<boolean> {\n // TODO: Setting value to null is not supported. https://github.com/atomic-testing/atomic-testing/issues/68\n const currentValue = await this.getValue();\n if (value === currentValue) {\n return true;\n }\n\n const valueToClick = (value == null ? currentValue : value) as number;\n const targetLocator = locatorUtil.append(this.parts.choices.locator, byValue(valueToClick.toString(), 'Same'));\n\n const targetExists = await this.interactor.exists(targetLocator);\n if (targetExists) {\n const id = await this.interactor.getAttribute(targetLocator, 'id');\n const labelLocator = locatorUtil.append(this.locator, byCssSelector(`label[for=\"${id}\"]`));\n await this.interactor.click(labelLocator);\n }\n // TODO: throw error if the value does not exist\n return targetExists;\n }\n\n get driverName(): string {\n return 'MuiV6ProgressDriver';\n }\n}\n","import { HTMLRadioButtonGroupDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssSelector,\n byInputType,\n byValue,\n ComponentDriver,\n IComponentDriverOption,\n IInputDriver,\n Interactor,\n locatorUtil,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nexport const parts = {\n choices: {\n locator: byInputType('radio'),\n driver: HTMLRadioButtonGroupDriver,\n },\n} satisfies ScenePart;\n\nexport class RatingDriver extends ComponentDriver<typeof parts> implements IInputDriver<number | null> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts,\n });\n }\n\n async getValue(): Promise<number | null> {\n // TODO: https://github.com/atomic-testing/atomic-testing/issues/69\n // getValue() does not work when readonly\n await this.enforcePartExistence('choices');\n const value = await this.parts.choices.getValue();\n if (value == null) {\n return null;\n }\n return parseFloat(value);\n }\n\n async setValue(value: number | null): Promise<boolean> {\n // TODO: Setting value to null is not supported. https://github.com/atomic-testing/atomic-testing/issues/68\n const currentValue = await this.getValue();\n if (value === currentValue) {\n return true;\n }\n\n const valueToClick = (value == null ? currentValue : value) as number;\n const targetLocator = locatorUtil.append(this.parts.choices.locator, byValue(valueToClick.toString(), 'Same'));\n\n const targetExists = await this.interactor.exists(targetLocator);\n if (targetExists) {\n const id = await this.interactor.getAttribute(targetLocator, 'id');\n const labelLocator = locatorUtil.append(this.locator, byCssSelector(`label[for=\"${id}\"]`));\n await this.interactor.click(labelLocator);\n }\n // TODO: throw error if the value does not exist\n return targetExists;\n }\n\n get driverName(): string {\n return 'MuiV6RatingDriver';\n }\n}\n","import {\n HTMLButtonDriver,\n HTMLElementDriver,\n HTMLSelectDriver,\n HTMLTextInputDriver,\n} from '@atomic-testing/component-driver-html';\nimport {\n byAttribute,\n byCssSelector,\n byRole,\n byTagName,\n ComponentDriver,\n IComponentDriverOption,\n IInputDriver,\n Interactor,\n listHelper,\n locatorUtil,\n Nullable,\n PartLocator,\n ScenePart,\n ScenePartDriver,\n} from '@atomic-testing/core';\n\nimport { MenuItemNotFoundError } from '../errors/MenuItemNotFoundError';\n\nimport { MenuItemDriver } from './MenuItemDriver';\n\nexport const selectPart = {\n trigger: {\n locator: byRole('combobox'), // Starting in 5.12 and beyond, the role has changed from 'button' to 'combobox'\n driver: HTMLButtonDriver,\n },\n dropdown: {\n locator: byCssSelector('[role=presentation] [role=listbox]', 'Root'),\n driver: HTMLElementDriver,\n },\n input: {\n locator: byTagName('input'),\n driver: HTMLTextInputDriver,\n },\n nativeSelect: {\n locator: byTagName('select'),\n driver: HTMLSelectDriver,\n },\n} satisfies ScenePart;\n\nexport type SelectScenePart = typeof selectPart;\nexport type SelectScenePartDriver = ScenePartDriver<SelectScenePart>;\nexport interface MenuItemGetOption {\n /**\n * When true, the driver will not check if the dropdown is open, which helps speed the process up.\n */\n skipDropdownCheck?: boolean;\n}\nconst optionLocator = byRole('option');\n\nexport class SelectDriver extends ComponentDriver<SelectScenePart> implements IInputDriver<string | null> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts: selectPart,\n });\n }\n async isNative(): Promise<boolean> {\n const nativeSelectExists = await this.interactor.exists(this.parts.nativeSelect.locator);\n return Promise.resolve(nativeSelectExists);\n }\n\n async getValue(): Promise<string | null> {\n const isNative = await this.isNative();\n if (isNative) {\n const val = (await this.parts.nativeSelect.getValue()) as Nullable<string>;\n return val;\n }\n\n await this.enforcePartExistence('input');\n const value = await this.parts.input.getValue();\n return value ?? null;\n }\n\n async setValue(value: string | null): Promise<boolean> {\n let success = false;\n const isNative = await this.isNative();\n if (isNative) {\n success = await this.parts.nativeSelect.setValue(value);\n return success;\n }\n\n await this.openDropdown();\n await this.enforcePartExistence('dropdown');\n const optionSelector = byAttribute('data-value', value!);\n const optionLocator = locatorUtil.append(this.parts.dropdown.locator, optionSelector);\n const optionExists = await this.interactor.exists(optionLocator);\n\n if (optionExists) {\n await this.interactor.click(optionLocator);\n success = true;\n }\n\n return success;\n }\n\n /**\n * Select menu item by its label, if it exists\n * Limitation, this method will not work if the dropdown is a native select.\n * @param label\n * @returns\n */\n async getMenuItemByLabel(label: string, option?: MenuItemGetOption): Promise<MenuItemDriver | null> {\n if (!option?.skipDropdownCheck) {\n await this.openDropdown();\n }\n\n // TODO: Add native select support\n\n for await (const item of listHelper.getListItemIterator(this, optionLocator, MenuItemDriver)) {\n const itemLabel = await item.label();\n if (itemLabel === label) {\n return item;\n }\n }\n return null;\n }\n\n /**\n * Selects an option by its label\n * @param label\n * @returns\n */\n async selectByLabel(label: string): Promise<void> {\n const isNative = await this.isNative();\n if (isNative) {\n await this.parts.nativeSelect.selectByLabel(label);\n return;\n }\n\n await this.enforcePartExistence('trigger');\n await this.parts.trigger.click();\n\n await this.enforcePartExistence('dropdown');\n const item = await this.getMenuItemByLabel(label, { skipDropdownCheck: true });\n\n if (item) {\n await item.click();\n } else {\n throw new MenuItemNotFoundError(label, this);\n }\n }\n\n async getSelectedLabel(): Promise<string | null> {\n const isNative = await this.isNative();\n if (isNative) {\n return await this.parts.nativeSelect.getSelectedLabel();\n }\n\n await this.enforcePartExistence('trigger');\n const label = await this.parts.trigger.getText();\n return label ?? null;\n }\n\n override async exists(): Promise<boolean> {\n const triggerExists = await this.interactor.exists(this.parts.trigger.locator);\n if (triggerExists) {\n return true;\n }\n\n const nativeExists = await this.interactor.exists(this.parts.nativeSelect.locator);\n return nativeExists;\n }\n\n /**\n * 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\n * @returns For native dropdown it is always true. For custom dropdown, it is true if the dropdown is open.\n */\n async isDropdownOpen(): Promise<boolean> {\n const isNative = await this.isNative();\n if (isNative) {\n return true;\n } else {\n return this.parts.dropdown.exists();\n }\n }\n\n async openDropdown(): Promise<void> {\n const isOpen = await this.isDropdownOpen();\n if (isOpen) {\n return;\n }\n await this.parts.trigger.click();\n }\n\n async closeDropdown(): Promise<void> {\n const isOpen = await this.isDropdownOpen();\n if (!isOpen) {\n return;\n }\n await this.parts.trigger.click();\n }\n\n async isDisabled(): Promise<boolean> {\n const isNative = await this.isNative();\n if (isNative) {\n return this.parts.nativeSelect.isDisabled();\n } else {\n await this.enforcePartExistence('trigger');\n const isDisabled = await this.interactor.getAttribute(this.parts.trigger.locator, 'aria-disabled');\n return isDisabled === 'true';\n }\n }\n\n async isReadonly(): Promise<boolean> {\n const isNative = await this.isNative();\n if (isNative) {\n return this.parts.nativeSelect.isReadonly();\n } else {\n // Cannot determine readonly state of a select input.\n return false;\n }\n }\n\n get driverName(): string {\n return 'MuiV6SelectDriver';\n }\n}\n","import { HTMLTextInputDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssSelector,\n ComponentDriver,\n IComponentDriverOption,\n IInputDriver,\n Interactor,\n locatorUtil,\n PartLocator,\n ScenePart,\n ScenePartDriver,\n} from '@atomic-testing/core';\n\nexport const parts = {\n input: {\n locator: byCssSelector('input[type=\"range\"][data-index=\"0\"]'),\n driver: HTMLTextInputDriver,\n },\n} satisfies ScenePart;\n\nexport type SelectScenePart = typeof parts;\nexport type SelectScenePartDriver = ScenePartDriver<SelectScenePart>;\n\nexport class SliderDriver extends ComponentDriver<SelectScenePart> implements IInputDriver<number> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts: parts,\n });\n }\n\n /**\n * Return the first occurrence of the Slider input\n * @returns\n */\n async getValue(): Promise<number> {\n const values = await this.getRangeValues(1);\n return values[0]!;\n }\n\n /**\n * Set slider's range value. Do not use as it will throw an error\n * @param values\n * @see https://github.com/atomic-testing/atomic-testing/issues/73\n */\n async setValue(value: number): Promise<boolean> {\n const success = await this.setRangeValues([value]);\n return success;\n }\n\n async getRangeValues(count?: number): Promise<readonly number[]> {\n await this.enforcePartExistence('input');\n const result: number[] = [];\n\n let index = 0;\n let done = false;\n while (!done) {\n const locator = locatorUtil.append(this.locator, this.getInputLocator(index));\n const exists = await this.interactor.exists(locator);\n if (exists) {\n index++;\n done = count != null && index >= count;\n const value = await this.interactor.getAttribute(locator, 'value');\n result.push(parseFloat(value!));\n } else {\n done = true;\n }\n }\n return result;\n }\n\n private getInputLocator(index: number): PartLocator {\n return byCssSelector(`input[type=\"range\"][data-index=\"${index}\"]`);\n }\n\n /**\n * Set slider's range values. Do not use as it will throw an error\n * @param values\n * @see https://github.com/atomic-testing/atomic-testing/issues/73\n */\n async setRangeValues(_values: readonly number[]): Promise<boolean> {\n await this.enforcePartExistence('input');\n throw new Error('setRangeValue is not supported.');\n // for (let index = 0; index < values.length; index++) {\n // const locator = locatorUtil.append(this.locator, this.getInputLocator(index));\n // const exists = await this.interactor.exists(locator);\n // if (exists) {\n // // @ts-ignore\n // await this.interactor.changeValue(locator, values[index].toString());\n // // const driver = new HTMLTextInputDriver(locator, this.interactor);\n // // await driver.setValue(values[index].toString());\n // } else {\n // return false;\n // }\n // }\n\n // return true;\n }\n\n async isDisabled(): Promise<boolean> {\n await this.enforcePartExistence('input');\n const disabled = await this.parts.input.isDisabled();\n return disabled;\n }\n\n get driverName(): string {\n return 'MuiV6SliderDriver';\n }\n}\n","import { HTMLElementDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssClass,\n ComponentDriver,\n IComponentDriverOption,\n Interactor,\n locatorUtil,\n PartLocator,\n ScenePart,\n ComponentDriverCtor,\n} from '@atomic-testing/core';\n\nexport const parts = {\n contentDisplay: {\n locator: byCssClass('MuiSnackbarContent-message'),\n driver: HTMLElementDriver,\n },\n actionArea: {\n locator: byCssClass('MuiSnackbarContent-action'),\n driver: HTMLElementDriver,\n },\n} satisfies ScenePart;\n\n/**\n * Driver for Material UI v6 Snackbar component.\n * @see https://mui.com/material-ui/react-snackbar/\n */\nexport class SnackbarDriver extends ComponentDriver<typeof parts> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts: parts,\n });\n }\n\n /**\n * Get the label content of the snackbar.\n * @returns The label text content of the snackbar.\n */\n async getLabel(): Promise<string | null> {\n await this.enforcePartExistence('contentDisplay');\n const content = await this.parts.contentDisplay.getText();\n return content ?? null;\n }\n\n /**\n * Get a driver instance of a component in the action area of the snackbar.\n * @param locator\n * @param driverClass\n * @returns\n */\n async getActionComponent<ItemClass extends ComponentDriver>(\n locator: PartLocator,\n driverClass: ComponentDriverCtor<ItemClass>\n ): Promise<ItemClass | null> {\n await this.enforcePartExistence('actionArea');\n const componentLocator = locatorUtil.append(this.parts.actionArea.locator, locator);\n const exists = await this.interactor.exists(componentLocator);\n if (exists) {\n return new driverClass(componentLocator, this.interactor, this.commutableOption);\n }\n return null;\n }\n\n override get driverName(): string {\n return 'MuiV6SnackbarDriver';\n }\n}\n","import { HTMLCheckboxDriver } from '@atomic-testing/component-driver-html';\nimport {\n byAttribute,\n ComponentDriver,\n IComponentDriverOption,\n IFormFieldDriver,\n Interactor,\n IToggleDriver,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nexport const parts = {\n input: {\n locator: byAttribute('type', 'checkbox'),\n driver: HTMLCheckboxDriver,\n },\n} satisfies ScenePart;\n\nexport class SwitchDriver\n extends ComponentDriver<typeof parts>\n implements IFormFieldDriver<string | null>, IToggleDriver\n{\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts,\n });\n }\n\n override async exists(): Promise<boolean> {\n return this.interactor.exists(this.parts.input.locator);\n }\n\n async isSelected(): Promise<boolean> {\n await this.enforcePartExistence('input');\n return this.parts.input.isSelected();\n }\n async setSelected(selected: boolean): Promise<void> {\n await this.enforcePartExistence('input');\n await this.parts.input.setSelected(selected);\n }\n\n async getValue(): Promise<string | null> {\n await this.enforcePartExistence('input');\n return this.parts.input.getValue();\n }\n\n async isDisabled(): Promise<boolean> {\n await this.enforcePartExistence('input');\n return this.parts.input.isDisabled();\n }\n\n async isReadonly(): Promise<boolean> {\n // MUI v5 does not have a readonly state for the switch\n return Promise.resolve(false);\n }\n\n get driverName(): string {\n return 'MuiV6SwitchDriver';\n }\n}\n","import { HTMLElementDriver, HTMLTextInputDriver } from '@atomic-testing/component-driver-html';\nimport {\n byCssSelector,\n byRole,\n byTagName,\n ComponentDriver,\n IComponentDriverOption,\n IInputDriver,\n Interactor,\n Optional,\n PartLocator,\n ScenePart,\n} from '@atomic-testing/core';\n\nimport { SelectDriver } from './SelectDriver';\n\nexport const parts = {\n label: {\n locator: byCssSelector('>label'),\n driver: HTMLElementDriver,\n },\n helperText: {\n locator: byCssSelector('>p'),\n driver: HTMLElementDriver,\n },\n singlelineInput: {\n locator: byCssSelector('input:not([aria-hidden])'),\n driver: HTMLTextInputDriver,\n },\n multilineInput: {\n locator: byCssSelector('textarea:not([aria-hidden])'),\n driver: HTMLTextInputDriver,\n },\n selectInput: {\n locator: byCssSelector('>div'),\n driver: SelectDriver,\n },\n // Used to detect the presence of select input\n richSelectInputDetect: {\n locator: byRole('combobox'),\n driver: HTMLElementDriver,\n },\n nativeSelectInputDetect: {\n locator: byTagName('SELECT'),\n driver: HTMLElementDriver,\n },\n} satisfies ScenePart;\n\ntype TextFieldInputType = 'singleLine' | 'multiline' | 'select';\n\n/**\n * A driver for the Material UI v6 TextField component with single line or multiline text input.\n */\nexport class TextFieldDriver extends ComponentDriver<typeof parts> implements IInputDriver<string | null> {\n constructor(locator: PartLocator, interactor: Interactor, option?: Partial<IComponentDriverOption>) {\n super(locator, interactor, {\n ...option,\n parts,\n });\n }\n\n private async getInputType(): Promise<TextFieldInputType> {\n const result = await Promise.all([\n this.parts.singlelineInput.exists(),\n this.parts.richSelectInputDetect.exists(),\n this.parts.nativeSelectInputDetect.exists(),\n this.parts.multilineInput.exists(),\n ]).then(([singlelineExists, richSelectExists, nativeSelectExists, multilineExists]) => {\n if (singlelineExists) {\n return 'singleLine';\n }\n if (richSelectExists || nativeSelectExists) {\n return 'select';\n }\n if (multilineExists) {\n return 'multiline';\n }\n\n throw new Error('Unable to determine input type in TextFieldInput');\n });\n\n return result;\n }\n\n async getValue(): Promise<string | null> {\n const inputType = await this.getInputType();\n switch (inputType) {\n case 'singleLine':\n return this.parts.singlelineInput.getValue();\n case 'select':\n return this.parts.selectInput.getValue();\n case 'multiline':\n return this.parts.multilineInput.getValue();\n }\n }\n\n async setValue(value: string | null): Promise<boolean> {\n const inputType = await this.getInputType();\n switch (inputType) {\n cas