diff --git a/.changeset/tree-unitTests.md b/.changeset/tree-unitTests.md new file mode 100644 index 000000000..e046d47b0 --- /dev/null +++ b/.changeset/tree-unitTests.md @@ -0,0 +1,5 @@ +--- +'react-magma-dom': patch +--- + +fix(TreeView): Add more unit tests diff --git a/packages/react-magma-dom/src/components/TreeView/TreeView.stories.tsx b/packages/react-magma-dom/src/components/TreeView/TreeView.stories.tsx index fa37ce94b..59b818371 100644 --- a/packages/react-magma-dom/src/components/TreeView/TreeView.stories.tsx +++ b/packages/react-magma-dom/src/components/TreeView/TreeView.stories.tsx @@ -1331,12 +1331,29 @@ export const ComplexTreeWithShowAll = (args: Partial) => { { id: 'ad-1', title: 'Animation', - children: [], + children: null, }, { id: 'ad-2', title: 'Photography', - children: [], + children: [ + { + id: 'ad-2-child1', + title: 'Wedding', + children: [], + }, + { + id: 'ad-2-child2', + title: 'Nature', + children: [ + { + id: 'ad-2-child2-child1', + title: 'Pet', + children: [], + } + ], + } + ], }, { id: 'ad-3', @@ -1380,7 +1397,7 @@ export const ComplexTreeWithShowAll = (args: Partial) => { { id: 'discipline-geography', title: 'Geography', - children: [], + children: undefined, }, { id: 'discipline-his', @@ -1436,7 +1453,23 @@ export const ComplexTreeWithShowAll = (args: Partial) => { { id: 'nutr-2', title: 'Sports Nutrition', - children: [], + children: [ + { + id: 'nutr-2-child1', + title: 'Protein', + children: [], + }, + { + id: 'nutr-2-child2', + title: 'Supplements', + children: [ + { + id: 'nutr-2-child2-child1', + title: 'Creatine' + } + ], + } + ], }, ], }, diff --git a/packages/react-magma-dom/src/components/TreeView/TreeView.test.js b/packages/react-magma-dom/src/components/TreeView/TreeView.test.js index 878c2aee3..8d048f47d 100644 --- a/packages/react-magma-dom/src/components/TreeView/TreeView.test.js +++ b/packages/react-magma-dom/src/components/TreeView/TreeView.test.js @@ -172,6 +172,25 @@ const TreeItemsMultiLevelControlledOutside = props => { ); }; +const renderTreeItemsRecursively = (treeItems, depth) => { + return treeItems.map(item => { + return ( + + {item.children?.length ? ( + renderTreeItemsRecursively(item.children, depth + 1) + ) : ( + <> + )} + + ); + }); +}; + describe('TreeView', () => { it('should find element by testId', () => { const { getByTestId } = render( @@ -2058,7 +2077,7 @@ describe('TreeView', () => { it('parent should have checked checkbox state when all disabled children are selected and all enabled children are selected. parent should have indeterminate checkbox state when all disabled children are selected and enabled children are partially selected. parent should have indeterminate checkbox state when all disabled children are selected and all enabled children are not selected. and toggle children selection', () => { const onSelectedItemChange = jest.fn(); - const { getByTestId, debug } = render( + const { getByTestId } = render( getTreeItemsWithDisabledChildren({ selectable: TreeViewSelectable.multi, preselectedItems: [ @@ -2598,6 +2617,219 @@ describe('TreeView', () => { }); }); + describe('recursive children', () => { + const recursiveTreeItems = [ + { + id: 'discipline-arts-design', + title: 'Arts and Design', + children: [ + { + id: 'ad-1', + title: 'Animation', + children: [], + }, + { + id: 'ad-2', + title: 'Photography', + children: [ + { + id: 'ad-2-child1', + title: 'Wedding', + children: [], + }, + { + id: 'ad-2-child2', + title: 'Nature', + children: [ + { + id: 'ad-2-child2-child1', + title: 'Pet', + children: [], + }, + ], + }, + ], + }, + { + id: 'ad-3', + title: 'Web Design', + children: [], + }, + ], + }, + { + id: 'discipline-geography', + title: 'Geography', + children: [], + }, + { + id: 'discipline-nutr', + title: 'Nutrition', + children: [ + { + id: 'nutr-1', + title: 'Community Nutrition', + children: [], + }, + { + id: 'nutr-2', + title: 'Sports Nutrition', + children: [ + { + id: 'nutr-2-child1', + title: 'Protein', + children: [], + }, + { + id: 'nutr-2-child2', + title: 'Supplements', + children: [ + { + id: 'nutr-2-child2-child1', + title: 'Creatine', + children: [ + { + id: 'nutr-2-child2-child1-child1', + title: 'Is it safe?', + }, + ], + }, + ], + }, + ], + }, + ], + }, + ]; + + it('can render recursively created children', () => { + const { getByTestId } = render( + {renderTreeItemsRecursively(recursiveTreeItems, 0)} + ); + + expect(getByTestId('discipline-arts-design')).toBeInTheDocument(); + expect(getByTestId('discipline-geography')).toBeInTheDocument(); + expect(getByTestId('discipline-nutr')).toBeInTheDocument(); + + userEvent.click(getByTestId('discipline-arts-design-expand')); + expect(getByTestId('ad-1')).toBeInTheDocument(); + expect(getByTestId('ad-2')).toBeInTheDocument(); + expect(getByTestId('ad-3')).toBeInTheDocument(); + userEvent.click(getByTestId('ad-2-expand')); + expect(getByTestId('ad-2-child1')).toBeInTheDocument(); + expect(getByTestId('ad-2-child2')).toBeInTheDocument(); + userEvent.click(getByTestId('ad-2-child2-expand')); + expect(getByTestId('ad-2-child2-child1')).toBeInTheDocument(); + + userEvent.click(getByTestId('discipline-nutr-expand')); + expect(getByTestId('nutr-1')).toBeInTheDocument(); + expect(getByTestId('nutr-2')).toBeInTheDocument(); + userEvent.click(getByTestId('nutr-2-expand')); + expect(getByTestId('nutr-2-child1')).toBeInTheDocument(); + expect(getByTestId('nutr-2-child2')).toBeInTheDocument(); + userEvent.click(getByTestId('nutr-2-child2-expand')); + expect(getByTestId('nutr-2-child2-child1')).toBeInTheDocument(); + userEvent.click(getByTestId('nutr-2-child2-child1-expand')); + expect(getByTestId('nutr-2-child2-child1-child1')).toBeInTheDocument(); + }); + + it('can render recursively created children with preselected items', () => { + const { getByTestId } = render( + + {renderTreeItemsRecursively(recursiveTreeItems, 0)} + + ); + + expect(getByTestId('discipline-arts-design')).toBeInTheDocument(); + expect(getByTestId('discipline-geography')).toBeInTheDocument(); + expect(getByTestId('discipline-nutr')).toBeInTheDocument(); + expect(getByTestId('discipline-geography')).toHaveAttribute( + 'aria-checked', + 'true' + ); + + userEvent.click(getByTestId('discipline-arts-design-expand')); + expect(getByTestId('ad-1')).toBeInTheDocument(); + expect(getByTestId('ad-2')).toBeInTheDocument(); + expect(getByTestId('ad-3')).toBeInTheDocument(); + userEvent.click(getByTestId('ad-2-expand')); + expect(getByTestId('ad-2-child1')).toBeInTheDocument(); + expect(getByTestId('ad-2-child2')).toBeInTheDocument(); + userEvent.click(getByTestId('ad-2-child2-expand')); + expect(getByTestId('ad-2-child2-child1')).toBeInTheDocument(); + expect(getByTestId('discipline-arts-design')).toHaveAttribute( + 'aria-checked', + 'mixed' + ); + expect(getByTestId('ad-1')).toHaveAttribute('aria-checked', 'true'); + }); + + it('can select and deselect recursively created children', () => { + const { getByTestId } = render( + + {renderTreeItemsRecursively(recursiveTreeItems, 0)} + + ); + + expect(getByTestId('discipline-arts-design')).toBeInTheDocument(); + expect(getByTestId('discipline-geography')).toBeInTheDocument(); + expect(getByTestId('discipline-nutr')).toBeInTheDocument(); + + userEvent.click(getByTestId('discipline-nutr-expand')); + expect(getByTestId('nutr-1')).toHaveAttribute('aria-checked', 'false'); + expect(getByTestId('nutr-2')).toHaveAttribute('aria-checked', 'false'); + + userEvent.click(getByTestId('nutr-2-expand')); + expect(getByTestId('nutr-2-child1')).toBeInTheDocument(); + expect(getByTestId('nutr-2-child2')).toBeInTheDocument(); + + userEvent.click(getByTestId('nutr-2-child2-expand')); + userEvent.click(getByTestId('nutr-2-child2-checkbox')); + expect(getByTestId('nutr-2-child2')).toHaveAttribute( + 'aria-checked', + 'true' + ); + expect(getByTestId('nutr-2-child2-child1')).toHaveAttribute( + 'aria-checked', + 'true' + ); + userEvent.click(getByTestId('nutr-2-child2-child1-expand')); + expect(getByTestId('nutr-2-child2-child1-child1')).toHaveAttribute( + 'aria-checked', + 'true' + ); + + expect(getByTestId('nutr-2')).toHaveAttribute('aria-checked', 'mixed'); + expect(getByTestId('discipline-nutr')).toHaveAttribute( + 'aria-checked', + 'mixed' + ); + userEvent.click(getByTestId('nutr-2-child1-checkbox')); + expect(getByTestId('nutr-2')).toHaveAttribute('aria-checked', 'true'); + userEvent.click(getByTestId('nutr-2-child2-child1-child1-checkbox')); + expect(getByTestId('nutr-2-child1')).toHaveAttribute( + 'aria-checked', + 'true' + ); + expect(getByTestId('nutr-2-child2')).toHaveAttribute( + 'aria-checked', + 'false' + ); + }); + }); + describe('tree with hidden items', () => { const propsFlatTree = { title: 'Chapter/Subchapter', diff --git a/packages/react-magma-dom/src/components/TreeView/useTreeView.ts b/packages/react-magma-dom/src/components/TreeView/useTreeView.ts index 28f9c8ae9..b99f159e1 100644 --- a/packages/react-magma-dom/src/components/TreeView/useTreeView.ts +++ b/packages/react-magma-dom/src/components/TreeView/useTreeView.ts @@ -152,8 +152,6 @@ export function useTreeView(props: UseTreeViewProps) { }); const selectedItems = React.useMemo(() => { - console.log(items); - return items.filter( item => item.checkedStatus === IndeterminateCheckboxStatus.checked ); @@ -167,7 +165,7 @@ export function useTreeView(props: UseTreeViewProps) { }, [items, rawInitialExpandedItems]); const itemToFocus = React.useMemo(() => { - const enabledItems = items.filter(item => !item?.isDisabled); + const enabledItems = items.filter(item => !item.isDisabled); const [firstItem] = enabledItems; if (selectable === TreeViewSelectable.off) { diff --git a/website/react-magma-docs/src/pages/api/tree-view.mdx b/website/react-magma-docs/src/pages/api/tree-view.mdx index d65a53690..0ad6dbf90 100644 --- a/website/react-magma-docs/src/pages/api/tree-view.mdx +++ b/website/react-magma-docs/src/pages/api/tree-view.mdx @@ -880,7 +880,7 @@ export function Example() { } ``` -## Show All / Show Less Button +## Show More / Show Less Button Use the `apiRef.showMore()` prop when using a Show All button inside a tree, and `apiRef.showLess()` when using a Show Less button.