From 01dbecd85ee9804e02d59becbf92d39a834dcac6 Mon Sep 17 00:00:00 2001 From: Zach Arend Date: Tue, 10 Oct 2023 18:34:12 +0000 Subject: [PATCH] fix(material/tree): backwards compatible isExpandable Fix backwards compatibility issue for Trees created before CdkTreeNode#isExpandable was added. Steps to reproduce. 1. Create a example tree using MatNestedTreeControl. Be sure to *not* provide an isExpandable function. 2. Add a parent and child node to example. 3. (App renders a tree with two nodes, parent has chrevron icon on it). 4. Navigate to parent node and press right arrow key on keyboard 5. (Expecting node to expand, but nothing seems to happen). Work-around: click on the node instead of using keyboard Change isExpandable logic of CdkTreeNode. Current behavior is that tree nodes are not expandable unless isExpandable is provided. With this commit applied, tree nodes that have a tree control will be expandable by default. Unless isExpandable is provided, trees using childrenAccessor or levelAccessor are not expandable, but trees using TreeControl are expandable. --- src/cdk/tree/tree-with-tree-control.spec.ts | 27 ++++++++++++------- src/cdk/tree/tree.ts | 20 ++++++++------ .../tree-nested-overview-example.html | 3 +-- .../tree/tree-using-tree-control.spec.ts | 10 +++---- 4 files changed, 36 insertions(+), 24 deletions(-) diff --git a/src/cdk/tree/tree-with-tree-control.spec.ts b/src/cdk/tree/tree-with-tree-control.spec.ts index 23b3bd9d38db..2566da86d598 100644 --- a/src/cdk/tree/tree-with-tree-control.spec.ts +++ b/src/cdk/tree/tree-with-tree-control.spec.ts @@ -19,6 +19,7 @@ import { import {CollectionViewer, DataSource} from '@angular/cdk/collections'; import {Directionality, Direction} from '@angular/cdk/bidi'; +import {createKeyboardEvent} from '@angular/cdk/testing/testbed/fake-events'; import {combineLatest, BehaviorSubject, Observable} from 'rxjs'; import {map} from 'rxjs/operators'; @@ -27,6 +28,7 @@ import {FlatTreeControl} from './control/flat-tree-control'; import {NestedTreeControl} from './control/nested-tree-control'; import {CdkTreeModule, CdkTreeNodePadding} from './index'; import {CdkTree, CdkTreeNode} from './tree'; +import {LEFT_ARROW, RIGHT_ARROW} from '../keycodes'; describe('CdkTree', () => { /** Represents an indent for expectNestedTreeToMatch */ @@ -758,7 +760,7 @@ describe('CdkTree', () => { it('with the right aria-expanded attrs', () => { expect(getNodeAttributes(getNodes(treeElement), 'aria-expanded')) .withContext('aria-expanded attributes') - .toEqual([null, null, null]); + .toEqual(['false', 'false', 'false']); component.toggleRecursively = false; let data = dataSource.data; @@ -773,10 +775,10 @@ describe('CdkTree', () => { // in DOM unless the parent node is expanded. expect(getNodeAttributes(getNodes(treeElement), 'aria-expanded')) .withContext('aria-expanded attributes') - .toEqual([null, 'true', 'false', null]); + .toEqual(['false', 'true', 'false', 'false']); }); - it('should expand/collapse the node multiple times', () => { + it('should expand/collapse the node multiple times using keyboard', () => { component.toggleRecursively = false; let data = dataSource.data; const child = dataSource.addChild(data[1], false); @@ -793,7 +795,10 @@ describe('CdkTree', () => { fixture.detectChanges(); - (getNodes(treeElement)[1] as HTMLElement).click(); + let node = getNodes(treeElement)[1] as HTMLElement; + + node.focus(); + node.dispatchEvent(createKeyboardEvent('keydown', RIGHT_ARROW)); fixture.detectChanges(); expect(component.treeControl.expansionModel.selected.length) @@ -807,7 +812,9 @@ describe('CdkTree', () => { [`topping_3 - cheese_3 + base_3`], ); - (getNodes(treeElement)[1] as HTMLElement).click(); + node = getNodes(treeElement)[1] as HTMLElement; + node.focus(); + node.dispatchEvent(createKeyboardEvent('keydown', LEFT_ARROW)); fixture.detectChanges(); expectNestedTreeToMatch( @@ -820,7 +827,9 @@ describe('CdkTree', () => { .withContext(`Expect node collapsed`) .toBe(0); - (getNodes(treeElement)[1] as HTMLElement).click(); + node = getNodes(treeElement)[1] as HTMLElement; + node.focus(); + node.dispatchEvent(createKeyboardEvent('keydown', RIGHT_ARROW)); fixture.detectChanges(); expect(component.treeControl.expansionModel.selected.length) @@ -1585,9 +1594,9 @@ class NestedCdkTreeAppWithToggle { getChildren = (node: TestData) => node.observableChildren; - treeControl: TreeControl = new NestedTreeControl(this.getChildren, { - isExpandable: node => node.children.length > 0, - }); + isExpandable?: (node: TestData) => boolean; + + treeControl: TreeControl = new NestedTreeControl(this.getChildren); dataSource: FakeDataSource | null = new FakeDataSource(this.treeControl); @ViewChild(CdkTree) tree: CdkTree; diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts index ac82bc83c5f2..a4f638e80214 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -1175,8 +1175,14 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI /** Determines if the tree node is expandable. */ _isExpandable(): boolean { - if (typeof this._tree.treeControl?.isExpandable === 'function') { - return this._tree.treeControl.isExpandable(this._data); + if (this._tree.treeControl) { + if (typeof this._tree.treeControl?.isExpandable === 'function') { + return this._tree.treeControl.isExpandable(this._data); + } + + // For compatibility with trees created using TreeControl before we added + // CdkTreeNode#isExpandable. + return true; } return this._inputIsExpandable; } @@ -1260,18 +1266,16 @@ export class CdkTreeNode implements OnDestroy, OnInit, TreeKeyManagerI /** Collapses this data node. Implemented for TreeKeyManagerItem. */ collapse(): void { - if (!this._isExpandable()) { - return; + if (this.isExpandable) { + this._tree.collapse(this._data); } - this._tree.collapse(this._data); } /** Expands this data node. Implemented for TreeKeyManagerItem. */ expand(): void { - if (!this._isExpandable()) { - return; + if (this.isExpandable) { + this._tree.expand(this._data); } - this._tree.expand(this._data); } _setTabFocusable() { diff --git a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html index 0be398dd2701..8f9e8036bd13 100644 --- a/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html +++ b/src/components-examples/material/tree/tree-nested-overview/tree-nested-overview-example.html @@ -8,8 +8,7 @@ + matTreeNodeToggle>