From 2488f8a42602100841c2b950a95b2042f1b26849 Mon Sep 17 00:00:00 2001 From: Mikkel Laursen Date: Sun, 12 Jan 2025 22:37:48 -0500 Subject: [PATCH] chore(docs): started creating testing recipes --- .../(markdown)/testing/recipes/page.mdx | 141 ++++++++++++++++++ .../src/components/MainLayout/navItems.ts | 5 + packages/core/src/form/__tests__/Select.tsx | 9 +- packages/core/src/test-utils/index.ts | 1 + packages/core/src/test-utils/utils/queries.ts | 74 +++++++++ 5 files changed, 227 insertions(+), 3 deletions(-) create mode 100644 apps/docs/src/app/(main)/(markdown)/testing/recipes/page.mdx create mode 100644 packages/core/src/test-utils/utils/queries.ts diff --git a/apps/docs/src/app/(main)/(markdown)/testing/recipes/page.mdx b/apps/docs/src/app/(main)/(markdown)/testing/recipes/page.mdx new file mode 100644 index 0000000000..b40e70bfe7 --- /dev/null +++ b/apps/docs/src/app/(main)/(markdown)/testing/recipes/page.mdx @@ -0,0 +1,141 @@ +# Recipes + +This page will provide common testing recipes for components through ReactMD. + +# Autocomplete + +TODO + +# Checkbox + +TODO + +# Dialog/Sheet + +TODO + +# Expansion Panel + +TODO + +# Menu + +TODO + +# Radio + +TODO + +# Select + +> All of these examples will use the [Simple Select](/components/select#simple-select) example code. + +## Find and Change Value + +This example showcases how to: + +- find the `Select` component +- find and verify the current selected option +- change the selected option + +```tsx +import { screen, rmdRender, userEvent } from "@react-md/core/test-utils"; + +it("should be able to change value", async () => { + const user = userEvent.setup(); + rmdRender(); + + // this is the clickable element that allows the listbox of options to appear + const select = screen.getByRole("combobox", { name: "Label" }); + // this stores the current value + const selectValue = screen.getByRole("textbox", { hidden: true }); + expect(selectValue).toHaveValue(""); + + await user.click(select); + // the `name` should be the accessible text in any of the available options + await user.click(screen.getByRole("option", { name: "Option 1" })); + expect(selectValue).toHaveValue("a"); + + await user.click(select); + + // the `Option 1` should now be selected + expect(() => + screen.getByRole("option", { name: "Option 1", selected: true }) + ).not.toThrow(); +}); +``` + +## Verify the Display Value + +This example showcases how to find and verify the selected option's display +value while the Select listbox is closed using the `getSelectParts` test util query. + +```tsx +import { + getSelectParts, + screen, + rmdRender, + userEvent, +} from "@react-md/core/test-utils"; + +it("should be able to verify the display value", async () => { + const user = userEvent.setup(); + rmdRender(); + + const { select, selectValue, selectDisplay } = getSelectParts({ + name: "Label", + }); + // this isn't required, but added to show what element this is + expect(selectDisplay).toHaveClass("rmd-selected-option"); + + // there is currently no selected value + expect(selectDisplay).toHaveTextContent(""); + + await user.click(select); + await user.click(screen.getByRole("option", { name: "Option 1" })); + expect(selectValue).toHaveValue("a"); + expect(selectDisplay).toHaveTextContent("Option 1"); +}); +``` + +# Slider + +TODO + +# Snackbar + +TODO + +# Switch + +## Find and Toggle + +The `Switch` is an extension of an `` with +`role="switch"`, so the element can be changed just like a `Checkbox`. + +```tsx +import { screen, rmdRender, userEvent } from "@react-md/core/test-utils"; + +it("should be able to change the checked state", async () => { + const user = userEvent.setup(); + rmdRender(); + + const switchElement = screen.getByRole("switch", { name: "Label" }); + expect(switchElement).not.toBeChecked(); + + await user.click(switchElement); + expect(switchElement).toBeChecked(); +}); +``` + +# Tabs + +TODO + +# Tooltip + +TODO + +# Tree + +TODO diff --git a/apps/docs/src/components/MainLayout/navItems.ts b/apps/docs/src/components/MainLayout/navItems.ts index 2beda909d5..e525a3d112 100644 --- a/apps/docs/src/components/MainLayout/navItems.ts +++ b/apps/docs/src/components/MainLayout/navItems.ts @@ -601,6 +601,11 @@ export const navItems: readonly NavigationItem[] = [ href: "/quickstart", children: "Quickstart", }, + { + type: "route", + href: "/recipes", + children: "Recipes", + }, { type: "route", href: "/polyfills", diff --git a/packages/core/src/form/__tests__/Select.tsx b/packages/core/src/form/__tests__/Select.tsx index a772345933..08835c410b 100644 --- a/packages/core/src/form/__tests__/Select.tsx +++ b/packages/core/src/form/__tests__/Select.tsx @@ -6,6 +6,7 @@ import { FontIcon } from "../../icon/FontIcon.js"; import { CircularProgress } from "../../progress/CircularProgress.js"; import { act, + getSelectParts, rmdRender, screen, userEvent, @@ -41,9 +42,11 @@ function render( ) { const user = userEvent.setup(); const { rerender } = rmdRender(); - const select = screen.getByRole("combobox", { name: "Select" }); - const selected = screen.getByTestId("selected"); - const selectValue = screen.getByRole("textbox", { hidden: true }); + const { + select, + selectValue, + selectDisplay: selected, + } = getSelectParts({ name: "Select" }); return { rerender: (props: Partial>) => { diff --git a/packages/core/src/test-utils/index.ts b/packages/core/src/test-utils/index.ts index 803037c53a..7b3de8784d 100644 --- a/packages/core/src/test-utils/index.ts +++ b/packages/core/src/test-utils/index.ts @@ -2,4 +2,5 @@ export * from "@testing-library/react"; export * from "@testing-library/user-event"; export * from "./mocks/match-media.js"; export * from "./render.js"; +export * from "./utils/queries.js"; export * from "./utils/resize-observer.js"; diff --git a/packages/core/src/test-utils/utils/queries.ts b/packages/core/src/test-utils/utils/queries.ts new file mode 100644 index 0000000000..009cd09032 --- /dev/null +++ b/packages/core/src/test-utils/utils/queries.ts @@ -0,0 +1,74 @@ +import { + type BoundFunctions, + type ByRoleOptions, + type queries, + screen, + within, +} from "@testing-library/dom"; + +/** + * @since 6.0.0 + */ +export interface SelectParts { + select: HTMLDivElement; + selectValue: HTMLInputElement; + selectDisplay: HTMLDivElement; +} + +/** + * @since 6.0.0 + */ +export interface GetSelectPartsOptions extends ByRoleOptions { + /** @defaultValue `screen` */ + container?: BoundFunctions; +} + +/** + * @example Simple Example + * ```tsx + * import { + * getSelectParts, + * screen, + * rmdRender, + * userEvent, + * } from "@react-md/core/test-utils"; + * + * it("should be able to verify the display value", async () => { + * const user = userEvent.setup(); + * rmdRender(); + * + * const { select, selectValue, selectDisplay } = getSelectParts({ + * name: "Label", + * }); + * // this isn't required, but added to show what element this is + * expect(selectDisplay).toHaveClass("rmd-selected-option"); + * + * // there is currently no selected value + * expect(selectDisplay).toHaveTextContent(""); + * + * await user.click(select); + * await user.click(screen.getByRole("option", { name: "Option 1" })); + * expect(selectValue).toHaveValue("a"); + * expect(selectDisplay).toHaveTextContent("Option 1"); + * }); + * ``` + * + * @since 6.0.0 + */ +export function getSelectParts(options: GetSelectPartsOptions): SelectParts { + const { container = screen, ...byRoleOptions } = options; + const select = container.getByRole("combobox", byRoleOptions); + const selectValue = within(select).getByRole("textbox", { + hidden: true, + }); + const selectDisplay = select.firstElementChild; + if (!(selectDisplay instanceof HTMLDivElement)) { + throw new Error("Unable to find the `Select` display value element"); + } + + return { + select, + selectValue, + selectDisplay, + }; +}