From 0e27fa76046bec4f8dac8a9cd17b30d9443ff723 Mon Sep 17 00:00:00 2001 From: Michael Salim Date: Mon, 26 Jul 2021 22:08:04 +0100 Subject: [PATCH] Feat: Add hasNodes property to TreeNode for overriding --- README.md | 2 + src/TreeMenu/__tests__/walk.test.tsx | 98 +++++++++++++++++++++++++++- src/TreeMenu/walk.tsx | 19 ++++-- 3 files changed, 113 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fa324ae46..658a7d98a 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,7 @@ Note the difference between the state `active` and `focused`. ENTER is equivalen | -------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ------- | | label | the rendered text of a Node | string | '' | | index | a number that defines the rendering order of this node on the same level; this is not needed if `data` is `TreeNode[]` | number | - | +| hasNodes | If passed, its value will be used to decide whether the node can be expanded. Otherwise, it will be decided based on if `nodes` contains any value | boolean | - | | nodes | a node without this property means that it is the last child of its branch | {[string]:TreeNode} \| TreeNode[] | - | | ...other | User defined props | any | - | @@ -192,6 +193,7 @@ Note the difference between the state `active` and `focused`. ENTER is equivalen | -------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------- | ------- | | key | Node name | string | - | | label | the rendered text of a Node | string | '' | +| hasNodes | If passed, its value will be used to decide whether the node can be expanded. Otherwise, it will be decided based on if `nodes` contains any value | boolean | nodes | a node without this property means that it is the last child of its branch | {[string]:TreeNode} \| TreeNode[] | - | | ...other | User defined props | any | - | diff --git a/src/TreeMenu/__tests__/walk.test.tsx b/src/TreeMenu/__tests__/walk.test.tsx index 08098d424..917f35d65 100644 --- a/src/TreeMenu/__tests__/walk.test.tsx +++ b/src/TreeMenu/__tests__/walk.test.tsx @@ -1,4 +1,4 @@ -import {slowWalk,fastWalk, TreeNode, TreeNodeObject, TreeNodeInArray } from '../walk'; +import { slowWalk, fastWalk, TreeNode, TreeNodeObject, TreeNodeInArray } from '../walk'; const mockDataInObject: TreeNodeObject = { atd: { @@ -97,6 +97,68 @@ const expectedOutcome = [ }, ]; +const hasNodesMockDataInObject: TreeNodeObject = { + A: { + label: 'A', + index: 0, + nodes: { + B: { + label: 'B', + index: 0, + }, + }, + }, + C: { + label: 'C', + index: 1, + hasNodes: true, + }, +}; + +const hasNodesMockDataInArray: TreeNodeInArray[] = [ + { + key: 'A', + label: 'A', + nodes: [ + { + key: 'B', + label: 'B', + nodes: [], + }, + ], + }, + { + key: 'C', + label: 'C', + hasNodes: true, + }, +]; + +const hasNodesExpectedOutcome = [ + { + index: 0, + isOpen: false, + key: 'A', + label: 'A', + level: 0, + hasNodes: true, + openNodes: [], + parent: '', + searchTerm: '', + }, + { + index: 1, + isOpen: false, + key: 'C', + label: 'C', + level: 0, + hasNodes: true, + openNodes: [], + parent: '', + searchTerm: '', + }, +]; + describe('slowWalk', () => { it('should transpose the data object to a desired shape', () => { const result = slowWalk({ data: mockDataInObject, openNodes: [], searchTerm: '7' }); @@ -106,6 +168,23 @@ describe('slowWalk', () => { const result = slowWalk({ data: mockDataInArray, openNodes: [], searchTerm: '7' }); expect(result).toEqual(expectedOutcome); }); + + it('should transpose the data object with supplied hasNodes', () => { + const result = slowWalk({ + data: hasNodesMockDataInObject, + openNodes: [], + searchTerm: '', + }); + expect(result).toEqual(hasNodesExpectedOutcome); + }); + it('should transpose the data array with supplied hasNodes', () => { + const result = slowWalk({ + data: hasNodesMockDataInArray, + openNodes: [], + searchTerm: '', + }); + expect(result).toEqual(hasNodesExpectedOutcome); + }); }); describe('fastWalk', () => { @@ -117,4 +196,21 @@ describe('fastWalk', () => { const result = fastWalk({ data: mockDataInArray, openNodes: [], searchTerm: '7' }); expect(result).toEqual(expectedOutcome); }); + + it('should transpose the data object with supplied hasNodes', () => { + const result = fastWalk({ + data: hasNodesMockDataInObject, + openNodes: [], + searchTerm: '', + }); + expect(result).toEqual(hasNodesExpectedOutcome); + }); + it('should transpose the data array with supplied hasNodes', () => { + const result = fastWalk({ + data: hasNodesMockDataInArray, + openNodes: [], + searchTerm: '', + }); + expect(result).toEqual(hasNodesExpectedOutcome); + }); }); diff --git a/src/TreeMenu/walk.tsx b/src/TreeMenu/walk.tsx index 64423df08..2767233c4 100644 --- a/src/TreeMenu/walk.tsx +++ b/src/TreeMenu/walk.tsx @@ -10,16 +10,20 @@ interface LocaleFunctionProps { [name: string]: any; } -interface MatchSearchFunctionProps extends LocaleFunctionProps { +interface OverrideProps extends LocaleFunctionProps { + hasNodes?: boolean; +} + +interface MatchSearchFunctionProps extends OverrideProps { searchTerm: string; } -export interface TreeNode extends LocaleFunctionProps { +export interface TreeNode extends OverrideProps { index: number; nodes?: TreeNodeObject; } -export interface TreeNodeInArray extends LocaleFunctionProps { +export interface TreeNodeInArray extends OverrideProps { key: string; nodes?: TreeNodeInArray[]; } @@ -106,9 +110,14 @@ const generateBranch = ({ }: BranchProps): Item[] => { const { parent, level, openNodes, searchTerm } = props; - const { nodes, label: rawLabel = 'unknown', ...nodeProps } = node; + const { + nodes, + label: rawLabel = 'unknown', + hasNodes: nodeHasNodes, + ...nodeProps + } = node; const key = [parent, nodeName].filter(x => x).join('/'); - const hasNodes = validateData(nodes); + const hasNodes = nodeHasNodes || validateData(nodes); const isOpen = hasNodes && (openNodes.includes(key) || !!searchTerm); const label = locale({ label: rawLabel, ...nodeProps });