Skip to content

Commit 287834a

Browse files
authored
feat(material-experimental/mdc-slider): add test harnesses (#22648)
Adds test harnesses for the MDC-based slider components.
1 parent 38baf24 commit 287834a

File tree

6 files changed

+392
-15
lines changed

6 files changed

+392
-15
lines changed

src/material-experimental/mdc-slider/testing/BUILD.bazel

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@ ts_library(
1212
deps = [
1313
"//src/cdk/coercion",
1414
"//src/cdk/testing",
15-
"//src/material/slider/testing",
16-
"@npm//rxjs",
17-
"@npm//zone.js",
15+
"//src/material-experimental/mdc-slider",
1816
],
1917
)
2018

@@ -23,16 +21,16 @@ ng_test_library(
2321
srcs = glob(["**/*.spec.ts"]),
2422
deps = [
2523
":testing",
24+
"//src/cdk/testing",
25+
"//src/cdk/testing/testbed",
2626
"//src/material-experimental/mdc-slider",
2727
"//src/material/slider/testing:harness_tests_lib",
2828
],
2929
)
3030

3131
ng_web_test_suite(
3232
name = "unit_tests",
33-
static_files = [
34-
"@npm//:node_modules/@material/slider/dist/mdc.slider.js",
35-
],
33+
static_files = ["@npm//:node_modules/@material/slider/dist/mdc.slider.js"],
3634
deps = [
3735
":unit_tests_lib",
3836
"//src/material-experimental:mdc_require_config.js",

src/material-experimental/mdc-slider/testing/public-api.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
export {SliderHarnessFilters} from '@angular/material/slider/testing';
10-
export {MatSliderHarness} from './slider-harness';
9+
export * from './slider-harness';
10+
export * from './slider-thumb-harness';
11+
export * from './slider-harness-filters';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
10+
/** Possible positions of a slider thumb. */
11+
export const enum ThumbPosition {
12+
START,
13+
END
14+
}
15+
16+
/** A set of criteria that can be used to filter a list of `MatSliderHarness` instances. */
17+
export interface SliderHarnessFilters extends BaseHarnessFilters {
18+
/** Filters out only range/non-range sliders. */
19+
isRange?: boolean;
20+
}
21+
22+
/** A set of criteria that can be used to filter a list of `MatSliderThumbHarness` instances. */
23+
export interface SliderThumbHarnessFilters extends BaseHarnessFilters {
24+
/** Filters out slider thumbs with a particular position. */
25+
position?: ThumbPosition;
26+
}

src/material-experimental/mdc-slider/testing/slider-harness.spec.ts

Lines changed: 183 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,189 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
/* tslint:disable-next-line:no-unused-variable */
10-
import {MatSlider} from '../index';
11-
12-
// TODO(wagnermaciel): Implement this in a separate PR
9+
import {Component} from '@angular/core';
10+
import {ComponentFixture, TestBed} from '@angular/core/testing';
11+
import {HarnessLoader, parallel} from '@angular/cdk/testing';
12+
import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
13+
import {MatSliderModule} from '@angular/material-experimental/mdc-slider';
14+
import {MatSliderHarness} from './slider-harness';
15+
import {ThumbPosition} from './slider-harness-filters';
1316

1417
describe('MDC-based MatSliderHarness', () => {
15-
it('does nothing yet', () => {});
18+
let fixture: ComponentFixture<SliderHarnessTest>;
19+
let loader: HarnessLoader;
20+
21+
beforeEach(async () => {
22+
await TestBed.configureTestingModule({
23+
imports: [MatSliderModule],
24+
declarations: [SliderHarnessTest],
25+
}).compileComponents();
26+
27+
fixture = TestBed.createComponent(SliderHarnessTest);
28+
fixture.detectChanges();
29+
loader = TestbedHarnessEnvironment.loader(fixture);
30+
});
31+
32+
it('should load all slider harnesses', async () => {
33+
const sliders = await loader.getAllHarnesses(MatSliderHarness);
34+
expect(sliders.length).toBe(2);
35+
});
36+
37+
it('should get whether is a range slider', async () => {
38+
const sliders = await loader.getAllHarnesses(MatSliderHarness);
39+
expect(await parallel(() => sliders.map(slider => slider.isRange()))).toEqual([false, true]);
40+
});
41+
42+
it('should get whether a slider is disabled', async () => {
43+
const slider = await loader.getHarness(MatSliderHarness);
44+
expect(await slider.isDisabled()).toBe(false);
45+
fixture.componentInstance.singleSliderDisabled = true;
46+
expect(await slider.isDisabled()).toBe(true);
47+
});
48+
49+
it('should get the min/max values of a single-thumb slider', async () => {
50+
const slider = await loader.getHarness(MatSliderHarness);
51+
const [min, max] = await parallel(() => [slider.getMinValue(), slider.getMaxValue()]);
52+
expect(min).toBe(0);
53+
expect(max).toBe(100);
54+
});
55+
56+
it('should get the min/max values of a range slider', async () => {
57+
const slider = await loader.getHarness(MatSliderHarness.with({isRange: true}));
58+
const [min, max] = await parallel(() => [slider.getMinValue(), slider.getMaxValue()]);
59+
expect(min).toBe(fixture.componentInstance.rangeSliderMin);
60+
expect(max).toBe(fixture.componentInstance.rangeSliderMax);
61+
});
62+
63+
it('should get the thumbs within a slider', async () => {
64+
const sliders = await loader.getAllHarnesses(MatSliderHarness);
65+
expect(await sliders[0].getStartThumb()).toBeTruthy();
66+
expect(await sliders[1].getStartThumb()).toBeTruthy();
67+
expect(await sliders[1].getEndThumb()).toBeTruthy();
68+
});
69+
70+
it('should get the step of a slider', async () => {
71+
const sliders = await loader.getAllHarnesses(MatSliderHarness);
72+
expect(await parallel(() => {
73+
return sliders.map(slider => slider.getStep());
74+
})).toEqual([1, fixture.componentInstance.rangeSliderStep]);
75+
});
76+
77+
it('should get the position of a slider thumb', async () => {
78+
const slider = await loader.getHarness(MatSliderHarness.with({selector: '#range'}));
79+
const [start, end] = await parallel(() => [slider.getStartThumb(), slider.getEndThumb()]);
80+
expect(await start.getPosition()).toBe(ThumbPosition.START);
81+
expect(await end.getPosition()).toBe(ThumbPosition.END);
82+
});
83+
84+
it('should get and set the value of a slider thumb', async () => {
85+
const slider = await loader.getHarness(MatSliderHarness);
86+
const thumb = await slider.getStartThumb();
87+
expect(await thumb.getValue()).toBe(0);
88+
await thumb.setValue(73);
89+
expect(await thumb.getValue()).toBe(73);
90+
});
91+
92+
it('should dispatch input and change events when setting the value', async () => {
93+
const slider = await loader.getHarness(MatSliderHarness);
94+
const thumb = await slider.getStartThumb();
95+
const changeSpy = spyOn(fixture.componentInstance, 'changeListener');
96+
const inputSpy = spyOn(fixture.componentInstance, 'inputListener');
97+
await thumb.setValue(73);
98+
expect(changeSpy).toHaveBeenCalledTimes(1);
99+
expect(inputSpy).toHaveBeenCalledTimes(1);
100+
expect(await thumb.getValue()).toBe(73);
101+
});
102+
103+
it('should get the value of a thumb as a percentage', async () => {
104+
const sliders = await loader.getAllHarnesses(MatSliderHarness);
105+
expect(await (await sliders[0].getStartThumb()).getPercentage()).toBe(0);
106+
expect(await (await sliders[1].getStartThumb()).getPercentage()).toBe(0.4);
107+
expect(await (await sliders[1].getEndThumb()).getPercentage()).toBe(0.5);
108+
});
109+
110+
it('should get the display value of a slider thumb', async () => {
111+
const slider = await loader.getHarness(MatSliderHarness);
112+
const thumb = await slider.getStartThumb();
113+
fixture.componentInstance.displayFn = value => `#${value}`;
114+
await thumb.setValue(73);
115+
expect(await thumb.getDisplayValue()).toBe('#73');
116+
});
117+
118+
it('should get the min/max values of a slider thumb', async () => {
119+
const instance = fixture.componentInstance;
120+
const slider = await loader.getHarness(MatSliderHarness.with({selector: '#range'}));
121+
const [start, end] = await parallel(() => [slider.getStartThumb(), slider.getEndThumb()]);
122+
123+
expect(await start.getMinValue()).toBe(instance.rangeSliderMin);
124+
expect(await start.getMaxValue()).toBe(instance.rangeSliderEndValue);
125+
expect(await end.getMinValue()).toBe(instance.rangeSliderStartValue);
126+
expect(await end.getMaxValue()).toBe(instance.rangeSliderMax);
127+
});
128+
129+
it('should get the disabled state of a slider thumb', async () => {
130+
const slider = await loader.getHarness(MatSliderHarness);
131+
const thumb = await slider.getStartThumb();
132+
133+
expect(await thumb.isDisabled()).toBe(false);
134+
fixture.componentInstance.singleSliderDisabled = true;
135+
expect(await thumb.isDisabled()).toBe(true);
136+
});
137+
138+
it('should get the name of a slider thumb', async () => {
139+
const slider = await loader.getHarness(MatSliderHarness);
140+
expect(await (await slider.getStartThumb()).getName()).toBe('price');
141+
});
142+
143+
it('should get the id of a slider thumb', async () => {
144+
const slider = await loader.getHarness(MatSliderHarness);
145+
expect(await (await slider.getStartThumb()).getId()).toBe('price-input');
146+
});
147+
148+
it('should get whether a slider thumb is required', async () => {
149+
const slider = await loader.getHarness(MatSliderHarness);
150+
expect(await (await slider.getStartThumb()).isRequired()).toBe(true);
151+
});
152+
153+
it('should be able to focus and blur a slider thumb', async () => {
154+
const slider = await loader.getHarness(MatSliderHarness);
155+
const thumb = await slider.getStartThumb();
156+
157+
expect(await thumb.isFocused()).toBe(false);
158+
await thumb.focus();
159+
expect(await thumb.isFocused()).toBe(true);
160+
await thumb.blur();
161+
expect(await thumb.isFocused()).toBe(false);
162+
});
163+
16164
});
165+
166+
@Component({
167+
template: `
168+
<mat-slider id="single" [displayWith]="displayFn" [disabled]="singleSliderDisabled">
169+
<input
170+
name="price"
171+
id="price-input"
172+
required
173+
matSliderThumb
174+
(input)="inputListener()"
175+
(change)="changeListener()">
176+
</mat-slider>
177+
178+
<mat-slider id="range" [min]="rangeSliderMin" [max]="rangeSliderMax" [step]="rangeSliderStep">
179+
<input [value]="rangeSliderStartValue" matSliderStartThumb>
180+
<input [value]="rangeSliderEndValue" matSliderEndThumb>
181+
</mat-slider>
182+
`,
183+
})
184+
class SliderHarnessTest {
185+
singleSliderDisabled = false;
186+
rangeSliderMin = 100;
187+
rangeSliderMax = 500;
188+
rangeSliderStep = 50;
189+
rangeSliderStartValue = 200;
190+
rangeSliderEndValue = 350;
191+
displayFn = (value: number) => value + '';
192+
inputListener() {}
193+
changeListener() {}
194+
}

src/material-experimental/mdc-slider/testing/slider-harness.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,63 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ComponentHarness} from '@angular/cdk/testing';
9+
import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
10+
import {coerceNumberProperty} from '@angular/cdk/coercion';
11+
import {SliderHarnessFilters, ThumbPosition} from './slider-harness-filters';
12+
import {MatSliderThumbHarness} from './slider-thumb-harness';
1013

1114
/** Harness for interacting with a MDC mat-slider in tests. */
1215
export class MatSliderHarness extends ComponentHarness {
13-
// TODO(wagnermaciel): Implement this in a separate PR
16+
static hostSelector = '.mat-mdc-slider';
17+
18+
/**
19+
* Gets a `HarnessPredicate` that can be used to search for a `MatSliderHarness` that meets
20+
* certain criteria.
21+
* @param options Options for filtering which input instances are considered a match.
22+
* @return a `HarnessPredicate` configured with the given options.
23+
*/
24+
static with(options: SliderHarnessFilters = {}): HarnessPredicate<MatSliderHarness> {
25+
return new HarnessPredicate(MatSliderHarness, options)
26+
.addOption('isRange', options.isRange, async (harness, value) => {
27+
return (await harness.isRange()) === value;
28+
});
29+
}
30+
31+
/** Gets the start/primary thumb of the slider. */
32+
async getStartThumb(): Promise<MatSliderThumbHarness> {
33+
return this.locatorFor(MatSliderThumbHarness.with({position: ThumbPosition.START}))();
34+
}
35+
36+
/** Gets the end thumb of the slider. Will throw an error for a non-range slider. */
37+
async getEndThumb(): Promise<MatSliderThumbHarness> {
38+
return this.locatorFor(MatSliderThumbHarness.with({position: ThumbPosition.END}))();
39+
}
40+
41+
/** Gets whether the slider is a range slider. */
42+
async isRange(): Promise<boolean> {
43+
return (await (await this.host()).hasClass('mdc-slider--range'));
44+
}
45+
46+
/** Gets whether the slider is disabled. */
47+
async isDisabled(): Promise<boolean> {
48+
return (await (await this.host()).hasClass('mdc-slider--disabled'));
49+
}
50+
51+
/** Gets the value step increments of the slider. */
52+
async getStep(): Promise<number> {
53+
// The same step value is forwarded to both thumbs.
54+
const startHost = await (await this.getStartThumb()).host();
55+
return coerceNumberProperty(await startHost.getProperty('step'));
56+
}
57+
58+
/** Gets the maximum value of the slider. */
59+
async getMaxValue(): Promise<number> {
60+
const endThumb = await this.isRange() ? await this.getEndThumb() : await this.getStartThumb();
61+
return endThumb.getMaxValue();
62+
}
63+
64+
/** Gets the minimum value of the slider. */
65+
async getMinValue(): Promise<number> {
66+
return (await this.getStartThumb()).getMinValue();
67+
}
1468
}

0 commit comments

Comments
 (0)