From 9045cf539f46e0022373faec7c3b6ea32ca37190 Mon Sep 17 00:00:00 2001 From: Andrew Holloway Date: Mon, 18 Sep 2023 11:13:42 -0500 Subject: [PATCH] feat: add event hooks to existing components (#1757) - accordion Add event tests for remaining form field components --- src/components/Accordion/Accordion.test.tsx | 27 ++++++++++++++ src/components/Accordion/Accordion.tsx | 16 +++++++-- .../Breadcrumbs/Breadcrumbs.stories.tsx | 12 +------ src/components/Breadcrumbs/Breadcrumbs.tsx | 3 +- src/components/Checkbox/Checkbox.test.tsx | 9 +++-- src/components/InputField/InputField.test.ts | 7 ---- src/components/InputField/InputField.test.tsx | 36 +++++++++++++++++++ ....test.ts.snap => InputField.test.tsx.snap} | 0 src/components/Popover/Popover.tsx | 3 +- src/components/Radio/Radio.test.tsx | 27 +++++++++++--- 10 files changed, 107 insertions(+), 33 deletions(-) delete mode 100644 src/components/InputField/InputField.test.ts create mode 100644 src/components/InputField/InputField.test.tsx rename src/components/InputField/__snapshots__/{InputField.test.ts.snap => InputField.test.tsx.snap} (100%) diff --git a/src/components/Accordion/Accordion.test.tsx b/src/components/Accordion/Accordion.test.tsx index 423e91913..c603f3267 100644 --- a/src/components/Accordion/Accordion.test.tsx +++ b/src/components/Accordion/Accordion.test.tsx @@ -76,4 +76,31 @@ describe('', () => { }); expect(onClose).toHaveBeenCalledTimes(1); }); + + it('should call onOpen callback when accordion opens', async () => { + const user = userEvent.setup(); + const onClose = jest.fn(); + const onOpen = jest.fn(); + render( + + + + Accordion Button + + Accordion Panel + + , + ); + const accordionButton = screen.getByTestId('accordion-button'); + + await act(async () => { + await user.click(accordionButton); + }); + expect(onOpen).toHaveBeenCalledTimes(1); + expect(onClose).not.toHaveBeenCalled(); + }); }); diff --git a/src/components/Accordion/Accordion.tsx b/src/components/Accordion/Accordion.tsx index cd79a86d2..5932b832d 100644 --- a/src/components/Accordion/Accordion.tsx +++ b/src/components/Accordion/Accordion.tsx @@ -2,6 +2,7 @@ import { Disclosure } from '@headlessui/react'; import clsx from 'clsx'; import React, { createContext, useContext } from 'react'; import { ENTER_KEYCODE, SPACEBAR_KEYCODE } from '../../util/keycodes'; +import type { Size } from '../../util/variant-types'; import Button from '../Button'; import Heading, { type HeadingElement } from '../Heading'; import Icon from '../Icon'; @@ -27,7 +28,7 @@ type AccordionProps = { /** * Various Accordion sizes. Defaults to 'md'. */ - size?: 'sm' | 'md'; + size?: Extract; }; type AccordionButtonProps = { @@ -45,9 +46,13 @@ type AccordionButtonProps = { */ headingAs?: HeadingElement; /** - * Callback called when accordion is closed. + * Callback for when accordion is closed. */ onClose?: () => void; + /** + * Callback for when according is opened. + */ + onOpen?: () => void; }; type AccordionPanelProps = { @@ -138,6 +143,7 @@ const AccordionButton = ({ className, headingAs, onClose, + onOpen, ...other }: AccordionButtonProps) => { const { @@ -168,12 +174,18 @@ const AccordionButton = ({ if (open && onClose) { onClose(); } + if (!open && onOpen) { + onOpen(); + } }} onKeyDown={(e) => { if (e.key === SPACEBAR_KEYCODE || e.key === ENTER_KEYCODE) { if (open && onClose) { onClose(); } + if (!open && onOpen) { + onOpen(); + } } }} status="neutral" diff --git a/src/components/Breadcrumbs/Breadcrumbs.stories.tsx b/src/components/Breadcrumbs/Breadcrumbs.stories.tsx index ea108f70d..3ee0edeee 100644 --- a/src/components/Breadcrumbs/Breadcrumbs.stories.tsx +++ b/src/components/Breadcrumbs/Breadcrumbs.stories.tsx @@ -28,17 +28,7 @@ export default { parameters: { badges: ['1.0'], }, - decorators: [ - (Story) => ( -
- {Story()} -
- ), - ], + decorators: [(Story) =>
{Story()}
], } as Meta; type Args = React.ComponentProps; diff --git a/src/components/Breadcrumbs/Breadcrumbs.tsx b/src/components/Breadcrumbs/Breadcrumbs.tsx index b08adca1c..416988e36 100644 --- a/src/components/Breadcrumbs/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs/Breadcrumbs.tsx @@ -2,7 +2,6 @@ import clsx from 'clsx'; import { debounce } from 'lodash'; import React, { createContext, useContext, type ReactNode } from 'react'; import { flattenReactChildren } from '../../util/flattenReactChildren'; -// import BreadcrumbsItem from '../BreadcrumbsItem'; import Icon from '../Icon'; import Menu from '../Menu'; import styles from './Breadcrumbs.module.css'; @@ -250,7 +249,7 @@ export const BreadcrumbsItem = ({ aria-label="Show more breadcrumbs" className={ellipsisButtonClassName} > - ... + … {menuItems} diff --git a/src/components/Checkbox/Checkbox.test.tsx b/src/components/Checkbox/Checkbox.test.tsx index 98a2b08e6..6039c71a5 100644 --- a/src/components/Checkbox/Checkbox.test.tsx +++ b/src/components/Checkbox/Checkbox.test.tsx @@ -1,14 +1,11 @@ import { generateSnapshots } from '@chanzuckerberg/story-utils'; import type { StoryFile } from '@storybook/testing-react'; -import { composeStories } from '@storybook/testing-react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { Checkbox } from './Checkbox'; import * as stories from './Checkbox.stories'; -const { Default } = composeStories(stories); - describe('', () => { generateSnapshots(stories as StoryFile); @@ -18,12 +15,14 @@ describe('', () => { expect(container.firstChild).toMatchSnapshot(); }); - test('should toggle the checkbox with space', async () => { + test('should toggle the checkbox with space and trigger onChange', async () => { const user = userEvent.setup(); - render(); + const onChange = jest.fn(); + render(); const checkbox = screen.getByRole('checkbox'); checkbox.focus(); await user.keyboard(' '); expect(checkbox).toBeChecked(); + expect(onChange).toHaveBeenCalledTimes(1); }); }); diff --git a/src/components/InputField/InputField.test.ts b/src/components/InputField/InputField.test.ts deleted file mode 100644 index 05c41fb39..000000000 --- a/src/components/InputField/InputField.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { generateSnapshots } from '@chanzuckerberg/story-utils'; -import type { StoryFile } from '@storybook/testing-react'; -import * as stories from './InputField.stories'; - -describe('', () => { - generateSnapshots(stories as StoryFile); -}); diff --git a/src/components/InputField/InputField.test.tsx b/src/components/InputField/InputField.test.tsx new file mode 100644 index 000000000..b5aca0bbd --- /dev/null +++ b/src/components/InputField/InputField.test.tsx @@ -0,0 +1,36 @@ +import { generateSnapshots } from '@chanzuckerberg/story-utils'; +import type { StoryFile } from '@storybook/testing-react'; +import { act, render, screen } from '@testing-library/react'; + +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { InputField } from './InputField'; + +import * as stories from './InputField.stories'; + +describe('', () => { + generateSnapshots(stories as StoryFile); + + it('handles changes to the text within the component', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + + render( + , + ); + const input = screen.getByTestId('test-input'); + const testText = 'typing'; + + input.focus(); + + await act(async () => { + await user.keyboard(testText); + }); + + expect(onChange).toHaveBeenCalledTimes(testText.length); + }); +}); diff --git a/src/components/InputField/__snapshots__/InputField.test.ts.snap b/src/components/InputField/__snapshots__/InputField.test.tsx.snap similarity index 100% rename from src/components/InputField/__snapshots__/InputField.test.ts.snap rename to src/components/InputField/__snapshots__/InputField.test.tsx.snap diff --git a/src/components/Popover/Popover.tsx b/src/components/Popover/Popover.tsx index 110601958..c9d4b7415 100644 --- a/src/components/Popover/Popover.tsx +++ b/src/components/Popover/Popover.tsx @@ -26,7 +26,7 @@ export const PopoverContext = createContext({}); /** * `import {Popover} from "@chanzuckerberg/eds";` * - * General-purpose floating menus that appear proximal to a trigger point + * General-purpose floating containers that appear proximal to a trigger point */ export const Popover = ({ placement = 'bottom-start', @@ -36,7 +36,6 @@ export const Popover = ({ ...other }: PopoverProps) => { const [referenceElement, setReferenceElement] = useState(); - const [popperElement, setPopperElement] = useState(); // Leverage usePopper hook from Popper js for additional popover behavior and adds behavior to context for consumption by subcomponents. diff --git a/src/components/Radio/Radio.test.tsx b/src/components/Radio/Radio.test.tsx index f085fb736..a855b8fcd 100644 --- a/src/components/Radio/Radio.test.tsx +++ b/src/components/Radio/Radio.test.tsx @@ -1,24 +1,43 @@ import { generateSnapshots } from '@chanzuckerberg/story-utils'; import type { StoryFile } from '@storybook/testing-react'; -import { composeStories } from '@storybook/testing-react'; import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; +import { Radio } from './Radio'; import * as stories from './Radio.stories'; -const { Default } = composeStories(stories); - describe('', () => { generateSnapshots(stories as StoryFile); test('should toggle the radio with space', async () => { const user = userEvent.setup(); - render(); + const onChange = jest.fn(); + + function ControlledRadio() { + const [checked, setChecked] = React.useState(false); + const handleChange = () => { + setChecked(!checked); + onChange(); + }; + + return ( + + ); + } + + render(); const radio = screen.getByRole('radio'); radio.focus(); + await act(async () => { await user.keyboard(' '); }); + expect(radio).toBeChecked(); + expect(onChange).toHaveBeenCalledTimes(1); }); });