Skip to content

Commit 21dc362

Browse files
committed
feat(material-experimental/mdc-slider): implement some basic unit tests
* implement unit tests for the standard slider, standard range slider, and for the slider ripple states * add mdc-slider theme to all-theme * make MatSliderVisualThumb ripple refs public so their states can be checked & tested * make MatSlider child component and html element getters default thumbPosition to the end thumb
1 parent 1fc0aba commit 21dc362

File tree

4 files changed

+341
-15
lines changed

4 files changed

+341
-15
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ ng_test_library(
7575
"//src/cdk/keycodes",
7676
"//src/cdk/platform",
7777
"//src/cdk/testing/private",
78+
"//src/material/core",
7879
"@npm//@angular/forms",
7980
"@npm//@angular/platform-browser",
81+
"@npm//@material/slider",
8082
],
8183
)
8284

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

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

9+
import {dispatchMouseEvent, dispatchPointerEvent} from '@angular/cdk/testing/private';
10+
import {Component, DebugElement, Type} from '@angular/core';
11+
import {ComponentFixture, TestBed} from '@angular/core/testing';
12+
import {RippleRef, RippleState} from '@angular/material/core';
13+
import {By} from '@angular/platform-browser';
14+
import {Thumb} from '@material/slider';
15+
import {MatSliderModule} from './module';
16+
import {MatSlider, MatSliderThumb, MatSliderVisualThumb} from './slider';
917

