diff --git a/components/core/tree/nz-tree-base.service.ts b/components/core/tree/nz-tree-base.service.ts index b78f9283573..59abcc17a10 100644 --- a/components/core/tree/nz-tree-base.service.ts +++ b/components/core/tree/nz-tree-base.service.ts @@ -12,6 +12,10 @@ import { NzTreeNode, NzTreeNodeKey } from './nz-tree-base-node'; import { flattenTreeData, isCheckDisabled, isInArray } from './nz-tree-base-util'; import { NzFormatEmitEvent } from './nz-tree-base.definitions'; +type BooleanKeys = { + [K in keyof T]-?: T[K] extends boolean ? K : never; +}[keyof T]; + @Injectable() export class NzTreeBaseService { DRAG_SIDE_RANGE = 0.25; @@ -37,10 +41,11 @@ export class NzTreeBaseService { */ initTree(nzNodes: NzTreeNode[]): void { this.rootNodes = nzNodes; - this.expandedNodeList = []; - this.selectedNodeList = []; + this.expandedNodeList = this.filterNodesRecursively(nzNodes, 'isExpanded'); + this.selectedNodeList = this.filterNodesRecursively(nzNodes, 'isSelected'); + this.checkedNodeList = this.filterNodesRecursively(nzNodes, 'isChecked'); this.halfCheckedNodeList = []; - this.checkedNodeList = []; + this.refreshCheckState(this.isCheckStrictly); // set halfCheckedNodeList this.matchedNodeList = []; } @@ -567,4 +572,14 @@ export class NzTreeBaseService { }; calc(node.getParentNode()); } + + filterNodesRecursively(nodes: NzTreeNode[], propertyName: BooleanKeys): NzTreeNode[] { + if (nodes == null) return []; + const reducer = (acc: NzTreeNode[], node: NzTreeNode): NzTreeNode[] => { + if (node[propertyName]) acc.push(node); + if (node.children.length > 0) node.children.reduce(reducer, acc); + return acc; + }; + return nodes.reduce(reducer, []); + } } diff --git a/components/tree/tree.spec.ts b/components/tree/tree.spec.ts index ba81dfbbe61..47baa8bded4 100644 --- a/components/tree/tree.spec.ts +++ b/components/tree/tree.spec.ts @@ -61,6 +61,19 @@ describe('tree', () => { expect(component.treeComponent.getExpandedNodeList().length).toEqual(1); })); + it('should expand the specified node based on "expanded" property', fakeAsync(() => { + const nodes: NzTreeNodeOptions[] = structuredClone(component.nodes); + nodes.find(n => n.key === '0-1')!.expanded = true; + component.nodes = nodes; + fixture.detectChanges(); + const shownNodes = nativeElement.querySelectorAll('nz-tree-node[builtin]'); + expect(shownNodes.length).toEqual(4); + tick(300); + fixture.detectChanges(); + // leaf node should not be included + expect(component.treeComponent.getExpandedNodeList().length).toEqual(1); + })); + it('should expand all nodes while setting nzExpandAll', fakeAsync(() => { component.expandAll = true; fixture.detectChanges(); @@ -86,6 +99,25 @@ describe('tree', () => { expect(component.treeComponent.getHalfCheckedNodeList().length).toEqual(1); })); + it('should render checkbox state of nodes based on "checked" property', fakeAsync(() => { + component.expandAll = true; // Just for testing the selected state + const nodes: NzTreeNodeOptions[] = structuredClone(component.nodes); + nodes + .find(n => n.key === '0-0')! + .children!.filter(n => ['0-0-0', '0-0-1'].includes(n.key)) + .forEach(n => (n.checked = true)); + component.nodes = nodes; + fixture.detectChanges(); + const checkedNodes = nativeElement.querySelectorAll('.ant-tree-checkbox-checked'); + const halfCheckedNodes = nativeElement.querySelectorAll('.ant-tree-checkbox-indeterminate'); + expect(checkedNodes.length).toEqual(2); + expect(halfCheckedNodes.length).toEqual(1); + tick(300); + fixture.detectChanges(); + expect(component.treeComponent.getCheckedNodeList().length).toEqual(2); + expect(component.treeComponent.getHalfCheckedNodeList().length).toEqual(1); + })); + it('node check should not affect other nodes based on nzCheckStrictly', fakeAsync(() => { component.expandAll = true; component.checkStrictly = true; @@ -101,6 +133,26 @@ describe('tree', () => { expect(component.treeComponent.getHalfCheckedNodeList().length).toEqual(0); })); + it('node check should not affect other nodes based on nzCheckStrictly (using "checked" property)', fakeAsync(() => { + component.expandAll = true; + component.checkStrictly = true; + const nodes: NzTreeNodeOptions[] = structuredClone(component.nodes); + nodes + .find(n => n.key === '0-0')! + .children!.filter(n => ['0-0-0', '0-0-1'].includes(n.key)) + .forEach(n => (n.checked = true)); + component.nodes = nodes; + fixture.detectChanges(); + const checkedNodes = nativeElement.querySelectorAll('.ant-tree-checkbox-checked'); + const halfCheckedNodes = nativeElement.querySelectorAll('.ant-tree-checkbox-indeterminate'); + expect(checkedNodes.length).toEqual(2); + expect(halfCheckedNodes.length).toEqual(0); + tick(300); + fixture.detectChanges(); + expect(component.treeComponent.getCheckedNodeList().length).toEqual(2); + expect(component.treeComponent.getHalfCheckedNodeList().length).toEqual(0); + })); + it('should select nodes based on nzSelectedKeys', fakeAsync(() => { component.defaultSelectedKeys = ['0-0', '0-1']; fixture.detectChanges(); @@ -112,6 +164,19 @@ describe('tree', () => { expect(component.treeComponent.getSelectedNodeList().length).toEqual(2); })); + it('should select nodes based on "selected" property', fakeAsync(() => { + const nodes: NzTreeNodeOptions[] = structuredClone(component.nodes); + nodes.filter(n => ['0-0', '0-1'].includes(n.key)).forEach(n => (n.selected = true)); + component.nodes = nodes; + fixture.detectChanges(); + // nzMultiple is true + const selectedNodes = nativeElement.querySelectorAll('.ant-tree-node-selected'); + expect(selectedNodes.length).toEqual(2); + tick(300); + fixture.detectChanges(); + expect(component.treeComponent.getSelectedNodeList().length).toEqual(2); + })); + it('should select only one nodes based on nzMultiple:false', fakeAsync(() => { component.multiple = false; component.defaultSelectedKeys = ['0-0', '0-1'];