From c4312d532a8a99e867292dc146fbed3fb18833ca Mon Sep 17 00:00:00 2001 From: Andrew Holloway Date: Mon, 22 Apr 2024 17:55:47 -0500 Subject: [PATCH] test: add snapshot tests to 2.0 components (#1920) --- .../Accordion/Accordion-v2.test.tsx | 115 ++ .../__snapshots__/Accordion-v2.test.tsx.snap | 1411 +++++++++++++++++ src/components/Button/Button-v2.test.tsx | 50 + .../__snapshots__/Button-v2.test.tsx.snap | 588 +++++++ .../ButtonGroup/ButtonGroup-v2.test.ts | 7 + .../__snapshots__/ButtonGroup-v2.test.ts.snap | 155 ++ ...Checkbox.test.tsx => Checkbox-v2.test.tsx} | 4 +- ...est.tsx.snap => Checkbox-v2.test.tsx.snap} | 185 ++- .../InputField/InputField-v2.test.tsx | 78 + .../__snapshots__/InputField-v2.test.tsx.snap | 695 ++++++++ src/components/Link/Link-v2.test.tsx | 66 + .../Link/__snapshots__/Link-v2.test.tsx.snap | 147 ++ src/components/Menu/Menu-v2.test.tsx | 96 ++ .../Menu/__snapshots__/Menu-v2.test.tsx.snap | 183 +++ src/components/Modal/Modal-v2.test.tsx | 134 ++ .../__snapshots__/Modal-v2.test.tsx.snap | 958 +++++++++++ src/components/Select/Select-v2.test.tsx | 286 ++++ .../__snapshots__/Select-v2.test.tsx.snap | 752 +++++++++ .../TextareaField/TextareaField-v2.test.tsx | 103 ++ .../TextareaField-v2.test.tsx.snap | 686 ++++++++ 20 files changed, 6628 insertions(+), 71 deletions(-) create mode 100644 src/components/Accordion/Accordion-v2.test.tsx create mode 100644 src/components/Accordion/__snapshots__/Accordion-v2.test.tsx.snap create mode 100644 src/components/Button/Button-v2.test.tsx create mode 100644 src/components/Button/__snapshots__/Button-v2.test.tsx.snap create mode 100644 src/components/ButtonGroup/ButtonGroup-v2.test.ts create mode 100644 src/components/ButtonGroup/__snapshots__/ButtonGroup-v2.test.ts.snap rename src/components/Checkbox/{Checkbox.test.tsx => Checkbox-v2.test.tsx} (91%) rename src/components/Checkbox/__snapshots__/{Checkbox.test.tsx.snap => Checkbox-v2.test.tsx.snap} (54%) create mode 100644 src/components/InputField/InputField-v2.test.tsx create mode 100644 src/components/InputField/__snapshots__/InputField-v2.test.tsx.snap create mode 100644 src/components/Link/Link-v2.test.tsx create mode 100644 src/components/Link/__snapshots__/Link-v2.test.tsx.snap create mode 100644 src/components/Menu/Menu-v2.test.tsx create mode 100644 src/components/Menu/__snapshots__/Menu-v2.test.tsx.snap create mode 100644 src/components/Modal/Modal-v2.test.tsx create mode 100644 src/components/Modal/__snapshots__/Modal-v2.test.tsx.snap create mode 100644 src/components/Select/Select-v2.test.tsx create mode 100644 src/components/Select/__snapshots__/Select-v2.test.tsx.snap create mode 100644 src/components/TextareaField/TextareaField-v2.test.tsx create mode 100644 src/components/TextareaField/__snapshots__/TextareaField-v2.test.tsx.snap diff --git a/src/components/Accordion/Accordion-v2.test.tsx b/src/components/Accordion/Accordion-v2.test.tsx new file mode 100644 index 000000000..4bc9444a2 --- /dev/null +++ b/src/components/Accordion/Accordion-v2.test.tsx @@ -0,0 +1,115 @@ +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 { Accordion } from './Accordion-v2'; +import * as stories from './Accordion-v2.stories'; + +const { Default } = composeStories(stories); + +describe('', () => { + generateSnapshots(stories as StoryFile); + + it('should open and close Accordion panel clicking Accordion button', async () => { + const user = userEvent.setup(); + render(); + expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument(); + const accordionButton = screen.getByTestId('accordion-button'); + + await user.click(accordionButton); + expect(screen.getByTestId('accordion-panel')).toBeInTheDocument(); + + await user.click(accordionButton); + expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument(); + }); + + it('should open and close Accordion panel with space and enter keys on the Accordion button', async () => { + const user = userEvent.setup(); + render(); + const accordionButton = screen.getByTestId('accordion-button'); + accordionButton.focus(); + + await user.keyboard(' '); + expect(screen.getByTestId('accordion-panel')).toBeInTheDocument(); + + await user.keyboard(' '); + expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument(); + + await user.keyboard('{enter}'); + expect(screen.getByTestId('accordion-panel')).toBeInTheDocument(); + + await user.keyboard('{enter}'); + expect(screen.queryByTestId('accordion-panel')).not.toBeInTheDocument(); + }); + + it('should call onClose callback when accordion closes', async () => { + const user = userEvent.setup(); + const onClose = jest.fn(); + render( + + + + Accordion Button + + Accordion Panel + + , + ); + const accordionButton = screen.getByTestId('accordion-button'); + + await user.click(accordionButton); + 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.getByRole('button'); + + await user.click(accordionButton); + expect(onOpen).toHaveBeenCalledTimes(1); + expect(onClose).not.toHaveBeenCalled(); + }); + + it('should not call onOpen callback when accordion opens on an empty row', async () => { + const user = userEvent.setup(); + const onClose = jest.fn(); + const onOpen = jest.fn(); + render( + + + + Accordion Button + + Accordion Panel + + , + ); + const accordionButton = screen.getByRole('button'); + + await user.click(accordionButton); + expect(onOpen).not.toHaveBeenCalled(); + expect(onClose).not.toHaveBeenCalled(); + }); +}); diff --git a/src/components/Accordion/__snapshots__/Accordion-v2.test.tsx.snap b/src/components/Accordion/__snapshots__/Accordion-v2.test.tsx.snap new file mode 100644 index 000000000..227490214 --- /dev/null +++ b/src/components/Accordion/__snapshots__/Accordion-v2.test.tsx.snap @@ -0,0 +1,1411 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Default story renders snapshot 1`] = ` +
+
+
+ +
+
+
+`; + +exports[` EmptyStackedOpen story renders snapshot 1`] = ` +
+
+
+ +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+
+`; + +exports[` HasLeadingIcon story renders snapshot 1`] = ` +
+
+
+ +
+
+
+`; + +exports[` HasLeadingNumberIcon story renders snapshot 1`] = ` +
+
+
+ +
+
+
+`; + +exports[` Small story renders snapshot 1`] = ` +
+
+
+ +
+
+
+`; + +exports[` Stacked story renders snapshot 1`] = ` +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+ +
+
+ +
+
+ +
+
+
+`; + +exports[` StackedOpen story renders snapshot 1`] = ` +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+
+`; + +exports[` StackedSmall story renders snapshot 1`] = ` +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+ +
+
+ +
+
+ +
+
+
+`; + +exports[` StackedSmallOpen story renders snapshot 1`] = ` +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+ +
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla amet, massa ultricies iaculis. Quam lacus maecenas nibh malesuada. At tristique et ullamcorper rhoncus amet pharetra aliquet tortor. Suscipit dui, nunc sit dui tellus massa laoreet tellus. +
+
+
+
+`; + +exports[` TitleAndSubtitle story renders snapshot 1`] = ` +
+
+
+ +
+
+
+`; + +exports[` UsingComplexHeaders story renders snapshot 1`] = ` +
+
+
+ +
+
+ +
+
+
+`; + +exports[` UsingRenderProp story renders snapshot 1`] = ` +
+
+
+ +
+
+
+`; + +exports[` WithLargeHeader story renders snapshot 1`] = ` +
+
+
+ +
+
+
+`; diff --git a/src/components/Button/Button-v2.test.tsx b/src/components/Button/Button-v2.test.tsx new file mode 100644 index 000000000..d8a401069 --- /dev/null +++ b/src/components/Button/Button-v2.test.tsx @@ -0,0 +1,50 @@ +import { generateSnapshots } from '@chanzuckerberg/story-utils'; +import type { StoryFile } from '@storybook/testing-react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { Button } from './Button-v2'; +import * as stories from './Button-v2.stories'; + +describe('); + + expect(screen.getByRole('button')).toHaveTextContent('Click'); + }); + + it('fires callback on click', async () => { + const user = userEvent.setup(); + const onClick = jest.fn(); + render(); + + await user.click(screen.getByRole('button')); + expect(onClick).toHaveBeenCalled(); + }); + + it('passes test ids down properly', () => { + render(); + expect(screen.getByTestId('example-test-id')).toMatchSnapshot(); + }); + + it('passes class names down properly', () => { + render( + , + ); + expect(screen.getByTestId('example-class-name')).toMatchSnapshot(); + }); + + it('forwards refs', () => { + const ref = React.createRef(); + render(); + + ref.current!.focus(); + + const button = screen.getByRole('button'); + expect(button).toHaveFocus(); + }); +}); diff --git a/src/components/Button/__snapshots__/Button-v2.test.tsx.snap b/src/components/Button/__snapshots__/Button-v2.test.tsx.snap new file mode 100644 index 000000000..9cb12fd72 --- /dev/null +++ b/src/components/Button/__snapshots__/Button-v2.test.tsx.snap @@ -0,0 +1,588 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` + + +
+`; + +exports[` +`; + +exports[` + + + +`; + +exports[` + + + +`; + +exports[` + + + +`; + +exports[` + + + +`; + +exports[` + + + + +`; + +exports[` + + + +`; + +exports[` + + + +`; + +exports[` +`; + +exports[` + . Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis. Nullam sit amet iaculis erat. Nulla id tellus ante. + + +`; + +exports[` +`; + +exports[` +`; diff --git a/src/components/ButtonGroup/ButtonGroup-v2.test.ts b/src/components/ButtonGroup/ButtonGroup-v2.test.ts new file mode 100644 index 000000000..549a2888d --- /dev/null +++ b/src/components/ButtonGroup/ButtonGroup-v2.test.ts @@ -0,0 +1,7 @@ +import { generateSnapshots } from '@chanzuckerberg/story-utils'; +import type { StoryFile } from '@storybook/testing-react'; +import * as stories from './ButtonGroup-v2.stories'; + +describe('', () => { + generateSnapshots(stories as StoryFile); +}); diff --git a/src/components/ButtonGroup/__snapshots__/ButtonGroup-v2.test.ts.snap b/src/components/ButtonGroup/__snapshots__/ButtonGroup-v2.test.ts.snap new file mode 100644 index 000000000..95bd2054c --- /dev/null +++ b/src/components/ButtonGroup/__snapshots__/ButtonGroup-v2.test.ts.snap @@ -0,0 +1,155 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Default story renders snapshot 1`] = ` +
+
+ + +
+
+`; + +exports[` HorizontalProgressive story renders snapshot 1`] = ` +
+
+ + +
+
+`; + +exports[` Vertical story renders snapshot 1`] = ` +
+
+ + +
+
+`; + +exports[` WithFiveButtons story renders snapshot 1`] = ` +
+
+ + + + + +
+
+`; diff --git a/src/components/Checkbox/Checkbox.test.tsx b/src/components/Checkbox/Checkbox-v2.test.tsx similarity index 91% rename from src/components/Checkbox/Checkbox.test.tsx rename to src/components/Checkbox/Checkbox-v2.test.tsx index 6039c71a5..fbffe38ac 100644 --- a/src/components/Checkbox/Checkbox.test.tsx +++ b/src/components/Checkbox/Checkbox-v2.test.tsx @@ -3,8 +3,8 @@ import type { StoryFile } 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'; +import { Checkbox } from './Checkbox-v2'; +import * as stories from './Checkbox-v2.stories'; describe('', () => { generateSnapshots(stories as StoryFile); diff --git a/src/components/Checkbox/__snapshots__/Checkbox.test.tsx.snap b/src/components/Checkbox/__snapshots__/Checkbox-v2.test.tsx.snap similarity index 54% rename from src/components/Checkbox/__snapshots__/Checkbox.test.tsx.snap rename to src/components/Checkbox/__snapshots__/Checkbox-v2.test.tsx.snap index 6209f78d2..6bc10871d 100644 --- a/src/components/Checkbox/__snapshots__/Checkbox.test.tsx.snap +++ b/src/components/Checkbox/__snapshots__/Checkbox-v2.test.tsx.snap @@ -2,7 +2,7 @@ exports[` Checked story renders snapshot 1`] = `
Checked story renders snapshot 1`] = ` - + +
`; exports[` Default story renders snapshot 1`] = `
Default story renders snapshot 1`] = ` id=":r0:" type="checkbox" /> - + +
`; exports[` Disabled story renders snapshot 1`] = `
Disabled story renders snapshot 1`] = ` id=":r5:" type="checkbox" /> - + +
Disabled story renders snapshot 1`] = ` id=":r6:" type="checkbox" /> - + +
Disabled story renders snapshot 1`] = ` id=":r7:" type="checkbox" /> - + +
@@ -115,107 +135,131 @@ exports[` Disabled story renders snapshot 2`] = ` id=":ra:" type="checkbox" /> - + + `; -exports[` Indeterminate story renders snapshot 1`] = ` +exports[` Error story renders snapshot 1`] = `
- + +
`; -exports[` LongLabels story renders snapshot 1`] = ` +exports[` Indeterminate story renders snapshot 1`] = `
- + +
`; -exports[` Medium story renders snapshot 1`] = ` +exports[` LongLabels story renders snapshot 1`] = `
- + +
`; -exports[` MediumChecked story renders snapshot 1`] = ` +exports[` WithSublabel story renders snapshot 1`] = `
- + + + Additional descriptive text + +
`; exports[` WithoutVisibleLabel story renders snapshot 1`] = `
WithoutVisibleLabel story renders snapshot 1`] = ` id=":r8:" type="checkbox" /> +
`; diff --git a/src/components/InputField/InputField-v2.test.tsx b/src/components/InputField/InputField-v2.test.tsx new file mode 100644 index 000000000..994d1e6f5 --- /dev/null +++ b/src/components/InputField/InputField-v2.test.tsx @@ -0,0 +1,78 @@ +import { generateSnapshots } from '@chanzuckerberg/story-utils'; +import type { StoryFile } from '@storybook/testing-react'; +import { render, screen } from '@testing-library/react'; + +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { InputField } from './InputField-v2'; + +import * as stories from './InputField-v2.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 user.keyboard(testText); + + expect(onChange).toHaveBeenCalledTimes(testText.length); + }); + + it('will not fire when maxLength is reached', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + const testText = 'typing'; + + render( + , + ); + const input = screen.getByTestId('test-input'); + + input.focus(); + await user.keyboard(testText); + + expect(onChange).toHaveBeenCalledTimes(0); + }); + + it('will fire when recommendedMaxLength is reached', async () => { + const user = userEvent.setup(); + const onChange = jest.fn(); + const testText = 'typing'; + + render( + , + ); + const input = screen.getByTestId('test-input'); + + input.focus(); + await user.keyboard(testText); + + expect(onChange).toHaveBeenCalledTimes(testText.length); + }); +}); diff --git a/src/components/InputField/__snapshots__/InputField-v2.test.tsx.snap b/src/components/InputField/__snapshots__/InputField-v2.test.tsx.snap new file mode 100644 index 000000000..a4d1ffbfa --- /dev/null +++ b/src/components/InputField/__snapshots__/InputField-v2.test.tsx.snap @@ -0,0 +1,695 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Default story renders snapshot 1`] = ` +
+
+
+ +
+
+ +
+
+ This is a fieldnote. +
+
+
+`; + +exports[` Disabled story renders snapshot 1`] = ` +
+
+
+ +
+
+ +
+
+ This InputField is disabled +
+
+
+`; + +exports[` Error story renders snapshot 1`] = ` +
+
+
+ +
+
+ +
+
+ + + error + + + + This is a fieldnote with an error. +
+
+
+`; + +exports[` InputWithin story renders snapshot 1`] = ` +
+
+
+ +
+
+ +
+ +
+
+
+
+`; + +exports[` LeadingIcon story renders snapshot 1`] = ` +
+
+
+ +
+ +
+
+
+
+`; + +exports[` NoFieldnote story renders snapshot 1`] = ` +
+
+
+ +
+
+ +
+
+
+`; + +exports[` NoVisibleLabel story renders snapshot 1`] = ` +
+
+
+
+ +
+
+ This input field has no visible label +
+
+
+`; + +exports[` Password story renders snapshot 1`] = ` +
+
+
+ +
+
+ +
+
+
+`; + +exports[` ReadOnly story renders snapshot 1`] = ` +
+
+
+ +
+
+ +
+
+ This will show up like text, but not be interactive +
+
+
+`; + +exports[` Required story renders snapshot 1`] = ` +
+
+
+ + + (Required) + +
+
+ +
+
+ This is a fieldnote for a required input field. +
+
+
+`; + +exports[` ShowHint story renders snapshot 1`] = ` +
+
+
+ + + (Optional) + +
+
+ +
+
+
+`; + +exports[` TabularInput story renders snapshot 1`] = ` +
+ + + + + + + + + + + + + +
+ Label + + Field +
+ + +
+
+ +
+
+
+
+`; + +exports[` Warning story renders snapshot 1`] = ` +
+
+
+ +
+
+ +
+
+ + + warning + + + + This uses the warning treatment and also applies to the field note +
+
+
+`; + +exports[` WithAMaxLength story renders snapshot 1`] = ` +
+
+
+ +
+ + 17 + + + / + 15 +
+
+
+ +
+ +
+`; + +exports[` WithARecommendedLength story renders snapshot 1`] = ` +
+
+
+ +
+ + 17 + + + / + 15 +
+
+
+ +
+ +
+`; + +exports[` WithBothMaxAndRecommendedLength story renders snapshot 1`] = ` +
+
+
+ +
+ + 17 + + + / + 15 +
+
+
+ +
+ +
+
+`; diff --git a/src/components/Link/Link-v2.test.tsx b/src/components/Link/Link-v2.test.tsx new file mode 100644 index 000000000..eed27a220 --- /dev/null +++ b/src/components/Link/Link-v2.test.tsx @@ -0,0 +1,66 @@ +import { generateSnapshots } from '@chanzuckerberg/story-utils'; +import type { StoryFile } from '@storybook/testing-react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { Link } from './Link-v2'; +import * as stories from './Link-v2.stories'; + +describe('', () => { + generateSnapshots(stories as StoryFile); + + it('renders the text in the link', () => { + render(Click); + + expect(screen.getByRole('link')).toHaveTextContent('Click'); + }); + + it('fires callback on click', async () => { + const user = userEvent.setup(); + const onClick = jest.fn(); + render( + + Click + , + ); + + await user.click(screen.getByRole('link')); + expect(onClick).toHaveBeenCalled(); + }); + + it('passes test ids down properly', () => { + render( + + Click + , + ); + expect(screen.getByTestId('example-test-id')).toMatchSnapshot(); + }); + + it('passes class names down properly', () => { + render( + + Click + , + ); + expect(screen.getByTestId('example-class-name')).toMatchSnapshot(); + }); + + it('forwards refs', () => { + const ref = React.createRef(); + render( + + Click + , + ); + + ref.current!.focus(); + + const link = screen.getByRole('link'); + expect(link).toHaveFocus(); + }); +}); diff --git a/src/components/Link/__snapshots__/Link-v2.test.tsx.snap b/src/components/Link/__snapshots__/Link-v2.test.tsx.snap new file mode 100644 index 000000000..ff6007f53 --- /dev/null +++ b/src/components/Link/__snapshots__/Link-v2.test.tsx.snap @@ -0,0 +1,147 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Default story renders snapshot 1`] = ` + + Link + +`; + +exports[` Emphasis story renders snapshot 1`] = ` + +`; + +exports[` LinkInParagraphContext story renders snapshot 1`] = ` +
+ Lorem ipsum dolor sit amet, + + + consectetur adipiscing elit + + . Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis. Nullam sit amet iaculis erat. Nulla id tellus ante. + + + Aliquam pellentesque ipsum sagittis, commodo neque at, ornare est. Maecenas a malesuada sem, vitae euismod erat. Nullam molestie nunc non dui dignissim fermentum. + + + Aliquam id volutpat nulla, sed auctor orci. Fusce cursus leo nisi. Fusce vehicula vitae nisl nec ultricies. Cras ut enim nec magna semper egestas. Sed sed quam id nisl bibendum convallis. Proin suscipit, odio + + + vel pulvinar + + + euismod, risus eros ullamcorper lectus, non blandit nulla dui eget massa. +
+`; + +exports[` LinkWithChevron story renders snapshot 1`] = ` + + Default + + +`; + +exports[` LinkWithOpenIcon story renders snapshot 1`] = ` + + Default + + +`; + +exports[` UsingExtendedLink story renders snapshot 1`] = ` +
+ Lorem ipsum dolor sit amet, + + + consectetur adipiscing elit + + . Morbi porta at ante quis molestie. Nam scelerisque id diam at iaculis. Nullam sit amet iaculis erat. Nulla id tellus ante. + +
+`; + +exports[` passes class names down properly 1`] = ` + + Click + +`; + +exports[` passes test ids down properly 1`] = ` + + Click + +`; diff --git a/src/components/Menu/Menu-v2.test.tsx b/src/components/Menu/Menu-v2.test.tsx new file mode 100644 index 000000000..619adf04f --- /dev/null +++ b/src/components/Menu/Menu-v2.test.tsx @@ -0,0 +1,96 @@ +import { generateSnapshots } from '@chanzuckerberg/story-utils'; +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 { Menu } from './Menu-v2'; +import * as stories from './Menu-v2.stories'; + +const { Default } = composeStories(stories); + +// Remove the tests with `play` in, b/c it causes the test runner to be unhappy +const { Opened, ...staticStories } = stories; + +describe('', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + generateSnapshots(staticStories, { + getElement: async () => { + const user = userEvent.setup(); + const triggerButton = await screen.findByRole('button'); + await user.click(triggerButton); + return triggerButton.parentElement; // eslint-disable-line testing-library/no-node-access + }, + }); + + it('should allow for keyboard navigation to enabled menu items', async () => { + const user = userEvent.setup(); + render(); + const triggerButton = await screen.findByRole('button'); + await user.click(triggerButton); + const menuContainer = await screen.findByRole('menu'); + + // a11y requires attaching the control dynamically, so confirm + expect(triggerButton.getAttribute('aria-controls')).toEqual( + menuContainer.getAttribute('id'), + ); + + expect(menuContainer.getAttribute('aria-activedescendant')).toBeNull(); + await user.keyboard('{arrowdown}'); + + expect(menuContainer.getAttribute('aria-activedescendant')).not.toBeNull(); + }); + + it('handles onclick events when there is an href present', async () => { + // create a spy on the `log` method, and avoid calling it by setting the mock implementation to nothing + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + const user = userEvent.setup(); + render(); + const triggerButton = await screen.findByRole('button'); + + await user.click(triggerButton); + await user.keyboard('{arrowdown}{arrowdown}{arrowdown}'); + await user.keyboard('{enter}'); + + expect(consoleSpy).toHaveBeenCalledTimes(1); + }); + + it('should close menu on keyboard escape key', async () => { + const user = userEvent.setup(); + render(); + const triggerButton = await screen.findByRole('button'); + await user.click(triggerButton); + const menuContainer = await screen.findByRole('menu'); + + // a11y attaches the control dynamically, so confirm + expect(triggerButton.getAttribute('aria-controls')).toEqual( + menuContainer.getAttribute('id'), + ); + + await user.keyboard('{Escape}'); + expect(screen.queryByTestId('menu-content')).not.toBeInTheDocument(); + }); + + it('should allow render prop usage', async () => { + const user = userEvent.setup(); + render( + + {({ open }) => ( + <> + {open ? 'open' : 'closed'} + + Menu item 1 + Menu item 2 + + + )} + , + ); + const triggerButton = await screen.findByRole('button'); + expect(triggerButton).toHaveAccessibleName('closed'); + await user.click(triggerButton); + expect(triggerButton).toHaveAccessibleName('open'); + }); +}); diff --git a/src/components/Menu/__snapshots__/Menu-v2.test.tsx.snap b/src/components/Menu/__snapshots__/Menu-v2.test.tsx.snap new file mode 100644 index 000000000..e55afe138 --- /dev/null +++ b/src/components/Menu/__snapshots__/Menu-v2.test.tsx.snap @@ -0,0 +1,183 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Default story renders snapshot 1`] = ` + +`; + +exports[` MenuWithAvatarButton story renders snapshot 1`] = ` + +`; + +exports[` MenuWithIconButton story renders snapshot 1`] = ` + +`; + +exports[` WithCustomButton story renders snapshot 1`] = ` + +`; + +exports[` WithLongButtonText story renders snapshot 1`] = ` + +`; + +exports[` WithShortButtonText story renders snapshot 1`] = ` + +`; diff --git a/src/components/Modal/Modal-v2.test.tsx b/src/components/Modal/Modal-v2.test.tsx new file mode 100644 index 000000000..9af8960ad --- /dev/null +++ b/src/components/Modal/Modal-v2.test.tsx @@ -0,0 +1,134 @@ +import { generateSnapshots, wait } from '@chanzuckerberg/story-utils'; +import type { StoryFile } from '@storybook/testing-react'; +import { composeStories } from '@storybook/testing-react'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { Modal } from './Modal-v2'; +import * as stories from './Modal-v2.stories'; +import '../../../jest/helpers/removeModalTransitionStylesJestSerializer'; + +const { Default } = composeStories(stories); + +window.ResizeObserver = class FakeResizeObserver { + observe() {} + unobserve() {} + disconnect() {} +}; + +describe('Modal', () => { + generateSnapshots(stories as StoryFile, { + getElement: async () => { + const user = userEvent.setup(); + const nonInteractiveModal = screen.queryByTestId('non-interactive'); + if (nonInteractiveModal) return nonInteractiveModal; + + const openModalButton = await screen.findByRole('button', { + name: 'Open the modal', + }); + await user.click(openModalButton); + const modal = await screen.findByRole('dialog'); + + // Give Headless UI's transition/style classes time to settle. + await wait(50); + + return modal; + }, + }); + + it('is initially closed', () => { + render(); + expect(screen.queryByRole('dialog')).toBeFalsy(); + }); + + it('shows the modal when the open modal button is clicked', async () => { + const user = userEvent.setup(); + render(); + const openModalButton = await screen.findByRole('button', { + name: 'Open the modal', + }); + await user.click(openModalButton); + const modal = await screen.findByRole('dialog'); + expect(modal).toBeTruthy(); + }); + + it('closes the modal on close button click', async () => { + const user = userEvent.setup(); + render(); + const openModalButton = await screen.findByRole('button', { + name: 'Open the modal', + }); + await user.click(openModalButton); + const closeButton = await screen.findByRole('button', { + name: 'close modal', + }); + await user.click(closeButton); + await waitFor(() => { + expect(screen.queryByRole('dialog')).toBeFalsy(); + }); + }); + + it('closes the modal on ESC key press', async () => { + const user = userEvent.setup(); + render(); + const openModalButton = await screen.findByRole('button', { + name: 'Open the modal', + }); + await user.click(openModalButton); + await user.keyboard('{Escape}'); + await waitFor(() => { + expect(screen.queryByRole('dialog')).toBeFalsy(); + }); + }); + + it('does not throw an error if modal uses ', () => { + const modalWithTitle = ( + {}} open> + + Modal Title + + Modal body content. + Modal footer content. + + ); + const renderMethod = () => { + render(modalWithTitle); + }; + + expect(renderMethod).not.toThrow(Error); + }); + + it('does not throw an error if modal uses aria-label', () => { + const modalWithAriaLabel = ( + {}} open> + Modal Title + Modal body content. + Modal footer content. + + ); + const renderMethod = () => { + render(modalWithAriaLabel); + }; + + expect(renderMethod).not.toThrow(Error); + }); + + it('does throw an error if modal does not use or aria-label', () => { + const modalWithoutTitleOrAriaLabel = ( + {}} open> + Modal Title + Modal body content. + Modal footer content. + + ); + const renderMethod = () => { + render(modalWithoutTitleOrAriaLabel); + }; + + // expect console error from react, suppressed. + const consoleErrorMock = jest.spyOn(console, 'error'); + consoleErrorMock.mockImplementation(); + expect(renderMethod).toThrow(Error); + consoleErrorMock.mockRestore(); + }); +}); diff --git a/src/components/Modal/__snapshots__/Modal-v2.test.tsx.snap b/src/components/Modal/__snapshots__/Modal-v2.test.tsx.snap new file mode 100644 index 000000000..f02068678 --- /dev/null +++ b/src/components/Modal/__snapshots__/Modal-v2.test.tsx.snap @@ -0,0 +1,958 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Modal ContentDefault story renders snapshot 1`] = ` + +`; + +exports[`Modal Default story renders snapshot 1`] = ` +