10-
/* tslint:disable-next-line:no-unused-variable */
11-
import {MatSlider} from './index';
18+
describe('MDC-based MatSlider' , () => {
19+
beforeAll(() => {
20+
// Mock #setPointerCapture as it throws errors on pointerdown without a real pointerId.
21+
spyOn(Element.prototype, 'setPointerCapture');
22+
});
1223

13-
// TODO(wagnermaciel): Implement this in a separate PR
24+
function createComponent<T>(component: Type<T>): ComponentFixture<T> {
25+
TestBed.configureTestingModule({
26+
imports: [MatSliderModule],
27+
declarations: [component],
28+
}).compileComponents();
29+
return TestBed.createComponent<T>(component);
30+
}
1431

15-
describe('MDC-based MatSlider' , () => {
1632
describe('standard slider', () => {
17-
it('does nothing yet', () => {});
33+
let fixture: ComponentFixture<StandardSlider>;
34+
let sliderDebugElement: DebugElement;
35+
let sliderInstance: MatSlider;
36+
let inputInstance: MatSliderThumb;
37+
38+
beforeEach(() => {
39+
fixture = createComponent(StandardSlider);
40+
fixture.detectChanges();
41+
sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
42+
sliderInstance = sliderDebugElement.componentInstance;
43+
inputInstance = sliderInstance._getInput(Thumb.END);
44+
});
45+
46+
beforeEach(done => {
47+
fixture.whenStable().then(() => done());
48+
});
49+
50+
it('should set the default values', () => {
51+
expect(inputInstance.value).toBe(0);
52+
expect(sliderInstance.min).toBe(0);
53+
expect(sliderInstance.max).toBe(100);
54+
});
55+
56+
it('should update the value on mousedown', () => {
57+
setValueByClick(sliderInstance, 19);
58+
expect(inputInstance.value).toBe(19);
59+
});
60+
61+
it('should update the value on a slide', () => {
62+
slideToValue(sliderInstance, 77);
63+
expect(inputInstance.value).toBe(77);
64+
});
65+
66+
it('should set the value as min when sliding before the track', () => {
67+
slideToValue(sliderInstance, -1);
68+
expect(inputInstance.value).toBe(0);
69+
});
70+
71+
it('should set the value as max when sliding past the track', () => {
72+
slideToValue(sliderInstance, 101);
73+
expect(inputInstance.value).toBe(100);
74+
});
75+
76+
it('should focus the slider input when clicking on the slider', () => {
77+
expect(document.activeElement).not.toBe(inputInstance._hostElement);
78+
setValueByClick(sliderInstance, 0);
79+
expect(document.activeElement).toBe(inputInstance._hostElement);
80+
});
81+
});
82+
83+
describe('standard range slider', () => {
84+
let fixture: ComponentFixture<StandardRangeSlider>;
85+
let sliderDebugElement: DebugElement;
86+
let sliderInstance: MatSlider;
87+
let startInputInstance: MatSliderThumb;
88+
let endInputInstance: MatSliderThumb;
89+
90+
beforeEach(() => {
91+
fixture = createComponent(StandardRangeSlider);
92+
fixture.detectChanges();
93+
sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
94+
sliderInstance = sliderDebugElement.componentInstance;
95+
startInputInstance = sliderInstance._getInput(Thumb.START);
96+
endInputInstance = sliderInstance._getInput(Thumb.END);
97+
});
98+
99+
beforeEach(done => {
100+
fixture.whenStable().then(() => done());
101+
});
102+
103+
it('should set the default values', () => {
104+
expect(startInputInstance.value).toBe(0);
105+
expect(endInputInstance.value).toBe(100);
106+
expect(sliderInstance.min).toBe(0);
107+
expect(sliderInstance.max).toBe(100);
108+
});
109+
110+
it('should update the start value on a slide', () => {
111+
slideToValue(sliderInstance, 19, Thumb.START);
112+
expect(startInputInstance.value).toBe(19);
113+
});
114+
115+
it('should update the end value on a slide', () => {
116+
slideToValue(sliderInstance, 27, Thumb.END);
117+
expect(endInputInstance.value).toBe(27);
118+
});
119+
120+
it('should update the start value on mousedown behind the start thumb', () => {
121+
sliderInstance._setValue(19, Thumb.START);
122+
setValueByClick(sliderInstance, 12);
123+
expect(startInputInstance.value).toBe(12);
124+
});
125+
126+
it('should update the end value on mousedown in front of the end thumb', () => {
127+
sliderInstance._setValue(27, Thumb.END);
128+
setValueByClick(sliderInstance, 55);
129+
expect(endInputInstance.value).toBe(55);
130+
});
131+
132+
it('should set the start value as min when sliding before the track', () => {
133+
slideToValue(sliderInstance, -1, Thumb.START);
134+
expect(startInputInstance.value).toBe(0);
135+
});
136+
137+
it('should set the end value as max when sliding past the track', () => {
138+
slideToValue(sliderInstance, 101, Thumb.START);
139+
expect(startInputInstance.value).toBe(100);
140+
});
141+
142+
it('should not let the start thumb slide past the end thumb', () => {
143+
sliderInstance._setValue(50, Thumb.END);
144+
slideToValue(sliderInstance, 75, Thumb.START);
145+
expect(startInputInstance.value).toBe(50);
146+
});
147+
148+
it('should not let the end thumb slide before the start thumb', () => {
149+
sliderInstance._setValue(50, Thumb.START);
150+
slideToValue(sliderInstance, 25, Thumb.END);
151+
expect(startInputInstance.value).toBe(50);
152+
});
153+
});
154+
155+
describe('ripple states', () => {
156+
let fixture: ComponentFixture<StandardSlider>;
157+
let inputInstance: MatSliderThumb;
158+
let thumbInstance: MatSliderVisualThumb;
159+
let thumbElement: HTMLElement;
160+
let thumbX: number;
161+
let thumbY: number;
162+
163+
beforeEach(() => {
164+
fixture = createComponent(StandardSlider);
165+
fixture.detectChanges();
166+
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
167+
const sliderInstance = sliderDebugElement.componentInstance;
168+
inputInstance = sliderInstance._getInput(Thumb.END);
169+
thumbInstance = sliderInstance._getThumb();
170+
thumbElement = thumbInstance._getHostElement();
171+
});
172+
173+
beforeEach(done => {
174+
fixture.whenStable().then(() => done());
175+
});
176+
177+
beforeEach(() => {
178+
const thumbDimensions = thumbElement.getBoundingClientRect();
179+
thumbX = thumbDimensions.left - (thumbDimensions.width / 2);
180+
thumbY = thumbDimensions.top - (thumbDimensions.height / 2);
181+
});
182+
183+
function isRippleVisible(rippleRef: RippleRef) {
184+
return rippleRef?.state === RippleState.FADING_IN
185+
|| rippleRef?.state === RippleState.VISIBLE;
186+
}
187+
188+
function blur() {
189+
inputInstance._hostElement.blur();
190+
}
191+
192+
function mouseenter() {
193+
dispatchMouseEvent(thumbElement, 'mouseenter', thumbX, thumbY);
194+
}
195+
196+
function mouseleave() {
197+
dispatchMouseEvent(thumbElement, 'mouseleave', thumbX, thumbY);
198+
}
199+
200+
function pointerdown() {
201+
dispatchPointerEvent(thumbElement, 'pointerdown', thumbX, thumbY);
202+
}
203+
204+
function pointerup() {
205+
dispatchPointerEvent(thumbElement, 'pointerup', thumbX, thumbY);
206+
}
207+
208+
it('should show the hover ripple on mouseenter', () => {
209+
expect(isRippleVisible(thumbInstance._hoverRippleRef)).toBe(false);
210+
mouseenter();
211+
expect(isRippleVisible(thumbInstance._hoverRippleRef)).toBe(true);
212+
});
213+
214+
it('should hide the hover ripple on mouseleave', () => {
215+
mouseenter();
216+
mouseleave();
217+
expect(isRippleVisible(thumbInstance._hoverRippleRef)).toBe(false);
218+
});
219+
220+
it('should show the focus ripple on pointerdown', () => {
221+
expect(isRippleVisible(thumbInstance._focusRippleRef)).toBe(false);
222+
pointerdown();
223+
expect(isRippleVisible(thumbInstance._focusRippleRef)).toBe(true);
224+
});
225+
226+
it('should continue to show the focus ripple on pointerup', () => {
227+
pointerdown();
228+
pointerup();
229+
expect(isRippleVisible(thumbInstance._focusRippleRef)).toBe(true);
230+
});
231+
232+
it('should hide the focus ripple on blur', () => {
233+
pointerdown();
234+
pointerup();
235+
blur();
236+
expect(isRippleVisible(thumbInstance._focusRippleRef)).toBe(false);
237+
});
238+
239+
it('should show the active ripple on pointerdown', () => {
240+
expect(isRippleVisible(thumbInstance._activeRippleRef)).toBe(false);
241+
pointerdown();
242+
expect(isRippleVisible(thumbInstance._activeRippleRef)).toBe(true);
243+
});
244+
245+
it('should hide the active ripple on pointerup', () => {
246+
pointerdown();
247+
pointerup();
248+
expect(isRippleVisible(thumbInstance._activeRippleRef)).toBe(false);
249+
});
250+
251+
// Edge cases.
252+
253+
it('should not show the hover ripple if the thumb is already focused', () => {
254+
pointerdown();
255+
mouseenter();
256+
expect(isRippleVisible(thumbInstance._hoverRippleRef)).toBe(false);
257+
});
258+
259+
it('should hide the hover ripple if the thumb is focused', () => {
260+
mouseenter();
261+
pointerdown();
262+
expect(isRippleVisible(thumbInstance._hoverRippleRef)).toBe(false);
263+
});
264+
265+
it('should not hide the focus ripple if the thumb is pressed', () => {
266+
pointerdown();
267+
blur();
268+
expect(isRippleVisible(thumbInstance._focusRippleRef)).toBe(true);
269+
});
270+
271+
it('should not hide the hover ripple on blur if the cursor is thumb being hovered', () => {
272+
mouseenter();
273+
pointerdown();
274+
pointerup();
275+
blur();
276+
expect(isRippleVisible(thumbInstance._hoverRippleRef)).toBe(true);
277+
});
278+
279+
it('should hide the focus ripple on drag end if the thumb already lost focus', () => {
280+
pointerdown();
281+
blur();
282+
pointerup();
283+
expect(isRippleVisible(thumbInstance._focusRippleRef)).toBe(false);
284+
});
18285
});
19286
});
287+
288+
289+
@Component({
290+
template: `
291+
<mat-slider>
292+
<input matSliderThumb>
293+
</mat-slider>
294+
`,
295+
})
296+
class StandardSlider {}
297+
298+
@Component({
299+
template: `
300+
<mat-slider>
301+
<input matSliderStartThumb>
302+
<input matSliderEndThumb>
303+
</mat-slider>
304+
`,
305+
})
306+
class StandardRangeSlider {}
307+
308+
/** Clicks on the MatSlider at the coordinates corresponding to the given value. */
309+
function setValueByClick(slider: MatSlider, value: number) {
310+
const {min, max} = slider;
311+
const percent = (value - min) / (max - min);
312+
313+
const sliderElement = slider._elementRef.nativeElement;
314+
const {top, left, width, height} = sliderElement.getBoundingClientRect();
315+
const x = left + (width * percent);
316+
const y = top + (height / 2);
317+
318+
dispatchPointerEvent(sliderElement, 'pointerdown', x, y);
319+
dispatchPointerEvent(sliderElement, 'pointerup', x, y);
320+
}
321+
322+
/** Slides the MatSlider's thumb to the given value. */
323+
function slideToValue(slider: MatSlider, value: number, thumbPosition: Thumb = Thumb.END) {
324+
const {min, max} = slider;
325+
const percent = (value - min) / (max - min);
326+
327+
const sliderElement = slider._elementRef.nativeElement;
328+
const thumbElement = slider._getThumbElement(thumbPosition);
329+
330+
const sliderDimensions = sliderElement.getBoundingClientRect();
331+
let thumbDimensions = thumbElement.getBoundingClientRect();
332+
333+
const startX = thumbDimensions.left + (thumbDimensions.width / 2);
334+
const startY = thumbDimensions.top + (thumbDimensions.height / 2);
335+
336+
const endX = sliderDimensions.left + (sliderDimensions.width * percent);
337+
const endY = sliderDimensions.top + (sliderDimensions.height / 2);
338+
339+
dispatchPointerEvent(sliderElement, 'pointerdown', startX, startY);
340+
dispatchPointerEvent(sliderElement, 'pointermove', endX, endY);
341+
dispatchPointerEvent(sliderElement, 'pointerup', endX, endY);
342+
}

0 commit comments

Comments
 (0)