From a9db3355829c38b7e120304ea6e529717cc04ac5 Mon Sep 17 00:00:00 2001 From: Marco Ciampini Date: Tue, 21 Feb 2023 19:16:47 +0100 Subject: [PATCH] `ToolsPanel`: refactor unit tests to TypeScript (#48275) * rename file * Move control value from props object to separate variable * Fix context type * Remove disabled option in the query, replace with aria-disabled attribute check * Reuse ToolsPanelItem component props type * Avoid reusing the same variable to hold two different query results * Type component * Add missing required props to ToolsPanelItem * Add types to internal `ToolsPanelOptional` component * Prefer React global for types * CHANGELOG * Extract controlvalue props --- packages/components/CHANGELOG.md | 1 + .../tools-panel/test/{index.js => index.tsx} | 162 +++++++++++------- 2 files changed, 102 insertions(+), 61 deletions(-) rename packages/components/src/tools-panel/test/{index.js => index.tsx} (92%) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 9b2129a98ba45..a180b283a78ff 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -34,6 +34,7 @@ - `ToolsPanel`: Ensure display of optional items when values are updated externally and multiple blocks selected ([47864](https://github.com/WordPress/gutenberg/pull/47864)). - `Navigator`: add more pattern matching tests, refine existing tests ([47910](https://github.com/WordPress/gutenberg/pull/47910)). - `ToolsPanel`: Refactor Storybook examples to TypeScript ([47944](https://github.com/WordPress/gutenberg/pull/47944)). +- `ToolsPanel`: Refactor unit tests to TypeScript ([48275](https://github.com/WordPress/gutenberg/pull/48275)). ## 23.3.0 (2023-02-01) diff --git a/packages/components/src/tools-panel/test/index.js b/packages/components/src/tools-panel/test/index.tsx similarity index 92% rename from packages/components/src/tools-panel/test/index.js rename to packages/components/src/tools-panel/test/index.tsx index 3c955a8addb2b..bc91691f9c7d8 100644 --- a/packages/components/src/tools-panel/test/index.js +++ b/packages/components/src/tools-panel/test/index.tsx @@ -9,11 +9,14 @@ import userEvent from '@testing-library/user-event'; */ import { ToolsPanel, ToolsPanelContext, ToolsPanelItem } from '../'; import { createSlotFill, Provider as SlotFillProvider } from '../../slot-fill'; +import type { ToolsPanelContext as ToolsPanelContextType } from '../types'; const { Fill: ToolsPanelItems, Slot } = createSlotFill( 'ToolsPanelSlot' ); const resetAll = jest.fn(); const noop = () => undefined; +type ControlValue = boolean | undefined; + // Default props for the tools panel. const defaultProps = { label: 'Panel header', @@ -21,24 +24,24 @@ const defaultProps = { }; // Default props for an enabled control to be rendered within panel. +let controlValue: ControlValue = true; const controlProps = { - attributes: { value: true }, hasValue: jest.fn().mockImplementation( () => { - return !! controlProps.attributes.value; + return !! controlValue; } ), label: 'Example', onDeselect: jest.fn().mockImplementation( () => { - controlProps.attributes.value = undefined; + controlValue = undefined; } ), onSelect: jest.fn(), }; // Default props without a value for an alternate control to be rendered within // the panel. +let altControlValue: ControlValue = false; const altControlProps = { - attributes: { value: false }, hasValue: jest.fn().mockImplementation( () => { - return !! altControlProps.attributes.value; + return !! altControlValue; } ), label: 'Alt', onDeselect: jest.fn(), @@ -46,24 +49,24 @@ const altControlProps = { }; // Default props for wrapped or grouped panel items. +let nestedControlValue: ControlValue = true; const nestedControlProps = { - attributes: { value: true }, hasValue: jest.fn().mockImplementation( () => { - return !! nestedControlProps.attributes.value; + return !! nestedControlValue; } ), label: 'Nested Control 1', onDeselect: jest.fn().mockImplementation( () => { - nestedControlProps.attributes.value = undefined; + nestedControlValue = undefined; } ), onSelect: jest.fn(), isShownByDefault: true, }; // Alternative props for wrapped or grouped panel items. +const altNestedControlValue: ControlValue = false; const altNestedControlProps = { - attributes: { value: false }, hasValue: jest.fn().mockImplementation( () => { - return !! altNestedControlProps.attributes.value; + return !! altNestedControlValue; } ), label: 'Nested Control 2', onDeselect: jest.fn(), @@ -90,7 +93,7 @@ const GroupedItems = ( { // This context object is used to help simulate different scenarios in which // `ToolsPanelItem` registration or deregistration requires testing. -const panelContext = { +const panelContext: ToolsPanelContextType = { panelId: '1234', menuItems: { default: {}, @@ -117,7 +120,10 @@ const renderGroupedItemsInPanel = () => { // Custom component rendering a panel item within a wrapping element. Also used // to test panel item registration and rendering. -const WrappedItem = ( { text, ...props } ) => { +const WrappedItem = ( { + text, + ...props +}: React.ComponentProps< typeof ToolsPanelItem > & { text: string } ) => { return (
Wrapper @@ -178,7 +184,7 @@ const openDropdownMenu = async () => { }; // Opens dropdown then selects the menu item by label before simulating a click. -const selectMenuItem = async ( label ) => { +const selectMenuItem = async ( label: string ) => { const user = userEvent.setup(); const menuItem = await screen.findByText( label ); await user.click( menuItem ); @@ -186,8 +192,8 @@ const selectMenuItem = async ( label ) => { describe( 'ToolsPanel', () => { afterEach( () => { - controlProps.attributes.value = true; - altControlProps.attributes.value = false; + controlValue = true; + altControlValue = false; } ); describe( 'basic rendering', () => { @@ -229,10 +235,20 @@ describe( 'ToolsPanel', () => { render( { false && ( - Should not show + false } + > + Should not show + ) } { false && ( - Not shown either + false } + > + Not shown either + ) } Visible but insignificant @@ -317,7 +333,11 @@ describe( 'ToolsPanel', () => { } ); it( 'should render optional panel item when value is updated externally and panel has an ID', async () => { - const ToolsPanelOptional = ( { toolsPanelItemValue } ) => { + const ToolsPanelOptional = ( { + toolsPanelItemValue, + }: { + toolsPanelItemValue?: number; + } ) => { const itemProps = { attributes: { value: toolsPanelItemValue }, hasValue: () => !! toolsPanelItemValue, @@ -349,7 +369,11 @@ describe( 'ToolsPanel', () => { it( 'should render optional item when value is updated externally and panelId is null', async () => { // This test partially covers: https://github.com/WordPress/gutenberg/issues/47368 - const ToolsPanelOptional = ( { toolsPanelItemValue } ) => { + const ToolsPanelOptional = ( { + toolsPanelItemValue, + }: { + toolsPanelItemValue?: number; + } ) => { const itemProps = { attributes: { value: toolsPanelItemValue }, hasValue: () => !! toolsPanelItemValue, @@ -452,10 +476,10 @@ describe( 'ToolsPanel', () => { } ); it( 'should render default controls with conditional isShownByDefault', async () => { + const linkedControlValue = false; const linkedControlProps = { - attributes: { value: false }, hasValue: jest.fn().mockImplementation( () => { - return !! linkedControlProps.attributes.value; + return !! linkedControlValue; } ), label: 'Linked', onDeselect: jest.fn(), @@ -472,7 +496,7 @@ describe( 'ToolsPanel', () => {
Linked control
@@ -495,13 +519,14 @@ describe( 'ToolsPanel', () => { expect( menuGroups.length ).toEqual( 3 ); // The linked control should be in the second group, of optional controls. - let optionalItem = within( menuGroups[ 1 ] ).getByText( 'Linked' ); - expect( optionalItem ).toBeInTheDocument(); + expect( + within( menuGroups[ 1 ] ).getByText( 'Linked' ) + ).toBeInTheDocument(); // Simulate the main control having a value set which should // trigger the linked control becoming a default control via the // conditional `isShownByDefault` prop. - altControlProps.attributes.value = true; + altControlValue = true; rerender( ); @@ -526,17 +551,18 @@ describe( 'ToolsPanel', () => { // Optional controls have an additional aria-label. This can be used // to confirm the conditional default control has been removed from // the optional menu item group. - optionalItem = screen.queryByRole( 'menuitemcheckbox', { - name: 'Show Linked', - } ); - expect( optionalItem ).not.toBeInTheDocument(); + expect( + screen.queryByRole( 'menuitemcheckbox', { + name: 'Show Linked', + } ) + ).not.toBeInTheDocument(); } ); it( 'should handle conditionally rendered default control', async () => { + const conditionalControlValue = false; const conditionalControlProps = { - attributes: { value: false }, hasValue: jest.fn().mockImplementation( () => { - return !! conditionalControlProps.attributes.value; + return !! conditionalControlValue; } ), label: 'Conditional', onDeselect: jest.fn(), @@ -551,7 +577,7 @@ describe( 'ToolsPanel', () => { >
Default control
- { !! altControlProps.attributes.value && ( + { !! altControlValue && ( { // Simulate the main control having a value set which will now // render the new default control into the ToolsPanel. - altControlProps.attributes.value = true; + altControlValue = true; rerender( ); @@ -614,7 +640,7 @@ describe( 'ToolsPanel', () => { // themselves, while those for the old panelId deregister. // // See: https://github.com/WordPress/gutenberg/pull/36588 - const context = { ...panelContext }; + const context: ToolsPanelContextType = { ...panelContext }; const TestPanel = () => ( @@ -678,7 +704,10 @@ describe( 'ToolsPanel', () => { // individual items should still render themselves in this case. // // See: https://github.com/WordPress/gutenberg/pull/37216 - const context = { ...panelContext, panelId: null }; + const context: ToolsPanelContextType = { + ...panelContext, + panelId: null, + }; const TestPanel = () => ( @@ -981,7 +1010,12 @@ describe( 'ToolsPanel', () => { // test that no orphaned items appear registered in the panel menu. // // See: https://github.com/WordPress/gutenberg/pull/34085 - const TestSlotFillPanel = ( { panelId } ) => ( + const TestSlotFillPanel = ( { + panelId, + }: Pick< + React.ComponentProps< typeof ToolsPanelItem >, + 'panelId' + > ) => ( @@ -1004,37 +1038,38 @@ describe( 'ToolsPanel', () => { // Only the item matching the panelId should have been registered // and appear in the panel menu. - let altMenuItem = screen.getByRole( 'menuitemcheckbox', { - name: 'Show Alt', - } ); - let exampleMenuItem = screen.queryByRole( 'menuitemcheckbox', { - name: 'Hide and reset Example', - } ); - - expect( altMenuItem ).toBeInTheDocument(); - expect( exampleMenuItem ).not.toBeInTheDocument(); + expect( + screen.getByRole( 'menuitemcheckbox', { + name: 'Show Alt', + } ) + ).toBeInTheDocument(); + expect( + screen.queryByRole( 'menuitemcheckbox', { + name: 'Hide and reset Example', + } ) + ).not.toBeInTheDocument(); // Re-render the panel with different panelID simulating a block // selection change. rerender( ); - - altMenuItem = screen.queryByRole( 'menuitemcheckbox', { - name: 'Show Alt', - } ); - exampleMenuItem = screen.getByRole( 'menuitemcheckbox', { - name: 'Hide and reset Example', - } ); - - expect( altMenuItem ).not.toBeInTheDocument(); - expect( exampleMenuItem ).toBeInTheDocument(); + expect( + screen.queryByRole( 'menuitemcheckbox', { + name: 'Show Alt', + } ) + ).not.toBeInTheDocument(); + expect( + screen.getByRole( 'menuitemcheckbox', { + name: 'Hide and reset Example', + } ) + ).toBeInTheDocument(); } ); } ); describe( 'panel header icon toggle', () => { + const defaultControlsValue = false; const defaultControls = { - attributes: { value: false }, hasValue: jest.fn().mockImplementation( () => { - return !! defaultControls.attributes.value; + return !! defaultControlsValue; } ), label: 'Default', onDeselect: jest.fn(), @@ -1042,10 +1077,10 @@ describe( 'ToolsPanel', () => { isShownByDefault: true, }; + const optionalControlsValue = false; const optionalControls = { - attributes: { value: false }, hasValue: jest.fn().mockImplementation( () => { - return !! optionalControls.attributes.value; + return !! optionalControlsValue; } ), label: 'Optional', onDeselect: jest.fn(), @@ -1115,9 +1150,10 @@ describe( 'ToolsPanel', () => { await openDropdownMenu(); const resetAllItem = await screen.findByRole( 'menuitem', { - disabled: false, + name: 'Reset all', } ); expect( resetAllItem ).toBeInTheDocument(); + expect( resetAllItem ).toHaveAttribute( 'aria-disabled', 'false' ); await selectMenuItem( 'Reset all' ); @@ -1126,9 +1162,13 @@ describe( 'ToolsPanel', () => { expect( announcement ).toHaveAttribute( 'aria-live', 'assertive' ); const disabledResetAllItem = await screen.findByRole( 'menuitem', { - disabled: true, + name: 'Reset all', } ); expect( disabledResetAllItem ).toBeInTheDocument(); + expect( disabledResetAllItem ).toHaveAttribute( + 'aria-disabled', + 'true' + ); } ); } );