Skip to content

Commit 5dbde07

Browse files
committed
set up basic tree test harness functionality
1 parent 49de56c commit 5dbde07

File tree

9 files changed

+429
-0
lines changed

9 files changed

+429
-0
lines changed

src/material/config.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ entryPoints = [
6161
"tooltip",
6262
"tooltip/testing",
6363
"tree",
64+
"tree/testing",
6465
"form-field/testing",
6566
"form-field/testing/control",
6667
"input/testing",

src/material/tree/testing/BUILD.bazel

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ts_library(
6+
name = "testing",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
module_name = "@angular/material/tree/testing",
12+
deps = [
13+
"//src/cdk/testing",
14+
"//src/cdk/coercion",
15+
],
16+
)
17+
18+
filegroup(
19+
name = "source-files",
20+
srcs = glob(["**/*.ts"]),
21+
)
22+
23+
ng_test_library(
24+
name = "harness_tests_lib",
25+
srcs = ["shared.spec.ts"],
26+
deps = [
27+
":testing",
28+
"//src/cdk/testing",
29+
"//src/cdk/testing/testbed",
30+
"//src/material/tree",
31+
"//src/cdk/tree",
32+
],
33+
)
34+
35+
ng_test_library(
36+
name = "unit_tests_lib",
37+
srcs = glob(
38+
["**/*.spec.ts"],
39+
exclude = ["shared.spec.ts"],
40+
),
41+
deps = [
42+
":harness_tests_lib",
43+
":testing",
44+
"//src/material/tree",
45+
],
46+
)
47+
48+
ng_web_test_suite(
49+
name = "unit_tests",
50+
deps = [":unit_tests_lib"],
51+
)

src/material/tree/testing/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export * from './public-api';
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {
10+
ComponentHarness,
11+
ComponentHarnessConstructor,
12+
HarnessPredicate
13+
} from '@angular/cdk/testing';
14+
import {TreeNodeHarnessFilters} from './tree-harness-filters';
15+
import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion';
16+
17+
export class MatTreeNodeHarness extends ComponentHarness {
18+
static hostSelector = '.mat-tree-node';
19+
20+
_toggle = this.locatorForOptional('mattreenodetoggle');
21+
22+
static with(options: TreeNodeHarnessFilters = {}): HarnessPredicate<MatTreeNodeHarness> {
23+
return getNodePredicate(MatTreeNodeHarness, options);
24+
}
25+
26+
async isExpanded(): Promise<boolean> {
27+
return coerceBooleanProperty(await (await this.host()).getAttribute('aria-expanded'));
28+
}
29+
30+
async isDisabled(): Promise<boolean> {
31+
return coerceBooleanProperty(await (await this.host()).getProperty('aria-disabled'));
32+
}
33+
34+
async getLevel(): Promise<number> {
35+
return coerceNumberProperty(await (await this.host()).getAttribute('aria-level'));
36+
}
37+
38+
async getRole(): Promise<string|null> {
39+
return (await this.host()).getAttribute('role');
40+
}
41+
42+
async getText(): Promise<string> {
43+
return (await this.host()).text();
44+
}
45+
46+
async toggleExpansion(): Promise<void> {
47+
const toggle = await this._toggle();
48+
if (toggle) {
49+
return toggle.click();
50+
}
51+
}
52+
}
53+
54+
export class MatNestedTreeNodeHarness extends MatTreeNodeHarness {
55+
static hostSelector = '.mat-nested-tree-node';
56+
57+
static with(options: TreeNodeHarnessFilters = {}): HarnessPredicate<MatNestedTreeNodeHarness> {
58+
return getNodePredicate(MatNestedTreeNodeHarness, options);
59+
}
60+
}
61+
62+
63+
function getNodePredicate<T extends MatTreeNodeHarness>(
64+
type: ComponentHarnessConstructor<T>,
65+
options: TreeNodeHarnessFilters): HarnessPredicate<T> {
66+
return new HarnessPredicate(type, options)
67+
.addOption('text', options.text,
68+
(harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))
69+
.addOption(
70+
'disabled', options.disabled,
71+
async (harness, disabled) => (await harness.isDisabled()) === disabled)
72+
.addOption(
73+
'expanded', options.expanded,
74+
async (harness, expanded) => (await harness.isExpanded()) === expanded)
75+
.addOption(
76+
'level', options.level,
77+
async (harness, level) => (await harness.getLevel()) === level)
78+
.addOption(
79+
'role', options.role,
80+
async (harness, role) => (await harness.getRole()) === role);
81+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export * from './node-harness';
10+
export * from './tree-harness';
11+
export * from './tree-harness-filters';
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import {Component} from '@angular/core';
2+
import {FlatTreeControl, NestedTreeControl} from '@angular/cdk/tree';
3+
import {
4+
MatTreeFlatDataSource,
5+
MatTreeFlattener,
6+
MatTreeModule,
7+
MatTreeNestedDataSource
8+
} from '@angular/material/tree';
9+
import {MatTreeHarness} from '@angular/material/tree/testing/tree-harness';
10+
import {ComponentFixture, TestBed} from '@angular/core/testing';
11+
import {HarnessLoader} from '@angular/cdk/testing';
12+
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
13+
14+
/** Shared tests to run on both the original and MDC-based trees. */
15+
export function runHarnessTests(
16+
treeModule: typeof MatTreeModule,
17+
treeHarness: typeof MatTreeHarness) {
18+
let fixture: ComponentFixture<TreeHarnessTest>;
19+
let loader: HarnessLoader;
20+
21+
beforeEach(async () => {
22+
await TestBed.configureTestingModule({
23+
imports: [treeModule],
24+
declarations: [TreeHarnessTest],
25+
}).compileComponents();
26+
27+
fixture = TestBed.createComponent(TreeHarnessTest);
28+
fixture.detectChanges();
29+
loader = TestbedHarnessEnvironment.loader(fixture);
30+
});
31+
32+
it('should load harness with 2 tress', async () => {
33+
const trees = await loader.getAllHarnesses(treeHarness);
34+
35+
expect(trees.length).toBe(2);
36+
});
37+
38+
it('should get the different type of tree nodes', async () => {
39+
const trees = await loader.getAllHarnesses(treeHarness);
40+
const flatTree = trees[0];
41+
const nestedTree = trees[1];
42+
const flatTreeFlatNodes = await flatTree.getNodes();
43+
const flatTreeNestedNodes = await flatTree.getNestedNodes();
44+
const nestedTreeFlatNodes = await nestedTree.getNodes();
45+
const nestedTreeNestedNodes = await nestedTree.getNestedNodes();
46+
47+
expect(flatTreeFlatNodes.length).toBe(2);
48+
expect(flatTreeNestedNodes.length).toBe(0);
49+
expect(nestedTreeFlatNodes.length).toBe(5);
50+
expect(nestedTreeNestedNodes.length).toBe(3);
51+
});
52+
53+
it('should correctly get properties of node', async () => {
54+
const trees = await loader.getAllHarnesses(treeHarness);
55+
const flatTree = trees[0];
56+
const flatTreeNodes = await flatTree.getNodes();
57+
const secondGroup = flatTreeNodes[1];
58+
59+
expect(await secondGroup.getRole()).toBe('group');
60+
expect(await secondGroup.getText()).toBe('Toggle Flat Group 2');
61+
expect(await secondGroup.getLevel()).toBe(0);
62+
expect(await secondGroup.isDisabled()).toBe(false);
63+
expect(await secondGroup.isExpanded()).toBe(false);
64+
});
65+
66+
it('should toggle expansion', async () => {
67+
const trees = await loader.getAllHarnesses(treeHarness);
68+
const nestedTree = trees[1];
69+
const nestedTreeNodes = await nestedTree.getNodes();
70+
const firstGroup = nestedTreeNodes[1];
71+
72+
expect(await firstGroup.isExpanded()).toBe(false);
73+
74+
await firstGroup.toggleExpansion();
75+
76+
expect(await firstGroup.isExpanded()).toBe(true);
77+
78+
});
79+
}
80+
81+
82+
interface FoodNode {
83+
name: string;
84+
children?: FoodNode[];
85+
}
86+
87+
const FLAT_TREE_DATA: FoodNode[] = [
88+
{
89+
name: 'Flat Group 1',
90+
children: [
91+
{name: 'Flat Leaf 1.1'},
92+
{name: 'Flat Leaf 1.2'},
93+
{name: 'Flat Leaf 1.3'},
94+
]
95+
}, {
96+
name: 'Flat Group 2',
97+
children: [
98+
{
99+
name: 'Flat Group 2.1',
100+
children: [
101+
{name: 'Flat Leaf 2.1.1'},
102+
{name: 'Flat Leaf 2.1.2'},
103+
]
104+
}, {
105+
name: 'Flat Group 2.2',
106+
children: [
107+
{name: 'Flat Leaf 2.2.1'},
108+
{name: 'Flat Leaf 2.2.2'},
109+
]
110+
},
111+
]
112+
},
113+
];
114+
115+
const NESTED_TREE_DATA: FoodNode[] = [
116+
{
117+
name: 'Nested Group 1',
118+
children: [
119+
{name: 'Nested Leaf 1.1'},
120+
{name: 'Nested Leaf 1.2'},
121+
{name: 'Nested Leaf 1.3'},
122+
]
123+
}, {
124+
name: 'Nested Group 2',
125+
children: [
126+
{
127+
name: 'Nested Group 2.1',
128+
children: [
129+
{name: 'Nested Leaf 2.1.1'},
130+
{name: 'Nested Leaf 2.1.2'},
131+
]
132+
},
133+
]
134+
},
135+
];
136+
137+
interface ExampleFlatNode {
138+
expandable: boolean;
139+
name: string;
140+
level: number;
141+
}
142+
143+
@Component({
144+
template: `
145+
<mat-tree [dataSource]="flatTreeDataSource" [treeControl]="flatTreeControl">
146+
<!-- This is the tree node template for leaf nodes -->
147+
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding>
148+
{{node.name}}
149+
</mat-tree-node>
150+
<!-- This is the tree node template for expandable nodes -->
151+
<mat-tree-node *matTreeNodeDef="let node;when: flatTreeHasChild" matTreeNodePadding>
152+
<button matTreeNodeToggle
153+
[attr.aria-label]="'toggle ' + node.name">
154+
Toggle
155+
</button>
156+
{{node.name}}
157+
</mat-tree-node>
158+
</mat-tree>
159+
160+
<mat-tree [dataSource]="nestedTreeDataSource" [treeControl]="nestedTreeControl">
161+
<!-- This is the tree node template for leaf nodes -->
162+
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle>
163+
{{node.name}}
164+
</mat-tree-node>
165+
<!-- This is the tree node template for expandable nodes -->
166+
<mat-nested-tree-node *matTreeNodeDef="let node; when: nestedTreeHasChild">
167+
<button matTreeNodeToggle
168+
[attr.aria-label]="'toggle ' + node.name">
169+
Toggle
170+
</button>
171+
{{node.name}}
172+
<ul [class.example-tree-invisible]="!nestedTreeControl.isExpanded(node)">
173+
<ng-container matTreeNodeOutlet></ng-container>
174+
</ul>
175+
</mat-nested-tree-node>
176+
</mat-tree>
177+
`
178+
})
179+
class TreeHarnessTest {
180+
flatTreeControl = new FlatTreeControl<ExampleFlatNode>(
181+
node => node.level, node => node.expandable);
182+
flatTreeDataSource = new MatTreeFlatDataSource(this.flatTreeControl, this.treeFlattener);
183+
nestedTreeControl = new NestedTreeControl<FoodNode>(node => node.children);
184+
nestedTreeDataSource = new MatTreeNestedDataSource<FoodNode>();
185+
186+
constructor() {
187+
this.flatTreeDataSource.data = FLAT_TREE_DATA;
188+
this.nestedTreeDataSource.data = NESTED_TREE_DATA;
189+
}
190+
191+
flatTreeHasChild = (_: number, node: ExampleFlatNode) => node.expandable;
192+
193+
nestedTreeHasChild = (_: number, node: FoodNode) => !!node.children && node.children.length > 0;
194+
195+
private _transformer = (node: FoodNode, level: number) => {
196+
return {
197+
expandable: !!node.children && node.children.length > 0,
198+
name: node.name,
199+
level: level,
200+
};
201+
};
202+
203+
treeFlattener = new MatTreeFlattener(
204+
this._transformer, node => node.level, node => node.expandable, node => node.children);
205+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {BaseHarnessFilters} from '@angular/cdk/testing';
10+
11+
export interface TreeHarnessFilters extends BaseHarnessFilters {
12+
}
13+
14+
export interface TreeNodeHarnessFilters extends BaseHarnessFilters {
15+
/** Only find instances whose text matches the given value. */
16+
text?: string | RegExp;
17+
/** Only find instances whose state matches the given value. */
18+
disabled?: boolean;
19+
/** Only find instances whose expansion state matches the given value. */
20+
expanded?: boolean;
21+
/** Only find instances whose role matches the given value. */
22+
role?: 'treeitem'|'group';
23+
/** Only find instances whose level matches the given value. */
24+
level?: number;
25+
}

0 commit comments

Comments
 (0)