From e8e95f56278917b8f92cac0c7752980e31c661a2 Mon Sep 17 00:00:00 2001 From: Anirudh Mani Date: Mon, 26 Aug 2024 15:06:03 -0700 Subject: [PATCH 1/3] feat: add test selector for banner component --- packages/odyssey-react-mui/src/Banner.tsx | 31 +++++++++++++++++++ .../test-selectors/odysseyTestSelectors.ts | 2 ++ .../odyssey-mui/Banner/Banner.stories.tsx | 29 +++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/packages/odyssey-react-mui/src/Banner.tsx b/packages/odyssey-react-mui/src/Banner.tsx index 3c87dc8e12..0d1b4dab11 100644 --- a/packages/odyssey-react-mui/src/Banner.tsx +++ b/packages/odyssey-react-mui/src/Banner.tsx @@ -14,6 +14,7 @@ import { memo } from "react"; import { useTranslation } from "react-i18next"; import { Alert, AlertColor, AlertTitle, AlertProps } from "@mui/material"; +import { type FeatureTestSelector } from "./test-selectors"; import type { HtmlProps } from "./HtmlProps"; import { Link } from "./Link"; import { ScreenReaderText } from "./ScreenReaderText"; @@ -26,6 +27,36 @@ export const bannerSeverityValues: AlertColor[] = [ "error", ]; +export const BannerTestSelectors = { + feature: { + link: { + selector: { + method: "ByRole", + options: { + name: "${linkText}", + }, + role: "link", + templateVariableNames: ["linkText"], + }, + }, + closeButton: { + selector: { + method: "ByRole", + options: { + name: "${labelText}", + }, + role: "button", + templateVariableNames: ["labelText"], + }, + }, + }, + selector: { + method: "ByRole", + role: "${role}", + templateVariableNames: ["role"], + }, +} as const satisfies FeatureTestSelector; + export type BannerProps = { /** * If linkUrl is not undefined, this is the text of the link. diff --git a/packages/odyssey-react-mui/src/test-selectors/odysseyTestSelectors.ts b/packages/odyssey-react-mui/src/test-selectors/odysseyTestSelectors.ts index a096f84929..348d0b4b67 100644 --- a/packages/odyssey-react-mui/src/test-selectors/odysseyTestSelectors.ts +++ b/packages/odyssey-react-mui/src/test-selectors/odysseyTestSelectors.ts @@ -11,11 +11,13 @@ */ import { type FeatureTestSelector } from "./featureTestSelector"; +import { BannerTestSelectors } from "../Banner"; import { CalloutTestSelectors } from "../Callout"; import { TabsTestSelectors } from "../Tabs"; import { TextFieldTestSelectors } from "../TextField"; export const odysseyTestSelectors = { + Banner: BannerTestSelectors, Callout: CalloutTestSelectors, Tabs: TabsTestSelectors, TextField: TextFieldTestSelectors, diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/Banner/Banner.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/Banner/Banner.stories.tsx index 9e704b1ac0..d793f66d24 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/Banner/Banner.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/Banner/Banner.stories.tsx @@ -18,6 +18,7 @@ import { } from "@okta/odyssey-react-mui"; import { Meta, StoryObj } from "@storybook/react"; +import { queryOdysseySelector } from "@okta/odyssey-react-mui/test-selectors"; import { MuiThemeDecorator } from "../../../../.storybook/components"; import { userEvent, within } from "@storybook/testing-library"; import { expect, jest } from "@storybook/jest"; @@ -145,6 +146,20 @@ export const Linked: StoryObj = { await expect(link?.tagName).toBe("A"); await expect(link?.href).toBe(`${link?.baseURI}#anchor`); }); + + await step("has visible link", async () => { + const element = queryOdysseySelector({ + canvas: within(canvasElement), + componentName: "Banner", + templateArgs: { + role: "status", + }, + }).select?.("link", { + linkText: "View report", + }).element; + + expect(element).toBeVisible(); + }); }, }; @@ -161,5 +176,19 @@ export const Dismissible: StoryObj = { await expect(args.onClose).toHaveBeenCalled(); await axeRun("Dismissible Banner"); }); + + await step("has visible close button", async () => { + const element = queryOdysseySelector({ + canvas: within(canvasElement), + componentName: "Banner", + templateArgs: { + role: "alert", + }, + }).select?.("closeButton", { + labelText: "Close", + }).element; + + expect(element).toBeVisible(); + }); }, }; From 4c422aaac3522b84efaa5a7f0866d95f97022bbc Mon Sep 17 00:00:00 2001 From: Anirudh Mani Date: Mon, 26 Aug 2024 15:04:36 -0700 Subject: [PATCH 2/3] fix: modify textfield and callout testselectors --- packages/odyssey-react-mui/src/Callout.tsx | 15 +------ packages/odyssey-react-mui/src/TextField.tsx | 30 ++++++------- .../src/test-selectors/querySelector.ts | 4 +- .../odyssey-mui/Callout/Callout.stories.tsx | 2 +- .../TextField/TextField.stories.tsx | 44 +++++++++++++++++++ 5 files changed, 61 insertions(+), 34 deletions(-) diff --git a/packages/odyssey-react-mui/src/Callout.tsx b/packages/odyssey-react-mui/src/Callout.tsx index d904c43328..5eb4ad2aca 100644 --- a/packages/odyssey-react-mui/src/Callout.tsx +++ b/packages/odyssey-react-mui/src/Callout.tsx @@ -36,21 +36,8 @@ export const CalloutTestSelectors = { templateVariableNames: ["linkText"], }, }, - text: { - selector: { - method: "ByText", - templateVariableNames: ["text"], - text: "${text}", - }, - }, - title: { - selector: { - method: "ByText", - templateVariableNames: ["title"], - text: "${title}", - }, - }, }, + // label: ["description", "title"], selector: { method: "ByRole", options: { diff --git a/packages/odyssey-react-mui/src/TextField.tsx b/packages/odyssey-react-mui/src/TextField.tsx index c3ff2f110d..c4cc8a1f90 100644 --- a/packages/odyssey-react-mui/src/TextField.tsx +++ b/packages/odyssey-react-mui/src/TextField.tsx @@ -34,6 +34,7 @@ import { type FeatureTestSelector } from "./test-selectors"; export const TextFieldTestSelectors = { feature: { + // label: ["errorMessage", "hint", "label"], description: { selector: { method: "ByText", @@ -44,27 +45,14 @@ export const TextFieldTestSelectors = { errorMessage: { selector: { method: "ByText", - templateVariableNames: ["errorMessage"], - text: "${errorMessage}", - }, - }, - input: { - selector: { - method: "ByRole", - options: { - name: "${label}", - }, - role: "textbox", - templateVariableNames: ["label"], + templateVariableNames: ["text"], + text: "${text}", }, }, label: { selector: { - method: "ByRole", - options: { - name: "${label}", - }, - role: "LabelText", + method: "ByLabelText", + text: "${label}", templateVariableNames: ["label"], }, }, @@ -79,6 +67,14 @@ export const TextFieldTestSelectors = { }, }, }, + selector: { + method: "ByRole", + options: { + name: "${label}", + }, + role: "textbox", + templateVariableNames: ["label"], + }, } as const satisfies FeatureTestSelector; export const textFieldTypeValues = [ diff --git a/packages/odyssey-react-mui/src/test-selectors/querySelector.ts b/packages/odyssey-react-mui/src/test-selectors/querySelector.ts index 93955f515b..4dd4f0842b 100644 --- a/packages/odyssey-react-mui/src/test-selectors/querySelector.ts +++ b/packages/odyssey-react-mui/src/test-selectors/querySelector.ts @@ -144,7 +144,7 @@ export const querySelector = ({ }) : null; - const select = + const selectChild = "feature" in testSelectors ? ( featureName: FeatureName, @@ -166,7 +166,7 @@ export const querySelector = ({ return { element, - select, + selectChild, }; }; diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/Callout/Callout.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/Callout/Callout.stories.tsx index d144ffd2c2..d56bac9247 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/Callout/Callout.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/Callout/Callout.stories.tsx @@ -219,7 +219,7 @@ export const TitleWithLink: StoryObj = { role: "alert", title: /Safety checks failed/, }, - }).select?.("link", { + }).selectChild?.("link", { linkText: "Visit fueling console", }).element; diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/TextField/TextField.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/TextField/TextField.stories.tsx index e5a87fd9e1..e71b03226e 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/TextField/TextField.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/TextField/TextField.stories.tsx @@ -18,6 +18,7 @@ import { textFieldTypeValues, } from "@okta/odyssey-react-mui"; +import { queryOdysseySelector } from "@okta/odyssey-react-mui/test-selectors"; import { userEvent, within } from "@storybook/testing-library"; import { expect } from "@storybook/jest"; import { fieldComponentPropsMetaData } from "../../../fieldComponentPropsMetaData"; @@ -246,6 +247,19 @@ export const Error: StoryObj = { errorMessage: "This field is required.", defaultValue: "", }, + play: async ({ canvasElement, step }) => { + await step("has visible error message", async () => { + const element = queryOdysseySelector({ + canvas: within(canvasElement), + componentName: "TextField", + templateArgs: { + label: "Destination", + }, + }).element; + + expect(element).toHaveErrorMessage(/This field is required/); + }); + }, }; export const ErrorsList: StoryObj = { @@ -254,6 +268,21 @@ export const ErrorsList: StoryObj = { errorMessageList: ["At least 2 chars", "No more than 20 chars"], defaultValue: "", }, + play: async ({ canvasElement, step }) => { + await step("has visible error messages", async () => { + const element = queryOdysseySelector({ + canvas: within(canvasElement), + componentName: "TextField", + templateArgs: { + label: "Destination", + }, + }).element; + + expect(element).toHaveErrorMessage(/This field is required:/); + expect(element).toHaveErrorMessage(/At least 2 chars/); + expect(element).toHaveErrorMessage(/No more than 20 chars/); + }); + }, }; export const FullWidth: StoryObj = { @@ -267,6 +296,21 @@ export const Hint: StoryObj = { hint: "Specify your destination within the Sol system.", defaultValue: "", }, + play: async ({ canvasElement, step }) => { + await step("has visible label", async () => { + const element = queryOdysseySelector({ + canvas: within(canvasElement), + componentName: "TextField", + templateArgs: { + label: "Destination", + }, + }).element; + + expect(element).toHaveAccessibleDescription( + "Specify your destination within the Sol system.", + ); + }); + }, }; export const HintLink: StoryObj = { From c3dd605d9f923f8a9b04e84f79823207562bdd27 Mon Sep 17 00:00:00 2001 From: Anirudh Mani Date: Mon, 26 Aug 2024 14:59:14 -0700 Subject: [PATCH 3/3] feat: add checkbox test selector --- packages/odyssey-react-mui/src/Checkbox.tsx | 22 +++++++++++++++++ .../test-selectors/odysseyTestSelectors.ts | 2 ++ .../odyssey-mui/Checkbox/Checkbox.stories.tsx | 24 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/packages/odyssey-react-mui/src/Checkbox.tsx b/packages/odyssey-react-mui/src/Checkbox.tsx index 3931721b4f..7a228d39e8 100644 --- a/packages/odyssey-react-mui/src/Checkbox.tsx +++ b/packages/odyssey-react-mui/src/Checkbox.tsx @@ -28,8 +28,30 @@ import { getControlState, } from "./inputUtils"; import { FieldComponentProps } from "./FieldComponentProps"; +import { type FeatureTestSelector } from "./test-selectors"; import { Typography } from "./Typography"; +export const CheckboxTestSelectors = { + // feature: { + // hint: { + // selector: { + // method: "ByText", + // templateVariableNames: ["hint"], + // text: "${hint}", + // }, + // } + // }, + // label: ["hint"] + selector: { + method: "ByRole", + options: { + name: "${label}", + }, + role: "checkbox", + templateVariableNames: ["label"], + }, +} as const satisfies FeatureTestSelector; + export const checkboxValidityValues = ["valid", "invalid", "inherit"] as const; export type CheckboxProps = { diff --git a/packages/odyssey-react-mui/src/test-selectors/odysseyTestSelectors.ts b/packages/odyssey-react-mui/src/test-selectors/odysseyTestSelectors.ts index 348d0b4b67..226a5af68d 100644 --- a/packages/odyssey-react-mui/src/test-selectors/odysseyTestSelectors.ts +++ b/packages/odyssey-react-mui/src/test-selectors/odysseyTestSelectors.ts @@ -13,12 +13,14 @@ import { type FeatureTestSelector } from "./featureTestSelector"; import { BannerTestSelectors } from "../Banner"; import { CalloutTestSelectors } from "../Callout"; +import { CheckboxTestSelectors } from "../Checkbox"; import { TabsTestSelectors } from "../Tabs"; import { TextFieldTestSelectors } from "../TextField"; export const odysseyTestSelectors = { Banner: BannerTestSelectors, Callout: CalloutTestSelectors, + Checkbox: CheckboxTestSelectors, Tabs: TabsTestSelectors, TextField: TextFieldTestSelectors, } as const satisfies Record; diff --git a/packages/odyssey-storybook/src/components/odyssey-mui/Checkbox/Checkbox.stories.tsx b/packages/odyssey-storybook/src/components/odyssey-mui/Checkbox/Checkbox.stories.tsx index 24534fd826..1c59281ddc 100644 --- a/packages/odyssey-storybook/src/components/odyssey-mui/Checkbox/Checkbox.stories.tsx +++ b/packages/odyssey-storybook/src/components/odyssey-mui/Checkbox/Checkbox.stories.tsx @@ -18,6 +18,7 @@ import { import { Meta, StoryObj } from "@storybook/react"; import { userEvent, within } from "@storybook/testing-library"; import { expect } from "@storybook/jest"; +import { queryOdysseySelector } from "@okta/odyssey-react-mui/test-selectors"; import { fieldComponentPropsMetaData } from "../../../fieldComponentPropsMetaData"; import { MuiThemeDecorator } from "../../../../.storybook/components"; @@ -267,6 +268,17 @@ export const ReadOnly: StoryObj = { }, play: async ({ canvasElement, step }) => { await checkTheBox({ canvasElement, step })("ReadOnly Checkbox"); + await step("has aria read only set", async () => { + const element = queryOdysseySelector({ + canvas: within(canvasElement), + componentName: "Checkbox", + templateArgs: { + label: "Automatically assign Okta Admin Console", + }, + }).element; + + expect(element?.ariaReadOnly).toBeTruthy(); + }); }, }; @@ -284,6 +296,18 @@ export const Hint: StoryObj = { }, play: async ({ canvasElement, step }) => { await checkTheBox({ canvasElement, step })("Checkbox Hint"); + // This is commented because there's a separate ticket to cover this work: OKTA-793465 + // await step("has visible hint", async () => { + // const element = queryOdysseySelector({ + // canvas: within(canvasElement), + // componentName: "Checkbox", + // templateArgs: { + // label:"I agree to the terms and conditions", + // } + // }).element; + + // expect(element).toHaveAccessibleDescription(/Really helpful hint/); + // }); }, };