From accc99ae99eaacebcbf85f37cbcddba72f3b757d Mon Sep 17 00:00:00 2001 From: Piotr Rajnisz Date: Wed, 3 Sep 2025 10:16:04 +0200 Subject: [PATCH 1/3] fix(module:tree): read initial state of nzNodes --- components/core/tree/nz-tree-base.service.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/components/core/tree/nz-tree-base.service.ts b/components/core/tree/nz-tree-base.service.ts index b78f9283573..4eebb3b5245 100644 --- a/components/core/tree/nz-tree-base.service.ts +++ b/components/core/tree/nz-tree-base.service.ts @@ -37,11 +37,11 @@ export class NzTreeBaseService { */ initTree(nzNodes: NzTreeNode[]): void { this.rootNodes = nzNodes; - this.expandedNodeList = []; - this.selectedNodeList = []; - this.halfCheckedNodeList = []; - this.checkedNodeList = []; - this.matchedNodeList = []; + this.expandedNodeList = nzNodes == null ? [] : nzNodes.filter(n => n.isExpanded); + this.selectedNodeList = nzNodes == null ? [] : nzNodes.filter(n => n.isSelected); + this.halfCheckedNodeList = nzNodes == null ? [] : nzNodes.filter(n => n.isHalfChecked); + this.checkedNodeList = nzNodes == null ? [] : nzNodes.filter(n => n.isChecked); + this.matchedNodeList = nzNodes == null ? [] : nzNodes.filter(n => n.isMatched); } flattenTreeData(nzNodes: NzTreeNode[], expandedKeys: NzTreeNodeKey[] | true = []): void { From a2e23a17eddf10ece9e524684372c9d05f85d7c6 Mon Sep 17 00:00:00 2001 From: Piotr Rajnisz Date: Wed, 3 Sep 2025 12:15:42 +0200 Subject: [PATCH 2/3] fix(module:tree): read initial state of nzNodes read state recursively (including all descendant nodes) --- components/core/tree/nz-tree-base.service.ts | 24 ++++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/components/core/tree/nz-tree-base.service.ts b/components/core/tree/nz-tree-base.service.ts index 4eebb3b5245..d6dba233597 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,11 +41,11 @@ export class NzTreeBaseService { */ initTree(nzNodes: NzTreeNode[]): void { this.rootNodes = nzNodes; - this.expandedNodeList = nzNodes == null ? [] : nzNodes.filter(n => n.isExpanded); - this.selectedNodeList = nzNodes == null ? [] : nzNodes.filter(n => n.isSelected); - this.halfCheckedNodeList = nzNodes == null ? [] : nzNodes.filter(n => n.isHalfChecked); - this.checkedNodeList = nzNodes == null ? [] : nzNodes.filter(n => n.isChecked); - this.matchedNodeList = nzNodes == null ? [] : nzNodes.filter(n => n.isMatched); + this.expandedNodeList = this.filterNodesRecursively(nzNodes, 'isExpanded'); + this.selectedNodeList = this.filterNodesRecursively(nzNodes, 'isSelected'); + this.halfCheckedNodeList = this.filterNodesRecursively(nzNodes, 'isHalfChecked'); + this.checkedNodeList = this.filterNodesRecursively(nzNodes, 'isChecked'); + this.matchedNodeList = this.filterNodesRecursively(nzNodes, 'isMatched'); } flattenTreeData(nzNodes: NzTreeNode[], expandedKeys: NzTreeNodeKey[] | true = []): void { @@ -567,4 +571,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, []); + } } From 50fd439528e37b8a65b6c3052d920c6c8229b50a Mon Sep 17 00:00:00 2001 From: Piotr Rajnisz Date: Wed, 21 Jan 2026 19:02:53 +0100 Subject: [PATCH 3/3] fix(module:tree): read initial state of nzNodes getting half-checked nodes from checked nodes, setting matchedNodeList not needed (no "matched" in NzTreeNodeOptions); added tests --- components/core/tree/nz-tree-base.service.ts | 5 +- components/tree/tree.spec.ts | 65 ++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/components/core/tree/nz-tree-base.service.ts b/components/core/tree/nz-tree-base.service.ts index d6dba233597..59abcc17a10 100644 --- a/components/core/tree/nz-tree-base.service.ts +++ b/components/core/tree/nz-tree-base.service.ts @@ -43,9 +43,10 @@ export class NzTreeBaseService { this.rootNodes = nzNodes; this.expandedNodeList = this.filterNodesRecursively(nzNodes, 'isExpanded'); this.selectedNodeList = this.filterNodesRecursively(nzNodes, 'isSelected'); - this.halfCheckedNodeList = this.filterNodesRecursively(nzNodes, 'isHalfChecked'); this.checkedNodeList = this.filterNodesRecursively(nzNodes, 'isChecked'); - this.matchedNodeList = this.filterNodesRecursively(nzNodes, 'isMatched'); + this.halfCheckedNodeList = []; + this.refreshCheckState(this.isCheckStrictly); // set halfCheckedNodeList + this.matchedNodeList = []; } flattenTreeData(nzNodes: NzTreeNode[], expandedKeys: NzTreeNodeKey[] | true = []): void { 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'];