From 31eb65a9eafb5e8d18e9d83124efa50d173d2459 Mon Sep 17 00:00:00 2001 From: Ganesh Somasundaram Date: Fri, 30 Jun 2023 15:46:24 -0400 Subject: [PATCH] fix: add play test for dialog, menubutton, select, tag, passwordfield --- packages/odyssey-react-mui/src/Dialog.tsx | 3 ++ packages/odyssey-react-mui/src/Select.tsx | 2 + .../odyssey-mui/Dialog/Dialog.stories.tsx | 16 +++++++ .../MenuButton/MenuButton.stories.tsx | 31 ++++++++++-- .../PasswordField/PasswordField.stories.tsx | 34 ++++++++++++- .../odyssey-mui/Select/Select.stories.tsx | 48 +++++++++++++++++++ .../odyssey-mui/Tag/Tag.stories.tsx | 22 ++++++--- 7 files changed, 146 insertions(+), 10 deletions(-) diff --git a/packages/odyssey-react-mui/src/Dialog.tsx b/packages/odyssey-react-mui/src/Dialog.tsx index 1b5871a22b..b67d92f82b 100644 --- a/packages/odyssey-react-mui/src/Dialog.tsx +++ b/packages/odyssey-react-mui/src/Dialog.tsx @@ -36,6 +36,7 @@ export type DialogProps = { onClose: () => void; isOpen: boolean; title: string; + ariaLabel: string; }; const Dialog = ({ @@ -46,6 +47,7 @@ const Dialog = ({ isOpen, onClose, title, + ariaLabel, }: DialogProps) => { const [isContentScrollable, setIsContentScrollable] = useState(false); const dialogContentRef = useRef(null); @@ -87,6 +89,7 @@ const Dialog = ({ variant="floating" onClick={onClose} startIcon={} + ariaLabel={ariaLabel} /> diff --git a/packages/odyssey-react-mui/src/Select.tsx b/packages/odyssey-react-mui/src/Select.tsx index b816ee9f85..51d6cb2167 100644 --- a/packages/odyssey-react-mui/src/Select.tsx +++ b/packages/odyssey-react-mui/src/Select.tsx @@ -189,6 +189,7 @@ const Select = forwardRef( ref={ref} renderValue={isMultiSelect ? renderValue : undefined} value={selectedValue} + labelId={label} /> ), [ @@ -201,6 +202,7 @@ const Select = forwardRef( children, renderValue, selectedValue, + label, ] ); diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/Dialog/Dialog.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/Dialog/Dialog.stories.tsx index 399979a3e5..473cb2231d 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/Dialog/Dialog.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/Dialog/Dialog.stories.tsx @@ -18,6 +18,8 @@ import { DialogProps, } from "@okta/odyssey-react-mui"; import { useState } from "react"; +import { userEvent, waitFor, within } from "@storybook/testing-library"; +import { axeRun } from "../../../axe-util"; import { MuiThemeDecorator } from "../../../../.storybook/components"; @@ -48,11 +50,15 @@ const storybookMeta: Meta = { title: { control: "text", }, + ariaLabel: { + control: "text", + }, }, args: { children: "You are initiating this ship's self-destruct protocol. This ship, and its occupants, will be destroyed.", title: "Initiate self-destruct protocol", + ariaLabel: "close", }, decorators: [MuiThemeDecorator], }; @@ -104,6 +110,16 @@ export const Default: StoryObj = { "You are initiating this ship's self-destruct protocol. This ship, and its occupants, will be destroyed.", title: "Initiate self-destruct protocol", }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + await step("open Default Dialog", async () => { + const buttonElement = canvas.getByText("Open dialog"); + userEvent.click(buttonElement); + await waitFor(() => { + axeRun("Default Dialog"); + }); + }); + }, }; export const Long: StoryObj = { diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/MenuButton/MenuButton.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/MenuButton/MenuButton.stories.tsx index 20cf2bb4a0..414fc295b9 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/MenuButton/MenuButton.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/MenuButton/MenuButton.stories.tsx @@ -25,6 +25,9 @@ import { MoreIcon, } from "@okta/odyssey-react-mui"; import { MuiThemeDecorator } from "../../../../.storybook/components"; +import { userEvent, waitFor, within } from "@storybook/testing-library"; +import { axeRun } from "../../../axe-util"; +import type { PlaywrightProps } from "../storybookTypes"; const storybookMeta: Meta = { title: "MUI Components/Menu Button", @@ -54,6 +57,19 @@ const storybookMeta: Meta = { export default storybookMeta; +const clickMenuButton = + ({ canvasElement, step }: PlaywrightProps) => + async (args: MenuButtonProps, actionName: string) => { + const canvas = within(canvasElement); + await step("open menu button", async () => { + const buttonElement = canvas.getByText(args.buttonLabel || ""); + userEvent.click(buttonElement); + await waitFor(() => { + axeRun(actionName); + }); + }); + }; + export const Simple: StoryObj = { args: { buttonLabel: "More actions", @@ -63,6 +79,9 @@ export const Simple: StoryObj = { Launch, ], }, + play: async ({ args, canvasElement, step }) => { + clickMenuButton({ canvasElement, step })(args, "Menu Button Simple"); + }, }; export const ActionIcons: StoryObj = { @@ -88,6 +107,9 @@ export const ActionIcons: StoryObj = { , ], }, + play: async ({ args, canvasElement, step }) => { + clickMenuButton({ canvasElement, step })(args, "Menu Button Action Icons"); + }, }; export const ButtonVariant: StoryObj = { @@ -106,13 +128,13 @@ export const Groupings: StoryObj = { args: { buttonLabel: "More actions", children: [ - Crew, + Crew, Assign captain, View roster, - Ship, + Ship, Configure thrusters, View cargo, - , + , Logout, ], }, @@ -129,6 +151,9 @@ export const WithDestructive: StoryObj = { , ], }, + play: async ({ args, canvasElement, step }) => { + clickMenuButton({ canvasElement, step })(args, "Menu Button Destructive"); + }, }; export const IconButton: StoryObj = { diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/PasswordField/PasswordField.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/PasswordField/PasswordField.stories.tsx index 1a61be5177..1459ee3795 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/PasswordField/PasswordField.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/PasswordField/PasswordField.stories.tsx @@ -12,6 +12,9 @@ import { Meta, StoryObj } from "@storybook/react"; import { PasswordField, PasswordFieldProps } from "@okta/odyssey-react-mui"; +import { userEvent, waitFor } from "@storybook/testing-library"; +import { expect } from "@storybook/jest"; +import { axeRun } from "../../../axe-util"; import { MuiThemeDecorator } from "../../../../.storybook/components"; @@ -66,10 +69,39 @@ const storybookMeta: Meta = { autoCompleteType: "current-password", label: "Password", isOptional: false, + id: "password-input", }, decorators: [MuiThemeDecorator], }; export default storybookMeta; -export const Default: StoryObj = {}; +export const Default: StoryObj = { + play: async ({ args, canvasElement, step }) => { + await step("toggle password", async () => { + const fieldElement = canvasElement.querySelector( + `#${args.id}` + ) as HTMLInputElement; + expect(fieldElement.type).toBe("password"); + + const buttonElement = canvasElement.querySelector( + '[aria-label="toggle password visibility"]' + ); + if (buttonElement) { + userEvent.type(fieldElement, "qwerty"); + userEvent.click(buttonElement); + userEvent.tab(); + await waitFor(() => { + expect(fieldElement.type).toBe("text"); + }); + userEvent.click(buttonElement); + await waitFor(() => { + expect(fieldElement.type).toBe("password"); + }); + } + await waitFor(() => { + axeRun("Password Field Default"); + }); + }); + }, +}; diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/Select/Select.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/Select/Select.stories.tsx index 34b1c5ea14..3586132e9c 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/Select/Select.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/Select/Select.stories.tsx @@ -13,6 +13,9 @@ import { Meta, StoryObj } from "@storybook/react"; import { Select, SelectProps } from "@okta/odyssey-react-mui"; import { MuiThemeDecorator } from "../../../../.storybook/components"; +import { userEvent, waitFor, screen } from "@storybook/testing-library"; +import { axeRun } from "../../../axe-util"; +import { expect } from "@storybook/jest"; const storybookMeta: Meta = { title: "MUI Components/Forms/Select", @@ -181,6 +184,25 @@ const GroupTemplate: StoryObj = { export const Default: StoryObj = { ...Template, + play: async ({ canvasElement, step }) => { + await step("Select Earth from the listbox", async () => { + const comboBoxElement = canvasElement.querySelector( + '[aria-haspopup="listbox"]' + ); + if (comboBoxElement) { + userEvent.click(comboBoxElement); + const listboxElement = screen.getByRole("listbox"); + expect(listboxElement).toBeInTheDocument(); + const listItem = listboxElement.children[0]; + userEvent.click(listItem); + userEvent.tab(); + await waitFor(() => expect(listboxElement).not.toBeInTheDocument()); + const inputElement = canvasElement.querySelector("input"); + expect(inputElement?.value).toBe("Earth"); + await waitFor(() => axeRun("Select Multiple")); + } + }); + }, }; Default.args = {}; @@ -196,6 +218,11 @@ export const DefaultError: StoryObj = { args: { errorMessage: "Select your destination.", }, + play: async ({ step }) => { + await step("Check for a11y errors on Select Error", async () => { + await waitFor(() => axeRun("Select Multiple")); + }); + }, }; export const DefaultObject: StoryObj = { @@ -211,4 +238,25 @@ export const Multi: StoryObj = { args: { isMultiSelect: true, }, + play: async ({ canvasElement, step }) => { + await step("Select Multiple items from the listbox", async () => { + const comboBoxElement = canvasElement.querySelector( + '[aria-haspopup="listbox"]' + ); + if (comboBoxElement) { + userEvent.click(comboBoxElement); + const listboxElement = screen.getByRole("listbox"); + expect(listboxElement).toBeInTheDocument(); + + userEvent.click(listboxElement.children[0]); + userEvent.click(listboxElement.children[1]); + userEvent.tab(); + await waitFor(() => expect(listboxElement).not.toBeInTheDocument()); + const inputElement = canvasElement.querySelector("input"); + expect(inputElement?.value).toBe("Earth,Mars"); + userEvent.click(canvasElement); + await waitFor(() => axeRun("Select Multiple")); + } + }); + }, }; diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/Tag/Tag.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/Tag/Tag.stories.tsx index 8f70306f7c..4dcb5d1d5a 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/Tag/Tag.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/Tag/Tag.stories.tsx @@ -14,6 +14,9 @@ import { Meta, StoryObj } from "@storybook/react"; import { Tag, TagList, TagProps } from "@okta/odyssey-react-mui"; import { MuiThemeDecorator } from "../../../../.storybook/components"; import { GroupIcon } from "@okta/odyssey-react-mui"; +import { userEvent } from "@storybook/testing-library"; +import { expect } from "@storybook/jest"; +import { axeRun } from "../../../axe-util"; const storybookMeta: Meta = { title: "MUI Components/Tag", @@ -40,8 +43,6 @@ const storybookMeta: Meta = { }, args: { label: "Starship", - onRemove: undefined, - onClick: undefined, }, decorators: [MuiThemeDecorator], }; @@ -58,7 +59,7 @@ export const List: StoryObj = { render: function C(args) { return ( - + @@ -88,9 +89,18 @@ export const Clickable: StoryObj = { export const Removable: StoryObj = { args: { label: "Starship", - onRemove: function noRefCheck(event) { - event; - }, + }, + play: async ({ args, canvasElement, step }) => { + await step("remove the tag on click", async () => { + const tagElement = canvasElement.querySelector('[role="button"]'); + const removeIcon = tagElement?.querySelector("svg"); + if (removeIcon) { + userEvent.click(removeIcon); + userEvent.tab(); + expect(args.onRemove).toHaveBeenCalled(); + } + await axeRun("Removable Tag"); + }); }, };