From ab3a2202b8df47678a2f13fe9e22157ea21e0557 Mon Sep 17 00:00:00 2001 From: Katsiaryna Pustakhod Date: Tue, 22 Aug 2023 15:08:27 +0200 Subject: [PATCH 1/7] Provide fix. --- src/TreeView/index.tsx | 20 ++------------- src/TreeView/node.tsx | 20 ++------------- src/TreeView/utils.ts | 55 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/TreeView/index.tsx b/src/TreeView/index.tsx index f27367cd..cd6f95ee 100644 --- a/src/TreeView/index.tsx +++ b/src/TreeView/index.tsx @@ -32,13 +32,12 @@ import { symmetricDifference, usePrevious, usePreviousData, - isBranchSelectedAndHasSelectedDescendants, getTreeParent, getTreeNode, validateTreeViewData, noop, isBranchNotSelectedAndHasOnlySelectedChild, - isBranchSelectedAndHasOnlySelectedChild, + getOnSelectTreeAction, } from "./utils"; import { Node } from "./node"; import { @@ -884,24 +883,9 @@ const handleKeyDown = ({ return; } - const isSelectedAndHasSelectedDescendants = isBranchSelectedAndHasSelectedDescendants( - data, - element.id, - selectedIds - ); - - const isSelectedAndHasOnlySelectedChild = isBranchSelectedAndHasOnlySelectedChild( - data, - element.id, - selectedIds - ); - dispatch({ type: togglableSelect - ? isSelectedAndHasSelectedDescendants && - !isSelectedAndHasOnlySelectedChild - ? treeTypes.halfSelect - : treeTypes.toggleSelect + ? getOnSelectTreeAction(data, id, selectedIds, disabledIds) : treeTypes.select, id: id, multiSelect, diff --git a/src/TreeView/node.tsx b/src/TreeView/node.tsx index 3a6f2108..94322905 100644 --- a/src/TreeView/node.tsx +++ b/src/TreeView/node.tsx @@ -19,10 +19,9 @@ import { getDescendants, getTreeNode, isBranchNode, - isBranchSelectedAndHasOnlySelectedChild, - isBranchSelectedAndHasSelectedDescendants, noop, propagatedIds, + getOnSelectTreeAction, } from "./utils"; import { baseClassNames, clickActions } from "./constants"; @@ -148,25 +147,10 @@ export const Node = (props: INodeProps) => { lastManuallyToggled: element.id, }); } else if (event.ctrlKey || clickAction === clickActions.select) { - const isSelectedAndHasSelectedDescendants = isBranchSelectedAndHasSelectedDescendants( - data, - element.id, - selectedIds - ); - - const isSelectedAndHasOnlySelectedChild = isBranchSelectedAndHasOnlySelectedChild( - data, - element.id, - selectedIds - ); - //Select dispatch({ type: togglableSelect - ? isSelectedAndHasSelectedDescendants && - !isSelectedAndHasOnlySelectedChild - ? treeTypes.halfSelect - : treeTypes.toggleSelect + ? getOnSelectTreeAction(data, element.id, selectedIds, disabledIds) : treeTypes.select, id: element.id, multiSelect, diff --git a/src/TreeView/utils.ts b/src/TreeView/utils.ts index 5bae86d3..031846af 100644 --- a/src/TreeView/utils.ts +++ b/src/TreeView/utils.ts @@ -1,5 +1,6 @@ import { useEffect, useRef } from "react"; import { EventCallback, INode, INodeRef, NodeId } from "./types"; +import { treeTypes } from "./reducer"; // eslint-disable-next-line @typescript-eslint/no-empty-function export const noop = () => {}; @@ -411,17 +412,18 @@ export const isBranchSelectedAndHasSelectedDescendants = ( ); }; -export const isBranchNotSelectedAndHasAllSelectedDescendants = ( +export const isBranchSelectedAndHasAllSelectedEnabledDescendants = ( data: INode[], elementId: NodeId, - selectedIds: Set + selectedIds: Set, + disabledIds: Set ) => { + const children = getDescendants(data, elementId, new Set()); return ( isBranchNode(data, elementId) && - !selectedIds.has(elementId) && - getDescendants(data, elementId, new Set()).every((item) => - selectedIds.has(item) - ) + selectedIds.has(elementId) && + children.every((item) => selectedIds.has(item)) && + children.every((item) => !disabledIds.has(item)) ); }; @@ -453,6 +455,47 @@ export const isBranchSelectedAndHasOnlySelectedChild = ( ); }; +export const getOnSelectTreeAction = ( + data: INode[], + elementId: NodeId, + selectedIds: Set, + disabledIds: Set +) => { + const isSelectedAndHasSelectedDescendants = isBranchSelectedAndHasSelectedDescendants( + data, + elementId, + selectedIds + ); + + const isSelectedAndHasOnlySelectedChild = isBranchSelectedAndHasOnlySelectedChild( + data, + elementId, + selectedIds + ); + + const isSelectedAndHasAllSelectedEnabledDescendants = isBranchSelectedAndHasAllSelectedEnabledDescendants( + data, + elementId, + selectedIds, + disabledIds + ); + + if (isSelectedAndHasAllSelectedEnabledDescendants) { + // current element is branch and has no disabled and all selected descendants + return treeTypes.toggleSelect; + } else if ( + // current element is branch and has any number of selected descendants + // OR + // current element is branch and has only one selected child + isSelectedAndHasSelectedDescendants && + !isSelectedAndHasOnlySelectedChild + ) { + return treeTypes.halfSelect; + } + + return treeTypes.toggleSelect; +}; + export const getTreeParent = (data: INode[]): INode => { const parentNode: INode | undefined = data.find( (node) => node.parent === null From b69c30caa115798a43cdc2776dd852b8378bd4ec Mon Sep 17 00:00:00 2001 From: Katsiaryna Pustakhod Date: Wed, 23 Aug 2023 12:42:39 +0200 Subject: [PATCH 2/7] Add tests. --- src/__tests__/CheckboxTree.test.tsx | 2 +- src/__tests__/TreeViewOnSelect.test.tsx | 128 ++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/TreeViewOnSelect.test.tsx diff --git a/src/__tests__/CheckboxTree.test.tsx b/src/__tests__/CheckboxTree.test.tsx index 751bf1f4..7ca10c86 100644 --- a/src/__tests__/CheckboxTree.test.tsx +++ b/src/__tests__/CheckboxTree.test.tsx @@ -424,7 +424,7 @@ test("should set focus on first node if data has changed", () => { ); }); -test.only("should preserve focus on node if changed data contains previouslt focused node", () => { +test("should preserve focus on node if changed data contains previouslt focused node", () => { const filteredData = flattenTree({ name: "", children: [ diff --git a/src/__tests__/TreeViewOnSelect.test.tsx b/src/__tests__/TreeViewOnSelect.test.tsx new file mode 100644 index 00000000..2d0d3fdc --- /dev/null +++ b/src/__tests__/TreeViewOnSelect.test.tsx @@ -0,0 +1,128 @@ +import "@testing-library/jest-dom/extend-expect"; +import React from "react"; +import TreeView, { ITreeViewOnSelectProps } from "../TreeView"; +import { fireEvent, render } from "@testing-library/react"; +import { flattenTree } from "../TreeView/utils"; +import { INode, NodeId } from "../TreeView/types"; + +const folder = { + name: "", + children: [ + { + name: "Drinks", + children: [ + { + name: "Tea", + children: [ + { name: "Black Tea" }, + { name: "Green Tea" }, + { name: "Red Tea" }, + { name: "Matcha" }, + ], + }, + ], + }, + ], +}; + +const initialData = flattenTree(folder); + +interface TreeViewOnSelectProps { + data?: INode[]; + selectedIds?: NodeId[]; + propagateSelect?: boolean; + defaultSelectedIds?: NodeId[]; + onSelect: (props: ITreeViewOnSelectProps) => void; +} + +function TreeViewOnSelect(props: TreeViewOnSelectProps) { + const { + data = initialData, + selectedIds, + propagateSelect = true, + defaultSelectedIds, + onSelect, + } = props; + return ( +
+ { + return ( +
+
{ + handleSelect(e); + e.stopPropagation(); + }} + /> + + {element.name}-{element.id} + +
+ ); + }} + /> +
+ ); +} + +let mockedOnSelect: (props: ITreeViewOnSelectProps) => void; + +describe("onSelect", () => { + beforeEach(() => { + mockedOnSelect = jest.fn(); + }); + + test("should not show half-selected for deselected branch node", () => { + const { queryAllByRole } = render( + + ); + const nodes = queryAllByRole("treeitem"); + + const expectedSelected = { + element: { children: [3,4,5,6], id: 2, name: "Tea", parent: 1 }, + isSelected: true, + isHalfSelected: false, + }; + const expectedDeselected = { + element: { children: [3,4,5,6], id: 2, name: "Tea", parent: 1 }, + isSelected: false, + isHalfSelected: false, + }; + + if (document.activeElement == null) + throw new Error( + `Expected to find an active element on the document (after focusing the second element with role["treeitem"]), but did not.` + ); + + fireEvent.click(nodes[1].getElementsByClassName("checkbox-icon")[0]); + + expect(mockedOnSelect).toHaveBeenCalledWith(expect.objectContaining(expectedSelected)); + expect(nodes[1]).toHaveAttribute("aria-checked", "true"); + + fireEvent.click(nodes[1].getElementsByClassName("checkbox-icon")[0]); + + expect(mockedOnSelect).toHaveBeenCalledWith(expect.objectContaining(expectedDeselected)); + expect(nodes[1]).toHaveAttribute("aria-checked", "false"); + }); +}); From f67223f46f09d0b47d48d29f8047e933c9b5ba43 Mon Sep 17 00:00:00 2001 From: Katsiaryna Pustakhod Date: Thu, 24 Aug 2023 08:55:51 +0200 Subject: [PATCH 3/7] Fix display all examples --- website/sidebars.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/website/sidebars.js b/website/sidebars.js index cf571e4b..8d818f6d 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -13,11 +13,13 @@ module.exports = { "examples-ControlledExpandedNode", "examples-DataTypes", "examples-DirectoryTree", - "examples-MultiSelectDirectoryTree", + "examples-Filtering", "examples-MultiSelectCheckbox", - "examples-MultiSelectCheckboxDisabled", "examples-MultiSelectCheckboxAsync", + "examples-MultiSelectCheckboxAsyncControlled", "examples-MultiSelectCheckboxControlled", + "examples-MultiSelectCheckboxDisabled", + "examples-MultiSelectDirectoryTree", "examples-SingleSelectCheckbox", ], }, From 43b0cdd36093e262167e2d56b485d87e833d8401 Mon Sep 17 00:00:00 2001 From: Katsiaryna Pustakhod Date: Thu, 24 Aug 2023 09:13:03 +0200 Subject: [PATCH 4/7] Remove circular dependency --- src/TreeView/constants.ts | 21 +++++++++++++++++++++ src/TreeView/index.tsx | 2 +- src/TreeView/node.tsx | 4 ++-- src/TreeView/reducer.ts | 22 +--------------------- src/TreeView/utils.ts | 2 +- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/src/TreeView/constants.ts b/src/TreeView/constants.ts index 4ad6aa31..b886354b 100644 --- a/src/TreeView/constants.ts +++ b/src/TreeView/constants.ts @@ -22,3 +22,24 @@ export const nodeActions = { } as const; export const NODE_ACTIONS = Object.freeze(Object.values(nodeActions)); + +export const treeTypes = { + collapse: "COLLAPSE", + collapseMany: "COLLAPSE_MANY", + expand: "EXPAND", + expandMany: "EXPAND_MANY", + halfSelect: "HALF_SELECT", + select: "SELECT", + deselect: "DESELECT", + toggle: "TOGGLE", + toggleSelect: "TOGGLE_SELECT", + changeSelectMany: "SELECT_MANY", + exclusiveChangeSelectMany: "EXCLUSIVE_CHANGE_SELECT_MANY", + focus: "FOCUS", + blur: "BLUR", + disable: "DISABLE", + enable: "ENABLE", + clearLastManuallyToggled: "CLEAR_MANUALLY_TOGGLED", + controlledSelectMany: "CONTROLLED_SELECT_MANY", + updateTreeStateWhenDataChanged: "UPDATE_TREE_STATE_WHEN_DATA_CHANGED", +} as const; diff --git a/src/TreeView/index.tsx b/src/TreeView/index.tsx index cd6f95ee..cd7ff18e 100644 --- a/src/TreeView/index.tsx +++ b/src/TreeView/index.tsx @@ -4,7 +4,6 @@ import React, { useEffect, useReducer, useRef } from "react"; import { ITreeViewState, treeReducer, - treeTypes, TreeViewAction, } from "./reducer"; import { @@ -45,6 +44,7 @@ import { clickActions, CLICK_ACTIONS, NODE_ACTIONS, + treeTypes, } from "./constants"; interface IUseTreeProps { diff --git a/src/TreeView/node.tsx b/src/TreeView/node.tsx index 94322905..e4f6819d 100644 --- a/src/TreeView/node.tsx +++ b/src/TreeView/node.tsx @@ -1,6 +1,6 @@ import React from "react"; import cx from "classnames"; -import { ITreeViewState, treeTypes, TreeViewAction } from "./reducer"; +import { ITreeViewState, TreeViewAction } from "./reducer"; import { ClickActions, EventCallback, @@ -23,7 +23,7 @@ import { propagatedIds, getOnSelectTreeAction, } from "./utils"; -import { baseClassNames, clickActions } from "./constants"; +import { baseClassNames, clickActions, treeTypes } from "./constants"; export interface INodeProps { element: INode; diff --git a/src/TreeView/reducer.ts b/src/TreeView/reducer.ts index c4d71082..ec6679c7 100644 --- a/src/TreeView/reducer.ts +++ b/src/TreeView/reducer.ts @@ -1,27 +1,7 @@ +import { treeTypes } from "./constants"; import { NodeId } from "./types"; import { difference } from "./utils"; -export const treeTypes = { - collapse: "COLLAPSE", - collapseMany: "COLLAPSE_MANY", - expand: "EXPAND", - expandMany: "EXPAND_MANY", - halfSelect: "HALF_SELECT", - select: "SELECT", - deselect: "DESELECT", - toggle: "TOGGLE", - toggleSelect: "TOGGLE_SELECT", - changeSelectMany: "SELECT_MANY", - exclusiveChangeSelectMany: "EXCLUSIVE_CHANGE_SELECT_MANY", - focus: "FOCUS", - blur: "BLUR", - disable: "DISABLE", - enable: "ENABLE", - clearLastManuallyToggled: "CLEAR_MANUALLY_TOGGLED", - controlledSelectMany: "CONTROLLED_SELECT_MANY", - updateTreeStateWhenDataChanged: "UPDATE_TREE_STATE_WHEN_DATA_CHANGED", -} as const; - export type TreeViewAction = | { type: "COLLAPSE"; id: NodeId; lastInteractedWith?: NodeId | null } | { type: "COLLAPSE_MANY"; ids: NodeId[]; lastInteractedWith?: NodeId | null } diff --git a/src/TreeView/utils.ts b/src/TreeView/utils.ts index 031846af..56a6423a 100644 --- a/src/TreeView/utils.ts +++ b/src/TreeView/utils.ts @@ -1,6 +1,6 @@ import { useEffect, useRef } from "react"; import { EventCallback, INode, INodeRef, NodeId } from "./types"; -import { treeTypes } from "./reducer"; +import { treeTypes } from "./constants"; // eslint-disable-next-line @typescript-eslint/no-empty-function export const noop = () => {}; From 550a14cbdf060c804a4c711eeac03c7cf4e6b0f2 Mon Sep 17 00:00:00 2001 From: Katsiaryna Pustakhod Date: Thu, 24 Aug 2023 09:14:52 +0200 Subject: [PATCH 5/7] Review fix --- src/__tests__/TreeViewOnSelect.test.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/__tests__/TreeViewOnSelect.test.tsx b/src/__tests__/TreeViewOnSelect.test.tsx index 2d0d3fdc..6d23d299 100644 --- a/src/__tests__/TreeViewOnSelect.test.tsx +++ b/src/__tests__/TreeViewOnSelect.test.tsx @@ -59,7 +59,6 @@ function TreeViewOnSelect(props: TreeViewOnSelectProps) { nodeAction="check" nodeRenderer={({ element, - isHalfSelected, getNodeProps, handleSelect, handleExpand, @@ -67,9 +66,7 @@ function TreeViewOnSelect(props: TreeViewOnSelectProps) { return (
{ handleSelect(e); e.stopPropagation(); From e78d84e96b9e602d9ef35c87b9d66f8b50f9b7c7 Mon Sep 17 00:00:00 2001 From: Katsiaryna Pustakhod Date: Thu, 24 Aug 2023 11:50:44 +0200 Subject: [PATCH 6/7] Fix issue with halfSelected parent not change its status to selected when propagateSelect=false and after children deselected not possible to select parent. --- src/TreeView/index.tsx | 9 +- src/__tests__/CheckboxTree.test.tsx | 147 ++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 7 deletions(-) diff --git a/src/TreeView/index.tsx b/src/TreeView/index.tsx index df8781c6..b2e895fa 100644 --- a/src/TreeView/index.tsx +++ b/src/TreeView/index.tsx @@ -1,11 +1,7 @@ import cx from "classnames"; import PropTypes from "prop-types"; import React, { useEffect, useReducer, useRef } from "react"; -import { - ITreeViewState, - treeReducer, - TreeViewAction, -} from "./reducer"; +import { ITreeViewState, treeReducer, TreeViewAction } from "./reducer"; import { ClickActions, INode, @@ -101,7 +97,6 @@ const useTree = ({ const { selectedIds, - controlledIds, expandedIds, disabledIds, tabbableId, @@ -330,7 +325,7 @@ const useTree = ({ //Update parent if a child changes useEffect(() => { if (propagateSelectUpwards) { - const idsToUpdate = new Set([...toggledIds, ...controlledIds]); + const idsToUpdate = new Set([...toggledIds]); if ( lastInteractedWith && lastAction !== treeTypes.focus && diff --git a/src/__tests__/CheckboxTree.test.tsx b/src/__tests__/CheckboxTree.test.tsx index 7ca10c86..af85973e 100644 --- a/src/__tests__/CheckboxTree.test.tsx +++ b/src/__tests__/CheckboxTree.test.tsx @@ -57,6 +57,7 @@ function CheckboxTree({ defaultSelectedIds = [], defaultExpandedIds = [], defaultDisabledIds = [], + selectedIds, }: { defaultSelectedIds?: NodeId[]; defaultExpandedIds?: NodeId[]; @@ -64,6 +65,7 @@ function CheckboxTree({ propagateSelect?: boolean; multiSelect?: boolean; data?: INode[]; + selectedIds?: NodeId[]; }) { return (
@@ -76,6 +78,7 @@ function CheckboxTree({ defaultSelectedIds={defaultSelectedIds} defaultExpandedIds={defaultExpandedIds} defaultDisabledIds={defaultDisabledIds} + selectedIds={selectedIds} propagateSelectUpwards togglableSelect nodeAction="check" @@ -478,3 +481,147 @@ test("should preserve focus on node if changed data contains previouslt focused "Drinks" ); }); + +describe("halfSelected parent should change status when propagateSelect={false}", () => { + const checkSelectionAndHalfSelection = (nodes: HTMLElement[]) => { + //Initial state + //[-] Fruits + //*[ ] Avocados + //*[+] Bananas + //*[ ] Berries + // ... + + console.log(nodes[2].innerHTML); + expect(nodes[0]).toHaveAttribute("aria-checked", "mixed"); + expect(nodes[2]).toHaveAttribute("aria-checked", "true"); + + nodes[0].focus(); + if (document.activeElement == null) + throw new Error( + `Expected to find an active element on the document (after focusing the second element with role["treeitem"]), but did not.` + ); + fireEvent.click(nodes[0].getElementsByClassName("checkbox-icon")[0]); //select Fruits + //Expected state 1 + //[+] Fruits + //*[ ] Avocados + //*[+] Bananas + //*[ ] Berries + // ... + + expect(nodes[0]).toHaveAttribute("aria-checked", "true"); + expect(nodes[2]).toHaveAttribute("aria-checked", "true"); + + fireEvent.click(nodes[0].getElementsByClassName("checkbox-icon")[0]); //half-select Fruits + //Expected state 2 + //[-] Fruits + //*[ ] Avocados + //*[+] Bananas + //*[ ] Berries + // ... + expect(nodes[0]).toHaveAttribute("aria-checked", "mixed"); + expect(nodes[2]).toHaveAttribute("aria-checked", "true"); + }; + + test("to selected when default selected child and selection changed manually", () => { + const { queryAllByRole } = render( + + ); + checkSelectionAndHalfSelection(queryAllByRole("treeitem")); + }); + + test("to selected when selection changed manually", () => { + const { queryAllByRole } = render( + + ); + const nodes = queryAllByRole("treeitem"); + + nodes[0].focus(); + if (document.activeElement == null) + throw new Error( + `Expected to find an active element on the document (after focusing the second element with role["treeitem"]), but did not.` + ); + + fireEvent.click(nodes[2].getElementsByClassName("checkbox-icon")[0]); //select Bananas + + expect(nodes[0]).toHaveAttribute("aria-checked", "mixed"); + expect(nodes[2]).toHaveAttribute("aria-checked", "true"); + + checkSelectionAndHalfSelection(nodes); + }); + + test("to selected when selection changed controlled and manually", () => { + const { queryAllByRole } = render( + + ); + checkSelectionAndHalfSelection(queryAllByRole("treeitem")); + }); + + test("to deselected and to selected when no selected children after children where selected manually initially", () => { + const { queryAllByRole } = render( + + ); + const nodes = queryAllByRole("treeitem"); + + nodes[0].focus(); + if (document.activeElement == null) + throw new Error( + `Expected to find an active element on the document (after focusing the second element with role["treeitem"]), but did not.` + ); + + fireEvent.click(nodes[2].getElementsByClassName("checkbox-icon")[0]); //select Bananas + + expect(nodes[0]).toHaveAttribute("aria-checked", "mixed"); + expect(nodes[2]).toHaveAttribute("aria-checked", "true"); + + checkSelectionAndHalfSelection(nodes); + + fireEvent.click(nodes[2].getElementsByClassName("checkbox-icon")[0]); //deselect Bananas + + expect(nodes[0]).toHaveAttribute("aria-checked", "false"); + expect(nodes[2]).toHaveAttribute("aria-checked", "false"); + + fireEvent.click(nodes[0].getElementsByClassName("checkbox-icon")[0]); //select Fruits + + expect(nodes[0]).toHaveAttribute("aria-checked", "true"); + expect(nodes[2]).toHaveAttribute("aria-checked", "false"); + + fireEvent.click(nodes[0].getElementsByClassName("checkbox-icon")[0]); //deselect Fruits + + expect(nodes[0]).toHaveAttribute("aria-checked", "false"); + expect(nodes[2]).toHaveAttribute("aria-checked", "false"); + }); + + test("to deselected and to selected when no selected children after children where selected controlled initially", () => { + const { queryAllByRole } = render( + + ); + const nodes = queryAllByRole("treeitem"); + checkSelectionAndHalfSelection(nodes); + fireEvent.click(nodes[2].getElementsByClassName("checkbox-icon")[0]); //deselect Bananas + + expect(nodes[0]).toHaveAttribute("aria-checked", "false"); + expect(nodes[2]).toHaveAttribute("aria-checked", "false"); + + fireEvent.click(nodes[0].getElementsByClassName("checkbox-icon")[0]); //select Fruits + + expect(nodes[0]).toHaveAttribute("aria-checked", "true"); + expect(nodes[2]).toHaveAttribute("aria-checked", "false"); + + fireEvent.click(nodes[0].getElementsByClassName("checkbox-icon")[0]); //deselect Fruits + + expect(nodes[0]).toHaveAttribute("aria-checked", "false"); + expect(nodes[2]).toHaveAttribute("aria-checked", "false"); + }); +}); From 8f392fa8fb855fb1b1d833a7f92bd39c56509440 Mon Sep 17 00:00:00 2001 From: Katsiaryna Pustakhod Date: Thu, 24 Aug 2023 14:40:00 +0200 Subject: [PATCH 7/7] Tests refactored. --- src/__tests__/CheckboxTree.test.tsx | 159 +++++++++++++--------------- 1 file changed, 73 insertions(+), 86 deletions(-) diff --git a/src/__tests__/CheckboxTree.test.tsx b/src/__tests__/CheckboxTree.test.tsx index af85973e..f866e0e4 100644 --- a/src/__tests__/CheckboxTree.test.tsx +++ b/src/__tests__/CheckboxTree.test.tsx @@ -491,7 +491,6 @@ describe("halfSelected parent should change status when propagateSelect={false}" //*[ ] Berries // ... - console.log(nodes[2].innerHTML); expect(nodes[0]).toHaveAttribute("aria-checked", "mixed"); expect(nodes[2]).toHaveAttribute("aria-checked", "true"); @@ -522,106 +521,94 @@ describe("halfSelected parent should change status when propagateSelect={false}" expect(nodes[2]).toHaveAttribute("aria-checked", "true"); }; - test("to selected when default selected child and selection changed manually", () => { - const { queryAllByRole } = render( - - ); - checkSelectionAndHalfSelection(queryAllByRole("treeitem")); - }); - - test("to selected when selection changed manually", () => { - const { queryAllByRole } = render( - - ); - const nodes = queryAllByRole("treeitem"); - - nodes[0].focus(); - if (document.activeElement == null) - throw new Error( - `Expected to find an active element on the document (after focusing the second element with role["treeitem"]), but did not.` - ); - - fireEvent.click(nodes[2].getElementsByClassName("checkbox-icon")[0]); //select Bananas - - expect(nodes[0]).toHaveAttribute("aria-checked", "mixed"); - expect(nodes[2]).toHaveAttribute("aria-checked", "true"); - - checkSelectionAndHalfSelection(nodes); - }); - - test("to selected when selection changed controlled and manually", () => { - const { queryAllByRole } = render( - - ); - checkSelectionAndHalfSelection(queryAllByRole("treeitem")); - }); - - test("to deselected and to selected when no selected children after children where selected manually initially", () => { - const { queryAllByRole } = render( - - ); - const nodes = queryAllByRole("treeitem"); - - nodes[0].focus(); - if (document.activeElement == null) - throw new Error( - `Expected to find an active element on the document (after focusing the second element with role["treeitem"]), but did not.` - ); - - fireEvent.click(nodes[2].getElementsByClassName("checkbox-icon")[0]); //select Bananas - - expect(nodes[0]).toHaveAttribute("aria-checked", "mixed"); - expect(nodes[2]).toHaveAttribute("aria-checked", "true"); - - checkSelectionAndHalfSelection(nodes); - + const checkDeselectionAndSelectionOfParent = (nodes: HTMLElement[]) => { fireEvent.click(nodes[2].getElementsByClassName("checkbox-icon")[0]); //deselect Bananas - + //Initial state + //[ ] Fruits + //*[ ] Avocados + //*[ ] Bananas + //*[ ] Berries + // ... expect(nodes[0]).toHaveAttribute("aria-checked", "false"); expect(nodes[2]).toHaveAttribute("aria-checked", "false"); fireEvent.click(nodes[0].getElementsByClassName("checkbox-icon")[0]); //select Fruits - + //Expected state 1 + //[+] Fruits + //*[ ] Avocados + //*[ ] Bananas + //*[ ] Berries + // ... expect(nodes[0]).toHaveAttribute("aria-checked", "true"); expect(nodes[2]).toHaveAttribute("aria-checked", "false"); fireEvent.click(nodes[0].getElementsByClassName("checkbox-icon")[0]); //deselect Fruits - + //Expected state 1 + //[ ] Fruits + //*[ ] Avocados + //*[ ] Bananas + //*[ ] Berries + // ... expect(nodes[0]).toHaveAttribute("aria-checked", "false"); expect(nodes[2]).toHaveAttribute("aria-checked", "false"); - }); + }; - test("to deselected and to selected when no selected children after children where selected controlled initially", () => { - const { queryAllByRole } = render( - - ); - const nodes = queryAllByRole("treeitem"); - checkSelectionAndHalfSelection(nodes); - fireEvent.click(nodes[2].getElementsByClassName("checkbox-icon")[0]); //deselect Bananas + test( + "1. to selected when selection changed manually," + + "2. to deselected and to selected when no selected children after children where selected manually initially", + () => { + const { queryAllByRole } = render( + + ); + const nodes = queryAllByRole("treeitem"); - expect(nodes[0]).toHaveAttribute("aria-checked", "false"); - expect(nodes[2]).toHaveAttribute("aria-checked", "false"); + nodes[2].focus(); + if (document.activeElement == null) + throw new Error( + `Expected to find an active element on the document (after focusing the second element with role["treeitem"]), but did not.` + ); - fireEvent.click(nodes[0].getElementsByClassName("checkbox-icon")[0]); //select Fruits + fireEvent.click(nodes[2].getElementsByClassName("checkbox-icon")[0]); //select Bananas - expect(nodes[0]).toHaveAttribute("aria-checked", "true"); - expect(nodes[2]).toHaveAttribute("aria-checked", "false"); + expect(nodes[0]).toHaveAttribute("aria-checked", "mixed"); + expect(nodes[2]).toHaveAttribute("aria-checked", "true"); - fireEvent.click(nodes[0].getElementsByClassName("checkbox-icon")[0]); //deselect Fruits + checkSelectionAndHalfSelection(nodes); + checkDeselectionAndSelectionOfParent(nodes); + } + ); - expect(nodes[0]).toHaveAttribute("aria-checked", "false"); - expect(nodes[2]).toHaveAttribute("aria-checked", "false"); - }); + test( + "1. to selected when default selected child and selection changed manually" + + "2. to deselected and to selected when no selected children after children where selected by default initially", + () => { + const { queryAllByRole } = render( + + ); + const nodes = queryAllByRole("treeitem"); + checkSelectionAndHalfSelection(nodes); + checkDeselectionAndSelectionOfParent(nodes); + } + ); + + test( + "1. to selected when selection changed controlled and manually" + + "2. to deselected and to selected when no selected children after children where selected controlled initially", + () => { + const { queryAllByRole } = render( + + ); + const nodes = queryAllByRole("treeitem"); + checkSelectionAndHalfSelection(nodes); + checkDeselectionAndSelectionOfParent(nodes); + } + ); });