From acd41a2c0a2deb1d6a6b78403fb2bc5f6527dd55 Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Mon, 8 Apr 2024 14:09:04 +0200 Subject: [PATCH] [core] Use `describeTreeView` for icons tests (#12672) Signed-off-by: Flavien DELANGLE Co-authored-by: Nora <72460825+noraleonte@users.noreply.github.com> --- .../src/RichTreeView/RichTreeView.types.ts | 10 +- .../src/TreeItem/TreeItem.test.tsx | 40 ---- .../x-tree-view/src/TreeItem2/TreeItem2.tsx | 6 +- .../useTreeViewExpansion.test.tsx | 2 +- .../useTreeViewIcons.test.tsx | 197 ++++++++++++++++++ .../useTreeViewSelection.test.tsx | 2 +- .../describeTreeView/describeTreeView.tsx | 48 +++-- .../describeTreeView.types.ts | 32 +-- .../utils/tree-view/describeTreeView/index.ts | 1 + 9 files changed, 257 insertions(+), 81 deletions(-) create mode 100644 packages/x-tree-view/src/internals/plugins/useTreeViewIcons/useTreeViewIcons.test.tsx diff --git a/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts b/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts index 2687be9a9d65..a2ffd144c2f8 100644 --- a/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts +++ b/packages/x-tree-view/src/RichTreeView/RichTreeView.types.ts @@ -9,10 +9,10 @@ import { DefaultTreeViewPluginSlots, DefaultTreeViewPlugins, } from '../internals/plugins/defaultPlugins'; -import { TreeItem, TreeItemProps } from '../TreeItem'; +import { TreeItemProps } from '../TreeItem'; import { TreeItem2Props } from '../TreeItem2'; import { TreeViewItemId } from '../models'; -import { TreeViewPublicAPI } from '../internals/models'; +import { SlotComponentPropsFromProps, TreeViewPublicAPI } from '../internals/models'; interface RichTreeViewItemSlotOwnerState { itemId: TreeViewItemId; @@ -35,7 +35,11 @@ export interface RichTreeViewSlots extends DefaultTreeViewPluginSlots { export interface RichTreeViewSlotProps extends DefaultTreeViewPluginSlotProps { root?: SlotComponentProps<'ul', {}, RichTreeViewProps>; - item?: SlotComponentProps; + item?: SlotComponentPropsFromProps< + TreeItemProps | TreeItem2Props, + {}, + RichTreeViewItemSlotOwnerState + >; } export type RichTreeViewApiRef = React.MutableRefObject< diff --git a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx index 89697ecd54f0..3ca830125010 100644 --- a/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx +++ b/packages/x-tree-view/src/TreeItem/TreeItem.test.tsx @@ -106,46 +106,6 @@ describe('', () => { expect(handleClick.callCount).to.equal(1); }); - it('should display the right icons', () => { - const { getByTestId } = render( -
, - collapseIcon: () =>
, - endIcon: () =>
, - }} - defaultExpandedItems={['1']} - > - - -
}} - /> -
}} - /> - - - - - , - ); - - const getIcon = (testId) => getByTestId(testId).querySelector(`.${classes.iconContainer} div`); - - expect(getIcon('1')).attribute('data-test').to.equal('defaultCollapseIcon'); - expect(getIcon('2')).attribute('data-test').to.equal('defaultEndIcon'); - expect(getIcon('3')).attribute('data-test').to.equal('defaultExpandIcon'); - expect(getIcon('5')).attribute('data-test').to.equal('icon'); - expect(getIcon('6')).attribute('data-test').to.equal('endIcon'); - }); - it('should allow conditional child', () => { function TestComponent() { const [hide, setState] = React.useState(false); diff --git a/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx b/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx index c0959fe8c69c..c48addd33328 100644 --- a/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx +++ b/packages/x-tree-view/src/TreeItem2/TreeItem2.tsx @@ -157,6 +157,10 @@ const useUtilityClasses = (ownerState: TreeItem2OwnerState) => { return composeClasses(slots, getTreeItemUtilityClass, classes); }; +type TreeItem2Component = (( + props: TreeItem2Props & React.RefAttributes, +) => React.JSX.Element) & { propTypes?: any }; + /** * * Demos: @@ -264,7 +268,7 @@ export const TreeItem2 = React.forwardRef(function TreeItem2( ); -}); +}) as TreeItem2Component; TreeItem2.propTypes = { // ----------------------------- Warning -------------------------------- diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx index 19828a91f5a4..f0b18b58c1f5 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewExpansion/useTreeViewExpansion.test.tsx @@ -12,7 +12,7 @@ import { useTreeItem2Utils } from '@mui/x-tree-view/hooks'; * All tests related to keyboard navigation (e.g.: expanding using "Enter" and "ArrowRight") * are located in the `useTreeViewKeyboardNavigation.test.tsx` file. */ -describeTreeView( +describeTreeView<[UseTreeViewExpansionSignature]>( 'useTreeViewExpansion plugin', ({ render, setup }) => { describe('model props (expandedItems, defaultExpandedItems, onExpandedItemsChange)', () => { diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewIcons/useTreeViewIcons.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewIcons/useTreeViewIcons.test.tsx new file mode 100644 index 000000000000..a866127ab3cd --- /dev/null +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewIcons/useTreeViewIcons.test.tsx @@ -0,0 +1,197 @@ +import * as React from 'react'; +import { expect } from 'chai'; +import { + describeTreeView, + DescribeTreeViewRendererReturnValue, +} from 'test/utils/tree-view/describeTreeView'; +import { + UseTreeViewExpansionSignature, + UseTreeViewIconsSignature, +} from '@mui/x-tree-view/internals'; + +describeTreeView<[UseTreeViewIconsSignature, UseTreeViewExpansionSignature]>( + 'useTreeViewIcons plugin', + ({ render }) => { + describe('slots (expandIcon, collapseIcon, endIcon, icon)', () => { + const getIconTestId = (response: DescribeTreeViewRendererReturnValue<[]>, itemId: string) => + response.getItemIconContainer(itemId).querySelector(`div`)?.dataset.testid; + + it('should render the expandIcon slot defined on the tree if no icon slot is defined on the item and the item is collapsed', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + }); + + expect(getIconTestId(response, '1')).to.equal('treeExpandIcon'); + }); + + it('should render the collapseIcon slot defined on the tree if no icon is defined on the item and the item is expanded', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + defaultExpandedItems: ['1'], + }); + + expect(getIconTestId(response, '1')).to.equal('treeCollapseIcon'); + }); + + it('should render the endIcon slot defined on the tree if no icon is defined on the item and the item has no children', () => { + const response = render({ + items: [{ id: '1' }], + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + }); + + expect(getIconTestId(response, '1')).to.equal('treeEndIcon'); + }); + + it('should render the expandIcon slot defined on the item if the item is collapsed', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + slotProps: { + item: { + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + }, + }, + }); + + expect(getIconTestId(response, '1')).to.equal('itemExpandIcon'); + }); + + it('should render the collapseIcon slot defined on the item if the item is expanded', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + slotProps: { + item: { + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + }, + }, + defaultExpandedItems: ['1'], + }); + + expect(getIconTestId(response, '1')).to.equal('itemCollapseIcon'); + }); + + it('should render the endIcon slot defined on the tree if the item has no children', () => { + const response = render({ + items: [{ id: '1' }], + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + slotProps: { + item: { + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + }, + }, + }); + + expect(getIconTestId(response, '1')).to.equal('itemEndIcon'); + }); + + it('should render the icon slot defined on the item if the item is collapsed', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + slotProps: { + item: { + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + icon: () =>
, + }, + }, + }, + }); + + expect(getIconTestId(response, '1')).to.equal('itemIcon'); + }); + + it('should render the icon slot defined on the item if the item is expanded', () => { + const response = render({ + items: [{ id: '1', children: [{ id: '1.1' }] }], + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + slotProps: { + item: { + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + icon: () =>
, + }, + }, + }, + defaultExpandedItems: ['1'], + }); + + expect(getIconTestId(response, '1')).to.equal('itemIcon'); + }); + + it('should render the icon slot defined on the item if the item has no children', () => { + const response = render({ + items: [{ id: '1' }], + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + }, + slotProps: { + item: { + slots: { + expandIcon: () =>
, + collapseIcon: () =>
, + endIcon: () =>
, + icon: () =>
, + }, + }, + }, + }); + + expect(getIconTestId(response, '1')).to.equal('itemIcon'); + }); + }); + }, +); diff --git a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx index 249ac91c9c63..c6d5e885ec2c 100644 --- a/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx +++ b/packages/x-tree-view/src/internals/plugins/useTreeViewSelection/useTreeViewSelection.test.tsx @@ -8,7 +8,7 @@ import { UseTreeViewSelectionSignature } from '@mui/x-tree-view/internals'; * All tests related to keyboard navigation (e.g.: selection using "Space") * are located in the `useTreeViewKeyboardNavigation.test.tsx` file. */ -describeTreeView('useTreeViewSelection plugin', ({ render }) => { +describeTreeView<[UseTreeViewSelectionSignature]>('useTreeViewSelection plugin', ({ render }) => { describe('model props (selectedItems, defaultSelectedItems, onSelectedItemsChange)', () => { it('should not select items when no default state and no control state are defined', () => { const response = render({ diff --git a/test/utils/tree-view/describeTreeView/describeTreeView.tsx b/test/utils/tree-view/describeTreeView/describeTreeView.tsx index 4a27c1d03647..5dc84c400887 100644 --- a/test/utils/tree-view/describeTreeView/describeTreeView.tsx +++ b/test/utils/tree-view/describeTreeView/describeTreeView.tsx @@ -15,15 +15,15 @@ import { DescribeTreeViewItem, } from './describeTreeView.types'; -const innerDescribeTreeView = ( +const innerDescribeTreeView = ( message: string, - testRunner: DescribeTreeViewTestRunner, + testRunner: DescribeTreeViewTestRunner, ): void => { const { render } = createRenderer(); const getUtils = ( result: MuiRenderResult, - ): Omit, 'setProps' | 'apiRef'> => { + ): Omit, 'setProps' | 'apiRef'> => { const getRoot = () => result.getByRole('tree'); const getAllItemRoots = () => result.queryAllByRole('treeitem'); @@ -57,7 +57,7 @@ const innerDescribeTreeView = ( describe(message, () => { describe('RichTreeView + TreeItem', () => { - const renderRichTreeView: DescribeTreeViewRenderer = ({ + const renderRichTreeView: DescribeTreeViewRenderer = ({ items: rawItems, slotProps, ...other @@ -84,7 +84,7 @@ const innerDescribeTreeView = ( return { setProps: result.setProps, - apiRef: apiRef as { current: TreeViewPublicAPI<[TPlugin]> }, + apiRef: apiRef as unknown as { current: TreeViewPublicAPI }, ...getUtils(result), }; }; @@ -93,7 +93,7 @@ const innerDescribeTreeView = ( }); describe('RichTreeView + TreeItem2', () => { - const renderRichTreeView: DescribeTreeViewRenderer = ({ + const renderRichTreeView: DescribeTreeViewRenderer = ({ items: rawItems, slots, slotProps, @@ -122,7 +122,7 @@ const innerDescribeTreeView = ( return { setProps: result.setProps, - apiRef: apiRef as { current: TreeViewPublicAPI<[TPlugin]> }, + apiRef: apiRef as unknown as { current: TreeViewPublicAPI }, ...getUtils(result), }; }; @@ -131,7 +131,7 @@ const innerDescribeTreeView = ( }); describe('RichTreeViewPro + TreeItem', () => { - const renderRichTreeViewPro: DescribeTreeViewRenderer = ({ + const renderRichTreeViewPro: DescribeTreeViewRenderer = ({ items: rawItems, slotProps, ...other @@ -158,7 +158,7 @@ const innerDescribeTreeView = ( return { setProps: result.setProps, - apiRef: apiRef as { current: TreeViewPublicAPI<[TPlugin]> }, + apiRef: apiRef as unknown as { current: TreeViewPublicAPI }, ...getUtils(result), }; }; @@ -167,7 +167,7 @@ const innerDescribeTreeView = ( }); describe('RichTreeViewPro + TreeItem2', () => { - const renderRichTreeViewPro: DescribeTreeViewRenderer = ({ + const renderRichTreeViewPro: DescribeTreeViewRenderer = ({ items: rawItems, slots, slotProps, @@ -196,7 +196,7 @@ const innerDescribeTreeView = ( return { setProps: result.setProps, - apiRef: apiRef as { current: TreeViewPublicAPI<[TPlugin]> }, + apiRef: apiRef as unknown as { current: TreeViewPublicAPI }, ...getUtils(result), }; }; @@ -205,9 +205,10 @@ const innerDescribeTreeView = ( }); describe('SimpleTreeView + TreeItem', () => { - const renderSimpleTreeView: DescribeTreeViewRenderer = ({ + const renderSimpleTreeView: DescribeTreeViewRenderer = ({ items: rawItems, slots, + slotProps, ...other }) => { const items = rawItems as readonly DescribeTreeViewItem[]; @@ -221,20 +222,21 @@ const innerDescribeTreeView = ( disabled={item.disabled} data-testid={item.id} key={item.id} + {...slotProps?.item} > {item.children?.map(renderItem)} ); const result = render( - + {items.map(renderItem)} , ); return { setProps: result.setProps, - apiRef: apiRef as { current: TreeViewPublicAPI<[TPlugin]> }, + apiRef: apiRef as unknown as { current: TreeViewPublicAPI }, ...getUtils(result), }; }; @@ -243,9 +245,10 @@ const innerDescribeTreeView = ( }); describe('SimpleTreeView + TreeItem2', () => { - const renderSimpleTreeView: DescribeTreeViewRenderer = ({ + const renderSimpleTreeView: DescribeTreeViewRenderer = ({ items: rawItems, slots, + slotProps, ...other }) => { const items = rawItems as readonly DescribeTreeViewItem[]; @@ -259,20 +262,21 @@ const innerDescribeTreeView = ( disabled={item.disabled} data-testid={item.id} key={item.id} + {...slotProps?.item} > {item.children?.map(renderItem)} ); const result = render( - + {items.map(renderItem)} , ); return { setProps: result.setProps, - apiRef: apiRef as { current: TreeViewPublicAPI<[TPlugin]> }, + apiRef: apiRef as unknown as { current: TreeViewPublicAPI }, ...getUtils(result), }; }; @@ -282,15 +286,15 @@ const innerDescribeTreeView = ( }); }; -type Params = [ +type Params = [ string, - DescribeTreeViewTestRunner, + DescribeTreeViewTestRunner, ]; type DescribeTreeView = { -

(...args: Params

): void; - skip:

(...args: Params

) => void; - only:

(...args: Params

) => void; + (...args: Params): void; + skip: (...args: Params) => void; + only: (...args: Params) => void; }; /** diff --git a/test/utils/tree-view/describeTreeView/describeTreeView.types.ts b/test/utils/tree-view/describeTreeView/describeTreeView.types.ts index abe8936e2bd9..017aa8906f99 100644 --- a/test/utils/tree-view/describeTreeView/describeTreeView.types.ts +++ b/test/utils/tree-view/describeTreeView/describeTreeView.types.ts @@ -1,26 +1,28 @@ import * as React from 'react'; import { + MergePluginsProperty, TreeViewAnyPluginSignature, TreeViewPublicAPI, - TreeViewUsedParams, } from '@mui/x-tree-view/internals/models'; import { TreeItemProps } from '@mui/x-tree-view/TreeItem'; import { TreeItem2Props } from '@mui/x-tree-view/TreeItem2'; -export type DescribeTreeViewTestRunner = ( - params: DescribeTreeViewTestRunnerParams, +export type DescribeTreeViewTestRunner = ( + params: DescribeTreeViewTestRunnerParams, ) => void; -export interface DescribeTreeViewRendererReturnValue { +export interface DescribeTreeViewRendererReturnValue< + TPlugins extends TreeViewAnyPluginSignature[], +> { /** * Passes new props to the Tree View. * @param {Partial>} props A subset of the props accepted by the Tree View. */ - setProps: (props: Partial>) => void; + setProps: (props: Partial>) => void; /** * The ref object that allows Tree View manipulation. */ - apiRef: { current: TreeViewPublicAPI<[TPlugin]> }; + apiRef: { current: TreeViewPublicAPI }; /** * Returns the `root` slot of the Tree View. * @returns {HTMLElement} `root` slot of the Tree View. @@ -71,19 +73,23 @@ export interface DescribeTreeViewRendererReturnValue boolean; } -export type DescribeTreeViewRenderer = < +export type DescribeTreeViewRenderer = < R extends DescribeTreeViewItem, >( params: { items: readonly R[]; - } & Omit, 'slots' | 'slotProps'> & { - slots?: TreeViewUsedParams['slots'] & { item?: React.ElementType }; - slotProps?: TreeViewUsedParams['slots'] & { item?: TreeItemProps | TreeItem2Props }; + } & Omit, 'slots' | 'slotProps'> & { + slots?: MergePluginsProperty & { + item?: React.ElementType; + }; + slotProps?: MergePluginsProperty & { + item?: Partial | Partial; + }; }, -) => DescribeTreeViewRendererReturnValue; +) => DescribeTreeViewRendererReturnValue; -interface DescribeTreeViewTestRunnerParams { - render: DescribeTreeViewRenderer; +interface DescribeTreeViewTestRunnerParams { + render: DescribeTreeViewRenderer; setup: | 'SimpleTreeView + TreeItem' | 'SimpleTreeView + TreeItem2' diff --git a/test/utils/tree-view/describeTreeView/index.ts b/test/utils/tree-view/describeTreeView/index.ts index 5eba1f6c9165..4459a7a95583 100644 --- a/test/utils/tree-view/describeTreeView/index.ts +++ b/test/utils/tree-view/describeTreeView/index.ts @@ -1 +1,2 @@ export { describeTreeView } from './describeTreeView'; +export type { DescribeTreeViewRendererReturnValue } from './describeTreeView.types';