Skip to content

Commit 6bd79d5

Browse files
committed
feat(material-experimental): add test harness for slider
Resolves COMP-191.
1 parent 8e321ae commit 6bd79d5

File tree

7 files changed

+287
-1
lines changed

7 files changed

+287
-1
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@
9797
# Note to implementer: please repossess
9898
/src/material-experimental/mdc-radio/** @mmalerba
9999
/src/material-experimental/mdc-slide-toggle/** @crisbeto
100+
# Note to implementer: please repossess
101+
/src/material-experimental/mdc-slider/** @devversion
100102
/src/material-experimental/mdc-tabs/** @crisbeto
101103
/src/material-experimental/mdc-theming/** @mmalerba
102104
/src/material-experimental/mdc-typography/** @mmalerba

WORKSPACE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,4 @@ rbe_autoconfig(
107107
# We can't use the default "ubuntu16_04" RBE image provided by the autoconfig because we need
108108
# a specific Linux kernel that comes with "libx11" in order to run headless browser tests.
109109
repository = "google/rbe-ubuntu16-04-webtest",
110-
)
110+
)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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 = "harness",
7+
srcs = glob(
8+
["**/*.ts"],
9+
exclude = ["**/*.spec.ts"],
10+
),
11+
deps = [
12+
"//src/cdk-experimental/testing",
13+
"//src/cdk/coercion",
14+
],
15+
)
16+
17+
ng_test_library(
18+
name = "harness_tests",
19+
srcs = glob(["**/*.spec.ts"]),
20+
deps = [
21+
":harness",
22+
"//src/cdk-experimental/testing",
23+
"//src/cdk-experimental/testing/testbed",
24+
"//src/material/slider",
25+
],
26+
)
27+
28+
ng_web_test_suite(
29+
name = "tests",
30+
deps = [":harness_tests"],
31+
)
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 type SliderHarnessFilters = {
10+
id?: string;
11+
};
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import {HarnessLoader} from '@angular/cdk-experimental/testing';
2+
import {TestbedHarnessEnvironment} from '@angular/cdk-experimental/testing/testbed';
3+
import {Component} from '@angular/core';
4+
import {ComponentFixture, TestBed} from '@angular/core/testing';
5+
import {ReactiveFormsModule} from '@angular/forms';
6+
import {MatSliderModule} from '@angular/material/slider';
7+
import {MatSliderHarness} from './slider-harness';
8+
9+
let fixture: ComponentFixture<SliderHarnessTest>;
10+
let loader: HarnessLoader;
11+
let sliderHarness: typeof MatSliderHarness;
12+
13+
describe('MatSliderHarness', () => {
14+
describe('non-MDC-based', () => {
15+
beforeEach(async () => {
16+
await TestBed
17+
.configureTestingModule({
18+
imports: [MatSliderModule, ReactiveFormsModule],
19+
declarations: [SliderHarnessTest],
20+
})
21+
.compileComponents();
22+
23+
fixture = TestBed.createComponent(SliderHarnessTest);
24+
fixture.detectChanges();
25+
loader = TestbedHarnessEnvironment.loader(fixture);
26+
sliderHarness = MatSliderHarness;
27+
});
28+
29+
runTests();
30+
});
31+
32+
describe(
33+
'MDC-based',
34+
() => {
35+
// TODO: run tests for MDC based slider once implemented.
36+
});
37+
});
38+
39+
/** Shared tests to run on both the original and MDC-based sliders. */
40+
function runTests() {
41+
it('should load all slider harnesses', async () => {
42+
const sliders = await loader.getAllHarnesses(sliderHarness);
43+
expect(sliders.length).toBe(3);
44+
});
45+
46+
it('should load slider harness by id', async () => {
47+
const sliders = await loader.getAllHarnesses(sliderHarness.with({id: 'my-slider'}));
48+
expect(sliders.length).toBe(1);
49+
});
50+
51+
it('should get id of slider', async () => {
52+
const sliders = await loader.getAllHarnesses(sliderHarness);
53+
expect(await sliders[0].getId()).toBe(null);
54+
expect(await sliders[1].getId()).toBe('my-slider');
55+
expect(await sliders[2].getId()).toBe(null);
56+
});
57+
58+
it('should get value of slider', async () => {
59+
const sliders = await loader.getAllHarnesses(sliderHarness);
60+
expect(await sliders[0].getValue()).toBe(50);
61+
expect(await sliders[1].getValue()).toBe(0);
62+
expect(await sliders[2].getValue()).toBe(225);
63+
});
64+
65+
it('should get percentage of slider', async () => {
66+
const sliders = await loader.getAllHarnesses(sliderHarness);
67+
expect(await sliders[0].getPercentage()).toBe(0.5);
68+
expect(await sliders[1].getPercentage()).toBe(0);
69+
expect(await sliders[2].getPercentage()).toBe(0.5);
70+
});
71+
72+
it('should get max value of slider', async () => {
73+
const sliders = await loader.getAllHarnesses(sliderHarness);
74+
expect(await sliders[0].getMaxValue()).toBe(100);
75+
expect(await sliders[1].getMaxValue()).toBe(100);
76+
expect(await sliders[2].getMaxValue()).toBe(250);
77+
});
78+
79+
it('should get min value of slider', async () => {
80+
const sliders = await loader.getAllHarnesses(sliderHarness);
81+
expect(await sliders[0].getMinValue()).toBe(0);
82+
expect(await sliders[1].getMinValue()).toBe(0);
83+
expect(await sliders[2].getMinValue()).toBe(200);
84+
});
85+
86+
it('should get display value of slider', async () => {
87+
const sliders = await loader.getAllHarnesses(sliderHarness);
88+
expect(await sliders[0].getDisplayValue()).toBe('50');
89+
expect(await sliders[1].getDisplayValue()).toBe('Null');
90+
expect(await sliders[2].getDisplayValue()).toBe('#225');
91+
});
92+
93+
it('should get orientation of slider', async () => {
94+
const sliders = await loader.getAllHarnesses(sliderHarness);
95+
expect(await sliders[0].getOrientation()).toBe('horizontal');
96+
expect(await sliders[1].getOrientation()).toBe('horizontal');
97+
expect(await sliders[2].getOrientation()).toBe('vertical');
98+
});
99+
100+
it('should be able to focus slider', async () => {
101+
const [slider] = await loader.getAllHarnesses(sliderHarness);
102+
expect(getActiveElementTagName()).not.toBe('mat-slider');
103+
await slider.focus();
104+
expect(getActiveElementTagName()).toBe('mat-slider');
105+
});
106+
107+
it('should be able to blur slider', async () => {
108+
const [slider] = await loader.getAllHarnesses(sliderHarness);
109+
expect(getActiveElementTagName()).not.toBe('mat-slider');
110+
await slider.focus();
111+
expect(getActiveElementTagName()).toBe('mat-slider');
112+
await slider.blur();
113+
expect(getActiveElementTagName()).not.toBe('mat-slider');
114+
});
115+
116+
it('should get disabled state of slider', async () => {
117+
const sliders = await loader.getAllHarnesses(sliderHarness);
118+
expect(await sliders[0].isDisabled()).toBe(true);
119+
expect(await sliders[1].isDisabled()).toBe(false);
120+
expect(await sliders[2].isDisabled()).toBe(false);
121+
});
122+
}
123+
124+
function getActiveElementTagName() {
125+
return document.activeElement ? document.activeElement.tagName.toLowerCase() : '';
126+
}
127+
128+
@Component({
129+
template: `
130+
<mat-slider value="50" disabled></mat-slider>
131+
<mat-slider [id]="sliderId" [displayWith]="displayFn"></mat-slider>
132+
<mat-slider min="200" max="250" value="225" [displayWith]="displayFn" vertical></mat-slider>
133+
`
134+
})
135+
class SliderHarnessTest {
136+
sliderId = 'my-slider';
137+
138+
displayFn(value: number|null) {
139+
if (!value) {
140+
return 'Null';
141+
}
142+
return `#${value}`;
143+
}
144+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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-experimental/testing';
10+
import {coerceBooleanProperty, coerceNumberProperty} from '@angular/cdk/coercion';
11+
import {SliderHarnessFilters} from './slider-harness-filters';
12+
13+
/**
14+
* Harness for interacting with a standard mat-slider in tests.
15+
* @dynamic
16+
*/
17+
export class MatSliderHarness extends ComponentHarness {
18+
static hostSelector = 'mat-slider';
19+
20+
/**
21+
* Gets a `HarnessPredicate` that can be used to search for a mat-slider with
22+
* specific attributes.
23+
* @param options Options for narrowing the search:
24+
* - `id` finds a slider with specific id.
25+
* @return a `HarnessPredicate` configured with the given options.
26+
*/
27+
static with(options: SliderHarnessFilters = {}): HarnessPredicate<MatSliderHarness> {
28+
return new HarnessPredicate(MatSliderHarness)
29+
.addOption('id', options.id, async (harness, id) => (await harness.getId()) === id);
30+
}
31+
32+
private _textLabel = this.locatorFor('.mat-slider-thumb-label-text');
33+
34+
/** Gets the slider's id. */
35+
async getId(): Promise<string|null> {
36+
const id = await (await this.host()).getAttribute('id');
37+
// In case no id has been specified, the "id" property always returns
38+
// an empty string. To make this method more explicit, we return null.
39+
return id !== '' ? id : null;
40+
}
41+
42+
/** Gets the current display value of the slider. */
43+
async getDisplayValue(): Promise<string> {
44+
return (await this._textLabel()).text();
45+
}
46+
47+
/** Gets the current percentage value of the slider. */
48+
async getPercentage(): Promise<number> {
49+
const min = await this.getMinValue();
50+
const max = await this.getMaxValue();
51+
return ((await this.getValue() || 0) - min) / (max - min);
52+
}
53+
54+
/** Gets the current value of the slider. */
55+
async getValue(): Promise<number> {
56+
return coerceNumberProperty(await (await this.host()).getAttribute('aria-valuenow'));
57+
}
58+
59+
/** Gets the maximum value of the slider. */
60+
async getMaxValue(): Promise<number> {
61+
return coerceNumberProperty(await (await this.host()).getAttribute('aria-valuemax'));
62+
}
63+
64+
/** Gets the minimum value of the slider. */
65+
async getMinValue(): Promise<number> {
66+
return coerceNumberProperty(await (await this.host()).getAttribute('aria-valuemin'));
67+
}
68+
69+
/** Whether the slider is disabled. */
70+
async isDisabled(): Promise<boolean> {
71+
const disabled = (await this.host()).getAttribute('aria-disabled');
72+
return coerceBooleanProperty(await disabled);
73+
}
74+
75+
/** Gets the orientiation of the slider. */
76+
async getOrientation(): Promise<'horizonal'|'vertical'> {
77+
// "aria-orientation" will always be set to either "horizontal" or "vertical".
78+
return (await this.host()).getAttribute('aria-orientation') as any;
79+
}
80+
81+
/**
82+
* Focuses the slider and returns a void promise that indicates when the
83+
* action is complete.
84+
*/
85+
async focus(): Promise<void> {
86+
return (await this.host()).focus();
87+
}
88+
89+
/**
90+
* Blurs the slider and returns a void promise that indicates when the
91+
* action is complete.
92+
*/
93+
async blur(): Promise<void> {
94+
return (await this.host()).blur();
95+
}
96+
}

src/material/slider/slider.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,8 @@ export class MatSlider extends _MatSliderMixinBase
656656
let size = this.vertical ? this._sliderDimensions.height : this._sliderDimensions.width;
657657
let posComponent = this.vertical ? pos.y : pos.x;
658658

659+
console.error(posComponent, offset, size, (posComponent - offset) / size);
660+
659661
// The exact value is calculated from the event and used to find the closest snap value.
660662
let percent = this._clamp((posComponent - offset) / size);
661663

0 commit comments

Comments
 (0)