From a8c8b149ff80b164223ce3719728be947b95a76c Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Wed, 15 Jun 2022 10:13:24 +0200 Subject: [PATCH] `Navigation`: add controlled unit test, use modern Testing Library features (#41668) * Rewrite tests using user-event and modern testing library asseertions, remove unnecessary async * Add controlled test * CHANGELOG * Fix typo * Check for `aria-current` attribute instead of `is-active` classname --- packages/components/CHANGELOG.md | 4 + .../navigation/stories/controlled-state.js | 2 +- .../components/src/navigation/test/index.js | 304 +++++++++++++++--- 3 files changed, 257 insertions(+), 53 deletions(-) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 0ddf7c5e512112..2ed2499fc5d2c5 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -28,6 +28,10 @@ - `ExternalLink`: Convert to TypeScript ([#41681](https://github.com/WordPress/gutenberg/pull/41681)). - `InputControl` updated to satisfy `react/exhuastive-deps` eslint rule ([#41601](https://github.com/WordPress/gutenberg/pull/41601)) +### Experimental + +- `Navigation`: improve unit tests by using `@testing-library/user-event` and modern `@testing-library` assertions; add unit test for controlled component ([#41668](https://github.com/WordPress/gutenberg/pull/41668)). + ## 19.12.0 (2022-06-01) ### Bug Fix diff --git a/packages/components/src/navigation/stories/controlled-state.js b/packages/components/src/navigation/stories/controlled-state.js index 23f515df8c6036..68c1be54f1bffb 100644 --- a/packages/components/src/navigation/stories/controlled-state.js +++ b/packages/components/src/navigation/stories/controlled-state.js @@ -19,7 +19,7 @@ export function ControlledStateStory() { const MockLink = ( { href, children } ) => ( +

+

+ +

+ + + ); +}; + describe( 'Navigation', () => { it( 'should render the panes and active item', async () => { - render( testNavigation( { activeItem: 'item-2' } ) ); + render( ); const menuItems = screen.getAllByRole( 'listitem' ); - expect( menuItems.length ).toBe( 4 ); - expect( menuItems[ 0 ].textContent ).toBe( 'Item 1' ); - expect( menuItems[ 1 ].textContent ).toBe( 'Item 2' ); - expect( menuItems[ 2 ].textContent ).toBe( 'Category' ); - expect( menuItems[ 3 ].textContent ).toBe( 'customize me' ); - expect( menuItems[ 0 ].classList.contains( 'is-active' ) ).toBe( - false - ); - expect( menuItems[ 1 ].classList.contains( 'is-active' ) ).toBe( true ); + expect( menuItems ).toHaveLength( 4 ); + expect( menuItems[ 0 ] ).toHaveTextContent( 'Item 1' ); + expect( menuItems[ 1 ] ).toHaveTextContent( 'Item 2' ); + expect( menuItems[ 2 ] ).toHaveTextContent( 'Category' ); + expect( menuItems[ 3 ] ).toHaveTextContent( 'customize me' ); + + expect( + screen.getByRole( 'link', { current: 'page' } ) + ).toHaveTextContent( 'Item 2' ); } ); - it( 'should render anchor links when menu item supplies an href', async () => { - render( testNavigation() ); + it( 'should render anchor links when menu item supplies an href', () => { + render( ); const linkItem = screen.getByRole( 'link', { name: 'Item 2' } ); - expect( linkItem ).toBeDefined(); - expect( linkItem.target ).toBe( '_blank' ); + expect( linkItem ).toBeInTheDocument(); + expect( linkItem ).toHaveAttribute( 'target', '_blank' ); } ); - it( 'should render a custom component when menu item supplies one', async () => { - render( testNavigation() ); + it( 'should render a custom component when menu item supplies one', () => { + render( ); - const customItem = screen.getByText( 'customize me' ); - - expect( customItem ).toBeDefined(); + expect( screen.getByText( 'customize me' ) ).toBeInTheDocument(); } ); it( 'should set an active category on click', async () => { - render( testNavigation() ); + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); - fireEvent.click( screen.getByRole( 'button', { name: 'Category' } ) ); - const categoryTitle = screen.getByRole( 'heading' ); - const menuItems = screen.getAllByRole( 'listitem' ); + render( ); + + await user.click( screen.getByRole( 'button', { name: 'Category' } ) ); - expect( categoryTitle.textContent ).toBe( 'Category' ); - expect( menuItems.length ).toBe( 2 ); - expect( menuItems[ 0 ].textContent ).toBe( 'Child 1' ); - expect( menuItems[ 1 ].textContent ).toBe( 'Child 2' ); + expect( screen.getByRole( 'heading' ) ).toHaveTextContent( 'Category' ); + const menuItems = screen.getAllByRole( 'listitem' ); + expect( menuItems ).toHaveLength( 2 ); + expect( menuItems[ 0 ] ).toHaveTextContent( 'Child 1' ); + expect( menuItems[ 1 ] ).toHaveTextContent( 'Child 2' ); } ); - it( 'should render the root title', async () => { - const { rerender } = render( testNavigation() ); + it( 'should render the root title', () => { + const { rerender } = render( ); - const emptyTitle = screen.queryByRole( 'heading' ); - expect( emptyTitle ).toBeNull(); + expect( screen.queryByRole( 'heading' ) ).not.toBeInTheDocument(); - rerender( testNavigation( { rootTitle: 'Home' } ) ); + rerender( ); - const rootTitle = screen.getByRole( 'heading' ); - expect( rootTitle.textContent ).toBe( 'Home' ); + expect( screen.getByRole( 'heading' ) ).toBeInTheDocument(); + expect( screen.getByRole( 'heading' ) ).toHaveTextContent( 'Home' ); } ); - it( 'should render badges', async () => { - render( testNavigation( { showBadge: true } ) ); + it( 'should render badges', () => { + render( ); const menuItem = screen.getAllByRole( 'listitem' ); expect( menuItem[ 0 ].textContent ).toBe( 'Item 1' + '21' ); } ); - it( 'should render menu titles when items exist', async () => { + it( 'should render menu titles when items exist', () => { const { rerender } = render( ); - const emptyMenu = screen.queryByText( 'Menu title' ); - expect( emptyMenu ).toBeNull(); + expect( screen.queryByText( 'Menu title' ) ).not.toBeInTheDocument(); - rerender( testNavigation( { rootTitle: 'Menu title' } ) ); + rerender( ); - const menuTitle = screen.queryByText( 'Menu title' ); - expect( menuTitle ).not.toBeNull(); + expect( screen.getByText( 'Menu title' ) ).toBeInTheDocument(); } ); it( 'should navigate up a level when clicking the back button', async () => { - render( testNavigation( { rootTitle: 'Home' } ) ); - - fireEvent.click( screen.getByRole( 'button', { name: 'Category' } ) ); - let menuTitle = screen.getByRole( 'heading' ); - expect( menuTitle.textContent ).toBe( 'Category' ); - fireEvent.click( screen.getByRole( 'button', { name: 'Home' } ) ); - menuTitle = screen.getByRole( 'heading' ); - expect( menuTitle.textContent ).toBe( 'Home' ); + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + await user.click( screen.getByRole( 'button', { name: 'Category' } ) ); + + expect( screen.getByRole( 'heading' ) ).toHaveTextContent( 'Category' ); + + await user.click( screen.getByRole( 'button', { name: 'Home' } ) ); + + expect( screen.getByRole( 'heading' ) ).toHaveTextContent( 'Home' ); + } ); + + it( 'should navigate correctly when controlled', async () => { + const user = userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + + render( ); + + // check root menu is shown and item 1 is selected + expect( + screen.getByRole( 'heading', { name: 'Home' } ) + ).toBeInTheDocument(); + expect( + screen.getByRole( 'link', { current: 'page' } ) + ).toHaveTextContent( 'Item 1' ); + + // click Item 2, check it's selected + await user.click( screen.getByRole( 'link', { name: 'Item 2' } ) ); + expect( + screen.getByRole( 'link', { current: 'page' } ) + ).toHaveTextContent( 'Item 2' ); + + // click sub-menu, check new menu is shown + await user.click( screen.getByRole( 'button', { name: 'Sub-Menu' } ) ); + expect( + screen.getByRole( 'heading', { name: 'Sub-Menu' } ) + ).toBeInTheDocument(); + + // click Child 1, check it's selected + await user.click( screen.getByRole( 'button', { name: 'Child 1' } ) ); + expect( + screen.getByRole( 'button', { current: 'page' } ) + ).toHaveTextContent( 'Child 1' ); + + // click nested sub-menu, check nested sub-menu is shown + await user.click( + screen.getByRole( 'button', { name: 'Nested Sub-Menu' } ) + ); + expect( + screen.getByRole( 'heading', { name: 'Nested Sub-Menu' } ) + ).toBeInTheDocument(); + + // click Sub Child 2, check it's selected + await user.click( + screen.getByRole( 'button', { name: 'Sub-Child 2' } ) + ); + expect( + screen.getByRole( 'button', { current: 'page' } ) + ).toHaveTextContent( 'Sub-Child 2' ); + + // click back, check sub-menu is shown + await user.click( screen.getByRole( 'button', { name: 'Sub-Menu' } ) ); + expect( + screen.getByRole( 'heading', { name: 'Sub-Menu' } ) + ).toBeInTheDocument(); + + // click back, check root menu is shown + await user.click( screen.getByRole( 'button', { name: 'Home' } ) ); + expect( + screen.getByRole( 'heading', { name: 'Home' } ) + ).toBeInTheDocument(); + + // click the programmatic nested sub-menu button, check nested sub menu is shown + await user.click( + screen.getByRole( 'button', { + name: 'Open the Nested Sub-Menu menu', + } ) + ); + expect( + screen.getByRole( 'heading', { name: 'Nested Sub-Menu' } ) + ).toBeInTheDocument(); + + // click navigate to child2 item button, check the correct menu is shown and the item is selected + await user.click( + screen.getByRole( 'button', { + name: 'Navigate to Child 2 item', + } ) + ); + expect( + screen.getByRole( 'heading', { name: 'Sub-Menu' } ) + ).toBeInTheDocument(); + expect( + screen.getByRole( 'button', { current: 'page' } ) + ).toHaveTextContent( 'Child 2' ); } ); } );