From 43b8dcbb1f17a9d5fcedc88e996c241ab9e28dd8 Mon Sep 17 00:00:00 2001 From: DBowen33 <42016383+DBowen33@users.noreply.github.com> Date: Mon, 17 Jun 2024 14:37:57 -0500 Subject: [PATCH] fix(material/tree): aria-expanded attribute should not appear in the leaf node (#29096) * fix(material/tree): fixed unit tests fixed unit tests Fixes #21922 * fix(material/tree): updated public api file updated public api file Fixes #21922 --- src/cdk/tree/tree.spec.ts | 24 ++++++++---------------- src/cdk/tree/tree.ts | 22 +++++++++++++++++++++- src/material/tree/tree.spec.ts | 23 +++++++---------------- tools/public_api_guard/cdk/tree.md | 2 ++ 4 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/cdk/tree/tree.spec.ts b/src/cdk/tree/tree.spec.ts index db8d9eedbb15..093b4be86af5 100644 --- a/src/cdk/tree/tree.spec.ts +++ b/src/cdk/tree/tree.spec.ts @@ -145,20 +145,14 @@ describe('CdkTree', () => { let data = dataSource.data; dataSource.addChild(data[2]); fixture.detectChanges(); - expect( - getNodes(treeElement).every(node => { - return node.getAttribute('aria-expanded') === 'false'; - }), - ).toBe(true); + let ariaExpandedStates = getNodes(treeElement).map(n => n.getAttribute('aria-expanded')); + expect(ariaExpandedStates).toEqual([null, null, 'false', null]); component.treeControl.expandAll(); fixture.detectChanges(); - expect( - getNodes(treeElement).every(node => { - return node.getAttribute('aria-expanded') === 'true'; - }), - ).toBe(true); + ariaExpandedStates = getNodes(treeElement).map(n => n.getAttribute('aria-expanded')); + expect(ariaExpandedStates).toEqual([null, null, 'true', null]); }); it('with the right data', () => { @@ -805,11 +799,8 @@ describe('CdkTree', () => { }); it('with the right aria-expanded attrs', () => { - expect( - getNodes(treeElement).every(node => { - return node.getAttribute('aria-expanded') === 'false'; - }), - ).toBe(true); + let ariaExpandedStates = getNodes(treeElement).map(n => n.getAttribute('aria-expanded')); + expect(ariaExpandedStates).toEqual([null, null, null]); component.toggleRecursively = false; fixture.changeDetectorRef.markForCheck(); @@ -822,7 +813,7 @@ describe('CdkTree', () => { fixture.detectChanges(); const ariaExpanded = getNodes(treeElement).map(n => n.getAttribute('aria-expanded')); - expect(ariaExpanded).toEqual(['false', 'true', 'false', 'false']); + expect(ariaExpanded).toEqual([null, 'true', 'false', null]); }); it('should expand/collapse the node multiple times', () => { @@ -886,6 +877,7 @@ describe('CdkTree', () => { }); it('should expand/collapse the node recursively', () => { + fixture.changeDetectorRef.markForCheck(); let data = dataSource.data; const child = dataSource.addChild(data[1], false); dataSource.addChild(child, false); diff --git a/src/cdk/tree/tree.ts b/src/cdk/tree/tree.ts index 9746981bdf4f..16b2a21bc06f 100644 --- a/src/cdk/tree/tree.ts +++ b/src/cdk/tree/tree.ts @@ -324,7 +324,7 @@ export class CdkTree implements AfterContentChecked, CollectionViewer, exportAs: 'cdkTreeNode', host: { 'class': 'cdk-tree-node', - '[attr.aria-expanded]': 'isExpanded', + '[attr.aria-expanded]': 'isLeafNode ? null : isExpanded', }, standalone: true, }) @@ -375,6 +375,26 @@ export class CdkTreeNode implements FocusableOption, OnDestroy, OnInit return this._tree.treeControl.isExpanded(this._data); } + /* If leaf node, return true to not assign aria-expanded attribute */ + get isLeafNode(): boolean { + // If flat tree node data returns false for expandable property, it's a leaf node + if ( + this._tree.treeControl.isExpandable !== undefined && + !this._tree.treeControl.isExpandable(this._data) + ) { + return true; + + // If nested tree node data returns 0 descendants, it's a leaf node + } else if ( + this._tree.treeControl.isExpandable === undefined && + this._tree.treeControl.getDescendants(this._data).length === 0 + ) { + return true; + } + + return false; + } + get level(): number { // If the treeControl has a getLevel method, use it to get the level. Otherwise read the // aria-level off the parent node and use it as the level for this node (note aria-level is diff --git a/src/material/tree/tree.spec.ts b/src/material/tree/tree.spec.ts index 7091498106cc..fc6e29073623 100644 --- a/src/material/tree/tree.spec.ts +++ b/src/material/tree/tree.spec.ts @@ -78,20 +78,14 @@ describe('MatTree', () => { const data = underlyingDataSource.data; underlyingDataSource.addChild(data[2]); fixture.detectChanges(); - expect( - getNodes(treeElement).every(node => { - return node.getAttribute('aria-expanded') === 'false'; - }), - ).toBe(true); + let ariaExpandedStates = getNodes(treeElement).map(n => n.getAttribute('aria-expanded')); + expect(ariaExpandedStates).toEqual([null, null, 'false']); component.treeControl.expandAll(); fixture.detectChanges(); - expect( - getNodes(treeElement).every(node => { - return node.getAttribute('aria-expanded') === 'true'; - }), - ).toBe(true); + ariaExpandedStates = getNodes(treeElement).map(n => n.getAttribute('aria-expanded')); + expect(ariaExpandedStates).toEqual([null, null, 'true', null]); }); it('with the right data', () => { @@ -470,11 +464,8 @@ describe('MatTree', () => { }); it('with the right aria-expanded attrs', () => { - expect( - getNodes(treeElement).every(node => { - return node.getAttribute('aria-expanded') === 'false'; - }), - ).toBe(true); + let ariaExpandedStates = getNodes(treeElement).map(n => n.getAttribute('aria-expanded')); + expect(ariaExpandedStates).toEqual([null, null, null]); component.toggleRecursively = false; const data = underlyingDataSource.data; @@ -486,7 +477,7 @@ describe('MatTree', () => { fixture.detectChanges(); const ariaExpanded = getNodes(treeElement).map(n => n.getAttribute('aria-expanded')); - expect(ariaExpanded).toEqual(['false', 'true', 'false', 'false']); + expect(ariaExpanded).toEqual([null, 'true', 'false', null]); }); it('should expand/collapse the node', () => { diff --git a/tools/public_api_guard/cdk/tree.md b/tools/public_api_guard/cdk/tree.md index 693d41f1c2f3..92d38fefc764 100644 --- a/tools/public_api_guard/cdk/tree.md +++ b/tools/public_api_guard/cdk/tree.md @@ -127,6 +127,8 @@ export class CdkTreeNode implements FocusableOption, OnDestroy, OnInit // (undocumented) get isExpanded(): boolean; // (undocumented) + get isLeafNode(): boolean; + // (undocumented) get level(): number; static mostRecentTreeNode: CdkTreeNode | null; // (undocumented)