Skip to content

Commit 81294f7

Browse files
crisbetojelbourn
authored andcommitted
feat(sort): add test harness (#17802)
Sets up a test harness that covers `MatSort` and `MatSortHeader`.
1 parent 656c681 commit 81294f7

File tree

13 files changed

+419
-0
lines changed

13 files changed

+419
-0
lines changed

src/material/config.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ entryPoints = [
4040
"snack-bar",
4141
"snack-bar/testing",
4242
"sort",
43+
"sort/testing",
4344
"stepper",
4445
"table",
4546
"tabs",

src/material/sort/sort-header.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ interface MatSortHeaderColumnDef {
7373
templateUrl: 'sort-header.html',
7474
styleUrls: ['sort-header.css'],
7575
host: {
76+
'class': 'mat-sort-header',
7677
'(click)': '_handleClick()',
7778
'(mouseenter)': '_setIndicatorHintVisible(true)',
7879
'(mouseleave)': '_setIndicatorHintVisible(false)',

src/material/sort/sort.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ const _MatSortMixinBase: HasInitializedCtor & CanDisableCtor & typeof MatSortBas
6464
@Directive({
6565
selector: '[matSort]',
6666
exportAs: 'matSort',
67+
host: {'class': 'mat-sort'},
6768
inputs: ['disabled: matSortDisabled']
6869
})
6970
export class MatSort extends _MatSortMixinBase

src/material/sort/testing/BUILD.bazel

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
4+
5+
ts_library(
6+
name = "testing",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
module_name = "@angular/material/sort/testing",
12+
deps = [
13+
"//src/cdk/testing",
14+
"//src/material/sort",
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/sort",
31+
"@npm//@angular/platform-browser",
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/sort",
45+
],
46+
)
47+
48+
ng_web_test_suite(
49+
name = "unit_tests",
50+
deps = [":unit_tests_lib"],
51+
)

src/material/sort/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: 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 './sort-harness';
10+
export * from './sort-header-harness';
11+
export * from './sort-harness-filters';
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import {HarnessLoader} from '@angular/cdk/testing';
2+
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
3+
import {Component} from '@angular/core';
4+
import {ComponentFixture, TestBed} from '@angular/core/testing';
5+
import {MatSortModule, Sort} from '@angular/material/sort';
6+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
7+
import {MatSortHarness} from './sort-harness';
8+
9+
/** Shared tests to run on both the original and MDC-based sort. */
10+
export function runHarnessTests(
11+
sortModule: typeof MatSortModule,
12+
sortHarness: typeof MatSortHarness) {
13+
let fixture: ComponentFixture<SortHarnessTest>;
14+
let loader: HarnessLoader;
15+
16+
beforeEach(async () => {
17+
await TestBed.configureTestingModule({
18+
imports: [sortModule, NoopAnimationsModule],
19+
declarations: [SortHarnessTest],
20+
}).compileComponents();
21+
22+
fixture = TestBed.createComponent(SortHarnessTest);
23+
fixture.detectChanges();
24+
loader = TestbedHarnessEnvironment.loader(fixture);
25+
});
26+
27+
it('should load harness for mat-sort', async () => {
28+
const sorts = await loader.getAllHarnesses(sortHarness);
29+
expect(sorts.length).toBe(1);
30+
});
31+
32+
it('should load the harnesses for all the headers in a mat-sort', async () => {
33+
const sort = await loader.getHarness(sortHarness);
34+
const headers = await sort.getSortHeaders();
35+
expect(headers.length).toBe(5);
36+
});
37+
38+
it('should be able to filter headers by their label text', async () => {
39+
const sort = await loader.getHarness(sortHarness);
40+
const headers = await sort.getSortHeaders({label: 'Carbs'});
41+
expect(headers.length).toBe(1);
42+
expect(await headers[0].getLabel()).toBe('Carbs');
43+
});
44+
45+
it('should be able to filter headers by their labels via a regex', async () => {
46+
const sort = await loader.getHarness(sortHarness);
47+
const headers = await sort.getSortHeaders({label: /^C/});
48+
const labels = await Promise.all(headers.map(header => header.getLabel()));
49+
expect(headers.length).toBe(2);
50+
expect(labels).toEqual(['Calories', 'Carbs']);
51+
});
52+
53+
it('should be able to filter headers by their sorted state', async () => {
54+
const sort = await loader.getHarness(sortHarness);
55+
let headers = await sort.getSortHeaders({sortDirection: ''});
56+
expect(headers.length).toBe(5);
57+
58+
await headers[0].click();
59+
60+
headers = await sort.getSortHeaders({sortDirection: 'asc'});
61+
62+
expect(headers.length).toBe(1);
63+
});
64+
65+
it('should be able to get the label of a header', async () => {
66+
const sort = await loader.getHarness(sortHarness);
67+
const headers = await sort.getSortHeaders();
68+
const labels = await Promise.all(headers.map(header => header.getLabel()));
69+
expect(labels).toEqual(['Dessert', 'Calories', 'Fat', 'Carbs', 'Protein']);
70+
});
71+
72+
it('should be able to get the aria-label of a header', async () => {
73+
const sort = await loader.getHarness(sortHarness);
74+
const headers = await sort.getSortHeaders();
75+
const labels = await Promise.all(headers.map(header => header.getAriaLabel()));
76+
77+
expect(labels).toEqual([
78+
'Change sorting for name',
79+
'Change sorting for calories',
80+
'Change sorting for fat',
81+
'Change sorting for carbs',
82+
'Change sorting for protein'
83+
]);
84+
});
85+
86+
it('should get the disabled state of a header', async () => {
87+
const sort = await loader.getHarness(sortHarness);
88+
const thirdHeader = (await sort.getSortHeaders())[2];
89+
90+
expect(await thirdHeader.isDisabled()).toBe(false);
91+
92+
fixture.componentInstance.disableThirdHeader = true;
93+
fixture.detectChanges();
94+
95+
expect(await thirdHeader.isDisabled()).toBe(true);
96+
});
97+
98+
it('should get the active state of a header', async () => {
99+
const sort = await loader.getHarness(sortHarness);
100+
const secondHeader = (await sort.getSortHeaders())[1];
101+
102+
expect(await secondHeader.isActive()).toBe(false);
103+
104+
await secondHeader.click();
105+
106+
expect(await secondHeader.isActive()).toBe(true);
107+
});
108+
109+
it('should get the sorte direction of a header', async () => {
110+
const sort = await loader.getHarness(sortHarness);
111+
const secondHeader = (await sort.getSortHeaders())[1];
112+
113+
expect(await secondHeader.getSortDirection()).toBe('');
114+
115+
await secondHeader.click();
116+
expect(await secondHeader.getSortDirection()).toBe('asc');
117+
118+
await secondHeader.click();
119+
expect(await secondHeader.getSortDirection()).toBe('desc');
120+
});
121+
122+
it('should get the active header', async () => {
123+
const sort = await loader.getHarness(sortHarness);
124+
const fifthHeader = (await sort.getSortHeaders())[4];
125+
126+
expect(await sort.getActiveHeader()).toBeNull();
127+
128+
await fifthHeader.click();
129+
130+
const activeHeader = await sort.getActiveHeader();
131+
expect(activeHeader).toBeTruthy();
132+
expect(await activeHeader!.getLabel()).toBe('Protein');
133+
});
134+
}
135+
136+
@Component({
137+
template: `
138+
<table matSort (matSortChange)="sortData($event)">
139+
<tr>
140+
<th mat-sort-header="name">Dessert</th>
141+
<th mat-sort-header="calories">Calories</th>
142+
<th mat-sort-header="fat" [disabled]="disableThirdHeader">Fat</th>
143+
<th mat-sort-header="carbs">Carbs</th>
144+
<th mat-sort-header="protein">Protein</th>
145+
</tr>
146+
147+
<tr *ngFor="let dessert of sortedData">
148+
<td>{{dessert.name}}</td>
149+
<td>{{dessert.calories}}</td>
150+
<td>{{dessert.fat}}</td>
151+
<td>{{dessert.carbs}}</td>
152+
<td>{{dessert.protein}}</td>
153+
</tr>
154+
</table>
155+
`
156+
})
157+
class SortHarnessTest {
158+
disableThirdHeader = false;
159+
desserts = [
160+
{name: 'Frozen yogurt', calories: 159, fat: 6, carbs: 24, protein: 4},
161+
{name: 'Ice cream sandwich', calories: 237, fat: 9, carbs: 37, protein: 4},
162+
{name: 'Eclair', calories: 262, fat: 16, carbs: 24, protein: 6},
163+
{name: 'Cupcake', calories: 305, fat: 4, carbs: 67, protein: 4},
164+
{name: 'Gingerbread', calories: 356, fat: 16, carbs: 49, protein: 4},
165+
];
166+
167+
sortedData = this.desserts.slice();
168+
169+
sortData(sort: Sort) {
170+
const data = this.desserts.slice();
171+
172+
if (!sort.active || sort.direction === '') {
173+
this.sortedData = data;
174+
} else {
175+
this.sortedData = data.sort((a, b) => {
176+
const aValue = (a as any)[sort.active];
177+
const bValue = (b as any)[sort.active];
178+
return (aValue < bValue ? -1 : 1) * (sort.direction === 'asc' ? 1 : -1);
179+
});
180+
}
181+
}
182+
}
183+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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+
import {BaseHarnessFilters} from '@angular/cdk/testing';
9+
import {SortDirection} from '@angular/material/sort';
10+
11+
export interface SortHarnessFilters extends BaseHarnessFilters {
12+
}
13+
14+
export interface SortHeaderHarnessFilters extends BaseHarnessFilters {
15+
label?: string | RegExp;
16+
sortDirection?: SortDirection;
17+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {MatSortModule} from '@angular/material/sort';
2+
import {runHarnessTests} from '@angular/material/sort/testing/shared.spec';
3+
import {MatSortHarness} from './sort-harness';
4+
5+
describe('Non-MDC-based MatSortHarness', () => {
6+
runHarnessTests(MatSortModule, MatSortHarness);
7+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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 {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
10+
import {SortHarnessFilters, SortHeaderHarnessFilters} from './sort-harness-filters';
11+
import {MatSortHeaderHarness} from './sort-header-harness';
12+
13+
/** Harness for interacting with a standard `mat-sort` in tests. */
14+
export class MatSortHarness extends ComponentHarness {
15+
static hostSelector = '.mat-sort';
16+
17+
/**
18+
* Gets a `HarnessPredicate` that can be used to search for a `mat-sort` with specific attributes.
19+
* @param options Options for narrowing the search.
20+
* @return a `HarnessPredicate` configured with the given options.
21+
*/
22+
static with(options: SortHarnessFilters = {}): HarnessPredicate<MatSortHarness> {
23+
return new HarnessPredicate(MatSortHarness, options);
24+
}
25+
26+
/** Gets all of the sort headers in the `mat-sort`. */
27+
async getSortHeaders(filter: SortHeaderHarnessFilters = {}): Promise<MatSortHeaderHarness[]> {
28+
return this.locatorForAll(MatSortHeaderHarness.with(filter))();
29+
}
30+
31+
/** Gets the selected header in the `mat-sort`. */
32+
async getActiveHeader(): Promise<MatSortHeaderHarness|null> {
33+
const headers = await this.getSortHeaders();
34+
for (let i = 0; i < headers.length; i++) {
35+
if (await headers[i].isActive()) {
36+
return headers[i];
37+
}
38+
}
39+
return null;
40+
}
41+
}

0 commit comments

Comments
 (0)