Skip to content

Commit 395344e

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

File tree

14 files changed

+411
-7
lines changed

14 files changed

+411
-7
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
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+
9+
/**
10+
* Dimensions for element size and its position relative to the viewport.
11+
*/
12+
export interface ElementDimensions {
13+
top: number;
14+
left: number;
15+
width: number;
16+
height: number;
17+
}

src/cdk-experimental/testing/protractor/protractor-element.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {browser, ElementFinder} from 'protractor';
1010
import {TestElement} from '../test-element';
11+
import {ElementDimensions} from '../element-dimensions';
1112

1213
/** A `TestElement` implementation for Protractor. */
1314
export class ProtractorElement implements TestElement {
@@ -21,8 +22,11 @@ export class ProtractorElement implements TestElement {
2122
return this.element.clear();
2223
}
2324

24-
async click(): Promise<void> {
25-
return this.element.click();
25+
async click(relativeX = 0, relativeY = 0): Promise<void> {
26+
await browser.actions()
27+
.mouseMove(await this.element.getWebElement(), {x: relativeX, y: relativeY})
28+
.click()
29+
.perform();
2630
}
2731

2832
async focus(): Promise<void> {
@@ -55,4 +59,10 @@ export class ProtractorElement implements TestElement {
5559
const classes = (await this.getAttribute('class')) || '';
5660
return new Set(classes.split(/\s+/).filter(c => c)).has(name);
5761
}
62+
63+
async getDimensions(): Promise<ElementDimensions> {
64+
const {width, height} = await this.element.getSize();
65+
const {x: left, y: top} = await this.element.getLocation();
66+
return {width, height, left, top};
67+
}
5868
}

src/cdk-experimental/testing/test-element.ts

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

9+
import {ElementDimensions} from './element-dimensions';
10+
911
/**
1012
* This acts as a common interface for DOM elements across both unit and e2e tests. It is the
1113
* interface through which the ComponentHarness interacts with the component's DOM.
@@ -18,7 +20,7 @@ export interface TestElement {
1820
clear(): Promise<void>;
1921

2022
/** Click the element. */
21-
click(): Promise<void>;
23+
click(relativeX?: number, relativeY?: number): Promise<void>;
2224

2325
/** Focus the element. */
2426
focus(): Promise<void>;
@@ -46,4 +48,7 @@ export interface TestElement {
4648

4749
/** Checks whether the element has the given class. */
4850
hasClass(name: string): Promise<boolean>;
51+
52+
/** Gets the dimensions of the element. */
53+
getDimensions(): Promise<ElementDimensions>;
4954
}

src/cdk-experimental/testing/testbed/unit-test-element.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
triggerFocus
1515
} from '@angular/cdk/testing';
1616
import {TestElement} from '../test-element';
17+
import {ElementDimensions} from '../element-dimensions';
1718

1819
function isTextInput(element: Element): element is HTMLInputElement | HTMLTextAreaElement {
1920
return element.nodeName.toLowerCase() === 'input' ||
@@ -41,9 +42,14 @@ export class UnitTestElement implements TestElement {
4142
await this._stabilize();
4243
}
4344

44-
async click(): Promise<void> {
45+
async click(relativeX = 0, relativeY = 0): Promise<void> {
4546
await this._stabilize();
46-
dispatchMouseEvent(this.element, 'click');
47+
const {left, top} = this.element.getBoundingClientRect();
48+
const pageX = left + relativeX;
49+
const pageY = top + relativeY;
50+
dispatchMouseEvent(this.element, 'mousedown', pageX, pageY);
51+
dispatchMouseEvent(this.element, 'mouseup', pageX, pageY);
52+
dispatchMouseEvent(this.element, 'click', pageX, pageY);
4753
await this._stabilize();
4854
}
4955

@@ -105,4 +111,9 @@ export class UnitTestElement implements TestElement {
105111
await this._stabilize();
106112
return this.element.classList.contains(name);
107113
}
114+
115+
async getDimensions(): Promise<ElementDimensions> {
116+
await this._stabilize();
117+
return this.element.getBoundingClientRect();
118+
}
108119
}

src/cdk-experimental/testing/tests/protractor.e2e.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,11 @@ describe('ProtractorHarnessEnvironment', () => {
196196
.toBe(await browser.driver.switchTo().activeElement().getAttribute('id'));
197197
});
198198

199+
it('should be able to retrieve dimensions', async () => {
200+
const dimensions = await (await harness.title()).getDimensions();
201+
expect(dimensions).toEqual(jasmine.objectContaining({height: 50, width: 200}));
202+
});
203+
199204
it('should be able to hover', async () => {
200205
const host = await harness.host();
201206
let classAttr = await host.getAttribute('class');

src/cdk-experimental/testing/tests/test-main-component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<h1 style="height: 50px">Main Component</h1>
1+
<h1 style="height: 50px; width: 200px;">Main Component</h1>
22
<div id="username">Hello {{username}} from Angular 2!</div>
33
<div class="counters">
44
<button (click)="click()">Up</button><br>

src/cdk-experimental/testing/tests/testbed.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ describe('TestbedHarnessEnvironment', () => {
215215
expect(await input.getAttribute('id')).toBe(document.activeElement!.id);
216216
});
217217

218+
it('should be able to retrieve dimensions', async () => {
219+
const dimensions = await (await harness.title()).getDimensions();
220+
expect(dimensions).toEqual(jasmine.objectContaining({height: 50, width: 200}));
221+
});
222+
218223
it('should be able to hover', async () => {
219224
const host = await harness.host();
220225
let classAttr = await host.getAttribute('class');
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: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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 {MatSliderModule} from '@angular/material/slider';
6+
import {MatSliderHarness} from './slider-harness';
7+
8+
let fixture: ComponentFixture<SliderHarnessTest>;
9+
let loader: HarnessLoader;
10+
let sliderHarness: typeof MatSliderHarness;
11+
12+
describe('MatSliderHarness', () => {
13+
describe('non-MDC-based', () => {
14+
beforeEach(async () => {
15+
await TestBed
16+
.configureTestingModule({
17+
imports: [MatSliderModule],
18+
declarations: [SliderHarnessTest],
19+
})
20+
.compileComponents();
21+
22+
fixture = TestBed.createComponent(SliderHarnessTest);
23+
fixture.detectChanges();
24+
loader = TestbedHarnessEnvironment.loader(fixture);
25+
sliderHarness = MatSliderHarness;
26+
});
27+
28+
runTests();
29+
});
30+
31+
describe(
32+
'MDC-based',
33+
() => {
34+
// TODO: run tests for MDC based slider once implemented.
35+
});
36+
});
37+
38+
/** Shared tests to run on both the original and MDC-based sliders. */
39+
function runTests() {
40+
it('should load all slider harnesses', async () => {
41+
const sliders = await loader.getAllHarnesses(sliderHarness);
42+
expect(sliders.length).toBe(3);
43+
});
44+
45+
it('should load slider harness by id', async () => {
46+
const sliders = await loader.getAllHarnesses(sliderHarness.with({id: 'my-slider'}));
47+
expect(sliders.length).toBe(1);
48+
});
49+
50+
it('should get id of slider', async () => {
51+
const sliders = await loader.getAllHarnesses(sliderHarness);
52+
expect(await sliders[0].getId()).toBe(null);
53+
expect(await sliders[1].getId()).toBe('my-slider');
54+
expect(await sliders[2].getId()).toBe(null);
55+
});
56+
57+
it('should get value of slider', async () => {
58+
const sliders = await loader.getAllHarnesses(sliderHarness);
59+
expect(await sliders[0].getValue()).toBe(50);
60+
expect(await sliders[1].getValue()).toBe(0);
61+
expect(await sliders[2].getValue()).toBe(225);
62+
});
63+
64+
it('should get percentage of slider', async () => {
65+
const sliders = await loader.getAllHarnesses(sliderHarness);
66+
expect(await sliders[0].getPercentage()).toBe(0.5);
67+
expect(await sliders[1].getPercentage()).toBe(0);
68+
expect(await sliders[2].getPercentage()).toBe(0.5);
69+
});
70+
71+
it('should get max value of slider', async () => {
72+
const sliders = await loader.getAllHarnesses(sliderHarness);
73+
expect(await sliders[0].getMaxValue()).toBe(100);
74+
expect(await sliders[1].getMaxValue()).toBe(100);
75+
expect(await sliders[2].getMaxValue()).toBe(250);
76+
});
77+
78+
it('should get min value of slider', async () => {
79+
const sliders = await loader.getAllHarnesses(sliderHarness);
80+
expect(await sliders[0].getMinValue()).toBe(0);
81+
expect(await sliders[1].getMinValue()).toBe(0);
82+
expect(await sliders[2].getMinValue()).toBe(200);
83+
});
84+
85+
it('should get display value of slider', async () => {
86+
const sliders = await loader.getAllHarnesses(sliderHarness);
87+
expect(await sliders[0].getDisplayValue()).toBe('50');
88+
expect(await sliders[1].getDisplayValue()).toBe('Null');
89+
expect(await sliders[2].getDisplayValue()).toBe('#225');
90+
});
91+
92+
it('should get orientation of slider', async () => {
93+
const sliders = await loader.getAllHarnesses(sliderHarness);
94+
expect(await sliders[0].getOrientation()).toBe('horizontal');
95+
expect(await sliders[1].getOrientation()).toBe('horizontal');
96+
expect(await sliders[2].getOrientation()).toBe('vertical');
97+
});
98+
99+
it('should be able to focus slider', async () => {
100+
const [slider] = await loader.getAllHarnesses(sliderHarness);
101+
expect(getActiveElementTagName()).not.toBe('mat-slider');
102+
await slider.focus();
103+
expect(getActiveElementTagName()).toBe('mat-slider');
104+
});
105+
106+
it('should be able to blur slider', async () => {
107+
const [slider] = await loader.getAllHarnesses(sliderHarness);
108+
expect(getActiveElementTagName()).not.toBe('mat-slider');
109+
await slider.focus();
110+
expect(getActiveElementTagName()).toBe('mat-slider');
111+
await slider.blur();
112+
expect(getActiveElementTagName()).not.toBe('mat-slider');
113+
});
114+
115+
it('should be able to set value of slider', async () => {
116+
const sliders = await loader.getAllHarnesses(sliderHarness);
117+
expect(await sliders[1].getValue()).toBe(0);
118+
expect(await sliders[2].getValue()).toBe(225);
119+
120+
await sliders[1].setValue(33);
121+
await sliders[2].setValue(300);
122+
123+
expect(await sliders[1].getValue()).toBe(33);
124+
// value should be clamped to the maximum.
125+
expect(await sliders[2].getValue()).toBe(250);
126+
127+
// should not retrieve incorrect values in case slider is inverted
128+
// due to RTL page layout.
129+
fixture.componentInstance.dir = 'rtl';
130+
fixture.detectChanges();
131+
132+
await sliders[1].setValue(80);
133+
expect(await sliders[1].getValue()).toBe(80);
134+
135+
// should not retrieve incorrect values in case sliders are inverted.
136+
fixture.componentInstance.invertSliders = true;
137+
fixture.detectChanges();
138+
139+
await sliders[1].setValue(75);
140+
await sliders[2].setValue(210);
141+
142+
expect(await sliders[1].getValue()).toBe(75);
143+
expect(await sliders[2].getValue()).toBe(210);
144+
});
145+
146+
it('should get disabled state of slider', async () => {
147+
const sliders = await loader.getAllHarnesses(sliderHarness);
148+
expect(await sliders[0].isDisabled()).toBe(true);
149+
expect(await sliders[1].isDisabled()).toBe(false);
150+
expect(await sliders[2].isDisabled()).toBe(false);
151+
});
152+
}
153+
154+
function getActiveElementTagName() {
155+
return document.activeElement ? document.activeElement.tagName.toLowerCase() : '';
156+
}
157+
158+
@Component({
159+
template: `
160+
<mat-slider value="50" disabled></mat-slider>
161+
<div [dir]="dir">
162+
<mat-slider [id]="sliderId" [displayWith]="displayFn"
163+
[invert]="invertSliders"></mat-slider>
164+
</div>
165+
<mat-slider min="200" max="250" value="225" [displayWith]="displayFn" vertical
166+
[invert]="invertSliders">
167+
</mat-slider>
168+
`
169+
})
170+
class SliderHarnessTest {
171+
sliderId = 'my-slider';
172+
invertSliders = false;
173+
dir = 'ltr';
174+
175+
displayFn(value: number|null) {
176+
if (!value) {
177+
return 'Null';
178+
}
179+
return `#${value}`;
180+
}
181+
}

0 commit comments

Comments
 (0)