sass-references/angular-material/material/slider/slider.spec.ts

1885 lines
66 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {BidiModule, Directionality} from '@angular/cdk/bidi';
import {Platform} from '@angular/cdk/platform';
import {dispatchEvent, dispatchFakeEvent, dispatchPointerEvent} from '@angular/cdk/testing/private';
import {Component, Provider, QueryList, Type, ViewChild, ViewChildren} from '@angular/core';
import {
ComponentFixture,
TestBed,
fakeAsync,
flush,
tick,
waitForAsync,
} from '@angular/core/testing';
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
import {By} from '@angular/platform-browser';
import {of} from 'rxjs';
import {MatSliderModule} from './module';
import {MatSlider} from './slider';
import {MatSliderRangeThumb, MatSliderThumb} from './slider-input';
import {_MatThumb} from './slider-interface';
import {MatSliderVisualThumb} from './slider-thumb';
interface Point {
x: number;
y: number;
}
describe('MatSlider', () => {
let platform: Platform;
function createComponent<T>(component: Type<T>, providers: Provider[] = []): ComponentFixture<T> {
TestBed.configureTestingModule({
imports: [FormsModule, MatSliderModule, ReactiveFormsModule, BidiModule],
providers: [...providers],
declarations: [component],
});
platform = TestBed.inject(Platform);
return TestBed.createComponent<T>(component);
}
function checkInput(
input: MatSliderThumb,
{
min,
max,
value,
translateX,
width,
step,
}: {min: number; max: number; value: number; translateX: number; width?: number; step?: number},
): void {
expect(input.min).withContext('min').toBe(min);
expect(input.max).withContext('max').toBe(max);
expect(input.value).withContext('value').toBe(value);
// The discrepancy between the "ideal" and "actual" translateX comes from
// the 3px offset from the start & end of the slider track to the first
// and last tick marks.
//
// The "actual" translateX is calculated based on a slider that is 6px
// smaller than the width of the slider. Using this "actual" translateX in
// tests would make it even more difficult than it already is to tell if
// the translateX is off, so we abstract things in here so tests can be
// more intuitive.
//
// The most clear way to compare the two tx's is to just turn them into
// percentages by dividing by their (total height) / 100.
const idealTXPercentage = Math.round(translateX / 3);
const actualTXPercentage = Math.round((input.translateX - 3) / 2.94);
expect(actualTXPercentage)
.withContext(`translateX: ${input.translateX} should be close to ${translateX}`)
.toBe(idealTXPercentage);
if (step !== undefined) {
expect(input.step).withContext('step').toBe(step);
}
if (width !== undefined) {
const realWidth = parseInt(
(input as MatSliderRangeThumb)._hostElement.style.width.match(/\d+/)![0],
10,
);
expect(realWidth)
.withContext('width')
.toBeCloseTo((300 * width) / 100 + 16, 0);
}
}
// Note that this test is outside of the other `describe` blocks, because they all run
// `detectChanges` in the `beforeEach` and we're testing specifically what happens if it
// is destroyed before change detection has run.
it('should not throw if a slider is destroyed before the first change detection run', () => {
expect(() => {
const fixture = createComponent(StandardSlider);
fixture.destroy();
}).not.toThrow();
});
describe('standard slider', () => {
let fixture: ComponentFixture<StandardSlider>;
let slider: MatSlider;
let input: MatSliderThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(StandardSlider);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
}));
it('should set the default values', () => {
expect(slider.min).toBe(0);
expect(slider.max).toBe(100);
expect(slider.step).toBe(1);
checkInput(input, {min: 0, max: 100, value: 0, step: 1, translateX: 0});
});
it('should update by click', fakeAsync(() => {
setValueByClick(slider, input, 25);
checkInput(input, {min: 0, max: 100, value: 25, step: 1, translateX: 75});
setValueByClick(slider, input, 50);
checkInput(input, {min: 0, max: 100, value: 50, step: 1, translateX: 150});
setValueByClick(slider, input, 75);
checkInput(input, {min: 0, max: 100, value: 75, step: 1, translateX: 225});
setValueByClick(slider, input, 100);
checkInput(input, {min: 0, max: 100, value: 100, step: 1, translateX: 300});
}));
it('should update by slide', fakeAsync(() => {
slideToValue(slider, input, 25);
checkInput(input, {min: 0, max: 100, value: 25, step: 1, translateX: 75});
slideToValue(slider, input, 50);
checkInput(input, {min: 0, max: 100, value: 50, step: 1, translateX: 150});
slideToValue(slider, input, 75);
checkInput(input, {min: 0, max: 100, value: 75, step: 1, translateX: 225});
slideToValue(slider, input, 100);
checkInput(input, {min: 0, max: 100, value: 100, step: 1, translateX: 300});
}));
it('should not slide before the track', fakeAsync(() => {
slideToValue(slider, input, -10);
expect(input.value).toBe(0);
checkInput(input, {min: 0, max: 100, value: 0, step: 1, translateX: 0});
}));
it('should not slide past the track', fakeAsync(() => {
slideToValue(slider, input, 110);
expect(input.value).toBe(100);
checkInput(input, {min: 0, max: 100, value: 100, step: 1, translateX: 300});
}));
// TODO(wagnermaciel): Fix this test case (behavior works as intended in browser).
// it('should not break when the page layout changes', fakeAsync(async () => {
// slider._elementRef.nativeElement.style.marginLeft = '100px';
// tick(200);
// fixture.detectChanges();
// setValueByClick(slider, input, 25);
// checkInput(input, {min: 0, max: 100, value: 25, step: 1, translateX: 75});
// slider._elementRef.nativeElement.style.marginLeft = 'initial';
// }));
});
describe('standard range slider', () => {
let fixture: ComponentFixture<StandardRangeSlider>;
let slider: MatSlider;
let endInput: MatSliderRangeThumb;
let startInput: MatSliderRangeThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(StandardRangeSlider);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
}));
it('should set the default values', () => {
checkInput(startInput, {min: 0, max: 100, value: 0, step: 1, translateX: 0});
checkInput(endInput, {min: 0, max: 100, value: 100, step: 1, translateX: 300});
expect(slider.min).toBe(0);
expect(slider.max).toBe(100);
expect(slider.step).toBe(1);
});
it('should update by start input click', fakeAsync(() => {
setValueByClick(slider, startInput, 25);
checkInput(startInput, {min: 0, max: 100, value: 25, translateX: 75});
checkInput(endInput, {min: 25, max: 100, value: 100, translateX: 300});
}));
it('should update by end input click', fakeAsync(() => {
setValueByClick(slider, endInput, 75);
checkInput(startInput, {min: 0, max: 75, value: 0, translateX: 0});
checkInput(endInput, {min: 0, max: 100, value: 75, translateX: 225});
}));
it('should update by start thumb slide', fakeAsync(() => {
slideToValue(slider, startInput, 75);
checkInput(startInput, {min: 0, max: 100, value: 75, translateX: 225});
checkInput(endInput, {min: 75, max: 100, value: 100, translateX: 300});
}));
it('should update by end thumb slide', fakeAsync(() => {
slideToValue(slider, endInput, 25);
checkInput(startInput, {min: 0, max: 25, value: 0, translateX: 0});
checkInput(endInput, {min: 0, max: 100, value: 25, translateX: 75});
}));
it('should not allow start thumb to slide before the track', fakeAsync(() => {
slideToValue(slider, startInput, -10);
checkInput(startInput, {min: 0, max: 100, value: 0, translateX: 0});
checkInput(endInput, {min: 0, max: 100, value: 100, translateX: 300});
}));
it('should not allow end thumb to slide past the track', fakeAsync(() => {
slideToValue(slider, endInput, 110);
checkInput(startInput, {min: 0, max: 100, value: 0, translateX: 0});
checkInput(endInput, {min: 0, max: 100, value: 100, translateX: 300});
}));
it('should not allow start thumb to slide past the end thumb', fakeAsync(() => {
slideToValue(slider, endInput, 50);
slideToValue(slider, startInput, 55);
checkInput(startInput, {min: 0, max: 50, value: 50, translateX: 150});
checkInput(endInput, {min: 50, max: 100, value: 50, translateX: 150});
}));
it('should not allow end thumb to slide past the start thumb', fakeAsync(() => {
slideToValue(slider, startInput, 50);
slideToValue(slider, endInput, 45);
checkInput(startInput, {min: 0, max: 50, value: 50, translateX: 150});
checkInput(endInput, {min: 50, max: 100, value: 50, translateX: 150});
}));
// TODO(wagnermaciel): Fix this test case (behavior works as intended in browser).
// it('should not break when the page layout changes', fakeAsync(() => {
// slider._elementRef.nativeElement.style.marginLeft = '100px';
// setValueByClick(slider, startInput, 25);
// checkInput(startInput, {min: 0, max: 100, value: 25, translateX: 75});
// checkInput(endInput, {min: 25, max: 100, value: 100, translateX: 300});
// setValueByClick(slider, endInput, 75);
// checkInput(startInput, {min: 0, max: 75, value: 25, translateX: 75});
// checkInput(endInput, {min: 25, max: 100, value: 75, translateX: 225});
// slider._elementRef.nativeElement.style.marginLeft = 'initial';
// }));
});
describe('slider with min/max bindings', () => {
let fixture: ComponentFixture<SliderWithMinAndMax>;
let slider: MatSlider;
let input: MatSliderThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(SliderWithMinAndMax);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
}));
it('should have the correct initial values', () => {
checkInput(input, {min: 25, max: 75, value: 25, translateX: 0});
});
it('should update the min when the bound value changes', () => {
fixture.componentInstance.min = 0;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(input, {min: 0, max: 75, value: 25, translateX: 100});
});
it('should update the max when the bound value changes', () => {
fixture.componentInstance.max = 90;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(input, {min: 25, max: 90, value: 25, translateX: 0});
});
it('should update the value if the min increases past it', () => {
fixture.componentInstance.min = 50;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(input, {min: 50, max: 75, value: 50, translateX: 0});
});
it('should update the value if the max decreases below it', () => {
input.value = 75;
fixture.componentInstance.max = 50;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(input, {min: 25, max: 50, value: 50, translateX: 300});
});
it('should allow the min increase above the max', () => {
fixture.componentInstance.min = 80;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(input, {min: 80, max: 75, value: 80, translateX: 0});
});
it('should allow the max to decrease below the min', () => {
fixture.componentInstance.max = -10;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(input, {min: 25, max: -10, value: 25, translateX: 0});
});
it('should update the thumb translateX when the min changes', () => {
checkInput(input, {min: 25, max: 75, value: 25, translateX: 0});
fixture.componentInstance.min = -25;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(input, {min: -25, max: 75, value: 25, translateX: 150});
});
it('should update the thumb translateX when the max changes', fakeAsync(() => {
setValueByClick(slider, input, 50);
checkInput(input, {min: 25, max: 75, value: 50, translateX: 150});
fixture.componentInstance.max = 125;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(input, {min: 25, max: 125, value: 50, translateX: 75});
}));
});
describe('range slider with min/max bindings', () => {
let fixture: ComponentFixture<RangeSliderWithMinAndMax>;
let slider: MatSlider;
let endInput: MatSliderRangeThumb;
let startInput: MatSliderRangeThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(RangeSliderWithMinAndMax);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
}));
it('should have the correct initial values', () => {
checkInput(startInput, {min: 25, max: 75, value: 25, translateX: 0});
checkInput(endInput, {min: 25, max: 75, value: 75, translateX: 300});
});
describe('should handle min changes', () => {
it('that do not affect values', () => {
checkInput(startInput, {min: 25, max: 75, value: 25, translateX: 0});
checkInput(endInput, {min: 25, max: 75, value: 75, translateX: 300});
fixture.componentInstance.min = -25;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: -25, max: 75, value: 25, translateX: 150});
checkInput(endInput, {min: 25, max: 75, value: 75, translateX: 300});
});
it('that affect the start value', () => {
fixture.componentInstance.min = 50;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 50, max: 75, value: 50, translateX: 0});
checkInput(endInput, {min: 50, max: 75, value: 75, translateX: 300});
});
it('that affect both values', () => {
endInput.value = 50;
fixture.componentInstance.min = 60;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 60, max: 60, value: 60, translateX: 0});
checkInput(endInput, {min: 60, max: 75, value: 60, translateX: 0});
});
it('where the new start tx is greater than the old end tx', fakeAsync(() => {
fixture.componentInstance.min = 0;
fixture.componentInstance.max = 100;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, startInput, 10);
slideToValue(slider, endInput, 20);
checkInput(startInput, {min: 0, max: 20, value: 10, translateX: 30});
checkInput(endInput, {min: 10, max: 100, value: 20, translateX: 60});
fixture.componentInstance.min = -1000;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: -1000, max: 20, value: 10, translateX: 275.5});
checkInput(endInput, {min: 10, max: 100, value: 20, translateX: 278});
}));
it('where the new end tx is less than the old start tx', fakeAsync(() => {
fixture.componentInstance.min = 0;
fixture.componentInstance.max = 100;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, endInput, 92);
slideToValue(slider, startInput, 91);
checkInput(startInput, {min: 0, max: 92, value: 91, translateX: 273});
checkInput(endInput, {min: 91, max: 100, value: 92, translateX: 276});
fixture.componentInstance.min = 90;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 90, max: 92, value: 91, translateX: 30});
checkInput(endInput, {min: 91, max: 100, value: 92, translateX: 60});
}));
it('that make min and max equal', () => {
fixture.componentInstance.min = 75;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 75, max: 75, value: 75, translateX: 0});
checkInput(endInput, {min: 75, max: 75, value: 75, translateX: 0});
});
it('that increase above the max', () => {
fixture.componentInstance.min = 80;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 80, max: 75, value: 80, translateX: 0});
checkInput(endInput, {min: 80, max: 75, value: 80, translateX: 0});
});
});
describe('should handle max changes', () => {
it('that do not affect values', () => {
checkInput(startInput, {min: 25, max: 75, value: 25, translateX: 0});
checkInput(endInput, {min: 25, max: 75, value: 75, translateX: 300});
fixture.componentInstance.max = 125;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 25, max: 75, value: 25, translateX: 0});
checkInput(endInput, {min: 25, max: 125, value: 75, translateX: 150});
});
it('that affect the end value', () => {
fixture.componentInstance.max = 50;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(endInput, {min: 25, max: 50, value: 50, translateX: 300});
checkInput(startInput, {min: 25, max: 50, value: 25, translateX: 0});
});
it('that affect both values', () => {
startInput.value = 60;
fixture.componentInstance.max = 50;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(endInput, {min: 50, max: 50, value: 50, translateX: 300});
checkInput(startInput, {min: 25, max: 50, value: 50, translateX: 300});
});
it('where the new start tx is greater than the old end tx', fakeAsync(() => {
fixture.componentInstance.min = 0;
fixture.componentInstance.max = 100;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, startInput, 1);
slideToValue(slider, endInput, 2);
checkInput(startInput, {min: 0, max: 2, value: 1, translateX: 3});
checkInput(endInput, {min: 1, max: 100, value: 2, translateX: 6});
fixture.componentInstance.max = 10;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 0, max: 2, value: 1, translateX: 30});
checkInput(endInput, {min: 1, max: 10, value: 2, translateX: 60});
}));
it('where the new end tx is less than the old start tx', fakeAsync(() => {
fixture.componentInstance.min = 0;
fixture.componentInstance.max = 100;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, endInput, 95);
slideToValue(slider, startInput, 90);
checkInput(startInput, {min: 0, max: 95, value: 90, translateX: 270});
checkInput(endInput, {min: 90, max: 100, value: 95, translateX: 285});
fixture.componentInstance.max = 1000;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 0, max: 95, value: 90, translateX: 27});
checkInput(endInput, {min: 90, max: 1000, value: 95, translateX: 28.5});
}));
it('that make min and max equal', () => {
fixture.componentInstance.max = 25;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 25, max: 25, value: 25, translateX: 0});
checkInput(endInput, {min: 25, max: 25, value: 25, translateX: 0});
});
it('that decrease below the min', () => {
fixture.componentInstance.max = 0;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
// For some reason there was a bug with Safari 15.3.
// Manually testing on version 16.0 shows that this issue no longer exists.
if (!platform.SAFARI) {
checkInput(startInput, {min: 25, max: 0, value: 25, translateX: 0});
checkInput(endInput, {min: 25, max: 0, value: 25, translateX: 0});
}
});
});
});
describe('disabled slider', () => {
let slider: MatSlider;
let input: MatSliderThumb;
beforeEach(waitForAsync(() => {
const fixture = createComponent(DisabledSlider);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
}));
it('should be disabled', () => {
expect(slider.disabled).toBeTrue();
});
it('should have the disabled class on the root element', () => {
expect(slider._elementRef.nativeElement.classList).toContain('mdc-slider--disabled');
});
it('should set the disabled attribute on the input element', () => {
expect(input._hostElement.disabled).toBeTrue();
});
});
describe('disabled range slider', () => {
let slider: MatSlider;
let startInput: MatSliderThumb;
let endInput: MatSliderThumb;
beforeEach(waitForAsync(() => {
const fixture = createComponent(DisabledRangeSlider);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
}));
it('should be disabled', () => {
expect(slider.disabled).toBeTrue();
});
it('should have the disabled class on the root element', () => {
expect(slider._elementRef.nativeElement.classList).toContain('mdc-slider--disabled');
});
it('should set the disabled attribute on the input elements', () => {
expect(startInput._hostElement.disabled).toBeTrue();
expect(endInput._hostElement.disabled).toBeTrue();
});
});
describe('ripple states', () => {
let input: MatSliderThumb;
let thumbInstance: MatSliderVisualThumb;
let thumbElement: HTMLElement;
let thumbX: number;
let thumbY: number;
beforeEach(waitForAsync(() => {
const fixture = createComponent(StandardSlider);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
const slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
thumbInstance = slider._getThumb(_MatThumb.END);
thumbElement = thumbInstance._hostElement;
const thumbDimensions = thumbElement.getBoundingClientRect();
thumbX = thumbDimensions.left + thumbDimensions.width / 2;
thumbY = thumbDimensions.top + thumbDimensions.height / 2;
}));
function isRippleVisible(selector: string) {
flushRippleTransitions();
return thumbElement.querySelector(`.mat-mdc-slider-${selector}-ripple`) !== null;
}
function flushRippleTransitions() {
thumbElement.querySelectorAll('.mat-ripple-element').forEach(el => {
dispatchFakeEvent(el, 'transitionend');
});
}
function blur() {
input._hostElement.blur();
}
function pointerenter() {
dispatchPointerEvent(input._hostElement, 'pointermove', thumbX, thumbY);
}
function pointerleave() {
dispatchPointerEvent(input._hostElement, 'pointermove', thumbX + 1000, thumbY);
}
function pointerdown() {
dispatchPointerEvent(input._hostElement, 'pointerdown', thumbX, thumbY);
input.focus();
}
function pointerup() {
dispatchPointerEvent(input._hostElement, 'pointerup', thumbX, thumbY);
}
it('should show the hover ripple on pointerenter', fakeAsync(() => {
// Doesn't make sense to test for pointerenter events on touch devices.
expect(isRippleVisible('hover')).toBeFalse();
pointerenter();
expect(isRippleVisible('hover')).toBeTrue();
}));
it('should hide the hover ripple on pointerleave', fakeAsync(() => {
// Doesn't make sense to test for pointerleave events on touch devices.
pointerenter();
pointerleave();
expect(isRippleVisible('hover')).toBeFalse();
}));
it('should show the focus ripple on pointerdown', fakeAsync(() => {
expect(isRippleVisible('focus')).toBeFalse();
pointerdown();
flush();
expect(isRippleVisible('focus')).toBeTrue();
}));
it('should continue to show the focus ripple on pointerup', fakeAsync(() => {
pointerdown();
pointerup();
flush();
// The slider immediately loses focus on pointerup for Safari.
if (platform.SAFARI) {
expect(isRippleVisible('hover')).toBeTrue();
} else {
expect(isRippleVisible('focus')).toBeTrue();
}
}));
it('should hide the focus ripple on blur', fakeAsync(() => {
pointerdown();
pointerup();
blur();
flush();
expect(isRippleVisible('focus')).toBeFalse();
}));
it('should show the active ripple on pointerdown', fakeAsync(() => {
expect(isRippleVisible('active')).toBeFalse();
pointerdown();
expect(isRippleVisible('active')).toBeTrue();
flush();
}));
it('should hide the active ripple on pointerup', fakeAsync(() => {
pointerdown();
pointerup();
flush();
expect(isRippleVisible('active')).toBeFalse();
}));
// Edge cases.
it('should not show the hover ripple if the thumb is already focused', fakeAsync(() => {
pointerdown();
pointerenter();
flush();
expect(isRippleVisible('hover')).toBeFalse();
}));
it('should hide the hover ripple if the thumb is focused', fakeAsync(() => {
pointerenter();
pointerdown();
flush();
expect(isRippleVisible('hover')).toBeFalse();
}));
it('should not hide the focus ripple if the thumb is pressed', fakeAsync(() => {
pointerdown();
blur();
flush();
expect(isRippleVisible('focus')).toBeTrue();
}));
it('should not hide the hover ripple on blur if the thumb is hovered', fakeAsync(() => {
pointerenter();
pointerdown();
pointerup();
blur();
flush();
expect(isRippleVisible('hover')).toBeTrue();
}));
it('should hide the focus ripple on drag end if the thumb already lost focus', fakeAsync(() => {
pointerdown();
blur();
pointerup();
flush();
expect(isRippleVisible('focus')).toBeFalse();
}));
});
describe('slider with set value', () => {
let slider: MatSlider;
let input: MatSliderThumb;
beforeEach(waitForAsync(() => {
const fixture = createComponent(SliderWithValue);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
}));
it('should set the default value from the attribute', () => {
checkInput(input, {min: 0, max: 100, value: 50, translateX: 150});
});
it('should update the value', fakeAsync(() => {
slideToValue(slider, input, 75);
checkInput(input, {min: 0, max: 100, value: 75, translateX: 225});
}));
});
describe('range slider with set value', () => {
let slider: MatSlider;
let startInput: MatSliderThumb;
let endInput: MatSliderThumb;
beforeEach(waitForAsync(() => {
const fixture = createComponent(RangeSliderWithValue);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
}));
it('should set the correct initial values', fakeAsync(() => {
checkInput(startInput, {min: 0, max: 75, value: 25, translateX: 75});
checkInput(endInput, {min: 25, max: 100, value: 75, translateX: 225});
}));
it('should update the start value', fakeAsync(() => {
checkInput(startInput, {min: 0, max: 75, value: 25, translateX: 75});
checkInput(endInput, {min: 25, max: 100, value: 75, translateX: 225});
slideToValue(slider, startInput, 30);
checkInput(startInput, {min: 0, max: 75, value: 30, translateX: 90});
checkInput(endInput, {min: 30, max: 100, value: 75, translateX: 225});
}));
it('should update the end value', fakeAsync(() => {
slideToValue(slider, endInput, 77);
checkInput(startInput, {min: 0, max: 77, value: 25, translateX: 75});
checkInput(endInput, {min: 25, max: 100, value: 77, translateX: 231});
}));
});
describe('slider with set step', () => {
let fixture: ComponentFixture<SliderWithStep>;
let slider: MatSlider;
let input: MatSliderThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(SliderWithStep);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
}));
it('should update to the value based on the step', fakeAsync(() => {
slideToValue(slider, input, 30);
expect(input.value).toBe(25);
}));
it('should not add decimals to the value if it is a whole number', fakeAsync(() => {
fixture.componentInstance.step = 0.1;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, input, 11);
expect(input.value).toBe(11);
}));
it('should truncate long decimal values when using a decimal step', fakeAsync(() => {
fixture.componentInstance.step = 0.5;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, input, 55.555);
expect(input.value).toBe(55.5);
}));
it('should update the value on step change', fakeAsync(() => {
slideToValue(slider, input, 30);
fixture.componentInstance.step = 50;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(input.value).toBe(50);
}));
});
describe('range slider with set step', () => {
let fixture: ComponentFixture<RangeSliderWithStep>;
let slider: MatSlider;
let startInput: MatSliderThumb;
let endInput: MatSliderThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(RangeSliderWithStep);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
}));
it('should set the correct start value on slide', fakeAsync(() => {
slideToValue(slider, startInput, 30);
expect(startInput.value).toBe(25);
}));
it('should set the correct end value on slide', fakeAsync(() => {
slideToValue(slider, endInput, 45);
expect(endInput.value).toBe(50);
}));
it('should not add decimals to the end value if it is a whole number', fakeAsync(() => {
fixture.componentInstance.step = 0.1;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, endInput, 11);
expect(endInput.value).toBe(11);
}));
it('should not add decimals to the start value if it is a whole number', fakeAsync(() => {
fixture.componentInstance.step = 0.1;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, startInput, 11);
expect(startInput.value).toBe(11);
}));
it('should truncate long decimal start values when using a decimal step', fakeAsync(() => {
fixture.componentInstance.step = 0.1;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, startInput, 33.666);
expect(startInput.value).toBe(33.7);
}));
it('should truncate long decimal end values when using a decimal step', fakeAsync(() => {
fixture.componentInstance.step = 0.1;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, endInput, 33.6666);
expect(endInput.value).toBe(33.7);
}));
describe('should handle step changes', () => {
it('where the new start tx is greater than the old end tx', fakeAsync(() => {
fixture.componentInstance.step = 0;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, startInput, 45);
slideToValue(slider, endInput, 46);
checkInput(startInput, {min: 0, max: 46, value: 45, translateX: 135});
checkInput(endInput, {min: 45, max: 100, value: 46, translateX: 138});
fixture.componentInstance.step = 50;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 0, max: 50, value: 50, translateX: 150});
checkInput(endInput, {min: 50, max: 100, value: 50, translateX: 150});
}));
it('where the new end tx is less than the old start tx', fakeAsync(() => {
fixture.componentInstance.step = 0;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
slideToValue(slider, startInput, 21);
slideToValue(slider, endInput, 22);
checkInput(startInput, {min: 0, max: 22, value: 21, translateX: 63});
checkInput(endInput, {min: 21, max: 100, value: 22, translateX: 66});
fixture.componentInstance.step = 50;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
checkInput(startInput, {min: 0, max: 0, value: 0, translateX: 0});
checkInput(endInput, {min: 0, max: 100, value: 0, translateX: 0});
}));
});
});
describe('slider with custom thumb label formatting', () => {
let fixture: ComponentFixture<DiscreteSliderWithDisplayWith>;
let slider: MatSlider;
let input: MatSliderThumb;
let valueIndicatorTextElement: Element;
beforeEach(() => {
fixture = createComponent(DiscreteSliderWithDisplayWith);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider))!;
const sliderNativeElement = sliderDebugElement.nativeElement;
slider = sliderDebugElement.componentInstance;
valueIndicatorTextElement = sliderNativeElement.querySelector(
'.mdc-slider__value-indicator-text',
)!;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
});
it('should set the aria-valuetext attribute with the given `displayWith` function', fakeAsync(() => {
expect(input._hostElement.getAttribute('aria-valuetext')).toBe('$1');
setValueByClick(slider, input, 199);
fixture.detectChanges();
flush();
expect(input._hostElement.getAttribute('aria-valuetext')).toBe('$199');
}));
it('should invoke the passed-in `displayWith` function with the value', fakeAsync(() => {
spyOn(slider, 'displayWith').and.callThrough();
setValueByClick(slider, input, 199);
expect(slider.displayWith).toHaveBeenCalledWith(199);
}));
it('should format the thumb label based on the passed-in `displayWith` function', fakeAsync(() => {
setValueByClick(slider, input, 149);
fixture.detectChanges();
expect(valueIndicatorTextElement.textContent).toBe('$149');
}));
});
describe('range slider with custom thumb label formatting', () => {
let fixture: ComponentFixture<DiscreteRangeSliderWithDisplayWith>;
let slider: MatSlider;
let startValueIndicatorTextElement: Element;
let endValueIndicatorTextElement: Element;
let startInput: MatSliderThumb;
let endInput: MatSliderThumb;
beforeEach(() => {
fixture = createComponent(DiscreteRangeSliderWithDisplayWith);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider))!;
slider = sliderDebugElement.componentInstance;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
const startThumbElement = slider._getThumb(_MatThumb.START)._hostElement;
const endThumbElement = slider._getThumb(_MatThumb.END)._hostElement;
startValueIndicatorTextElement = startThumbElement.querySelector(
'.mdc-slider__value-indicator-text',
)!;
endValueIndicatorTextElement = endThumbElement.querySelector(
'.mdc-slider__value-indicator-text',
)!;
});
it('should set the aria-valuetext attribute with the given `displayWith` function', fakeAsync(() => {
expect(startInput._hostElement.getAttribute('aria-valuetext')).toBe('$1');
expect(endInput._hostElement.getAttribute('aria-valuetext')).toBe('$200');
setValueByClick(slider, startInput, 25);
setValueByClick(slider, endInput, 81);
expect(startInput._hostElement.getAttribute('aria-valuetext')).toBe('$25');
expect(endInput._hostElement.getAttribute('aria-valuetext')).toBe('$81');
}));
it('should invoke the passed-in `displayWith` function with the start value', fakeAsync(() => {
spyOn(slider, 'displayWith').and.callThrough();
setValueByClick(slider, startInput, 197);
expect(slider.displayWith).toHaveBeenCalledWith(197);
}));
it('should invoke the passed-in `displayWith` function with the end value', fakeAsync(() => {
spyOn(slider, 'displayWith').and.callThrough();
setValueByClick(slider, endInput, 72);
expect(slider.displayWith).toHaveBeenCalledWith(72);
}));
it('should format the start thumb label based on the passed-in `displayWith` function', fakeAsync(() => {
setValueByClick(slider, startInput, 120);
fixture.detectChanges();
expect(startValueIndicatorTextElement.textContent).toBe('$120');
}));
it('should format the end thumb label based on the passed-in `displayWith` function', fakeAsync(() => {
setValueByClick(slider, endInput, 70);
fixture.detectChanges();
expect(endValueIndicatorTextElement.textContent).toBe('$70');
}));
});
describe('slider with value property binding', () => {
let fixture: ComponentFixture<SliderWithOneWayBinding>;
let input: MatSliderThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(SliderWithOneWayBinding);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
const slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
}));
it('should update when bound value changes', () => {
fixture.componentInstance.value = 75;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(input.value).toBe(75);
});
});
describe('range slider with value property binding', () => {
let fixture: ComponentFixture<RangeSliderWithOneWayBinding>;
let startInput: MatSliderThumb;
let endInput: MatSliderThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(RangeSliderWithOneWayBinding);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
const slider = sliderDebugElement.componentInstance;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
}));
it('should update when bound start value changes', () => {
fixture.componentInstance.startValue = 30;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(startInput.value).toBe(30);
});
it('should update when bound end value changes', () => {
fixture.componentInstance.endValue = 70;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(endInput.value).toBe(70);
});
it('should update the input width when the start value changes', () => {
const startInputEl = startInput._elementRef.nativeElement;
const endInputEl = endInput._elementRef.nativeElement;
const startInputWidthBefore = startInputEl.getBoundingClientRect().width;
const endInputWidthBefore = endInputEl.getBoundingClientRect().width;
fixture.componentInstance.startValue = 10;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
const startInputWidthAfter = startInputEl.getBoundingClientRect().width;
const endInputWidthAfter = endInputEl.getBoundingClientRect().width;
expect(startInputWidthBefore).not.toBe(startInputWidthAfter);
expect(endInputWidthBefore).not.toBe(endInputWidthAfter);
});
it('should update the input width when the end value changes', () => {
const startInputEl = startInput._elementRef.nativeElement;
const endInputEl = endInput._elementRef.nativeElement;
const startInputWidthBefore = startInputEl.getBoundingClientRect().width;
const endInputWidthBefore = endInputEl.getBoundingClientRect().width;
fixture.componentInstance.endValue = 90;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
const startInputWidthAfter = startInputEl.getBoundingClientRect().width;
const endInputWidthAfter = endInputEl.getBoundingClientRect().width;
expect(startInputWidthBefore).not.toBe(startInputWidthAfter);
expect(endInputWidthBefore).not.toBe(endInputWidthAfter);
});
});
describe('slider with direction', () => {
let slider: MatSlider;
let input: MatSliderThumb;
beforeEach(waitForAsync(() => {
const fixture = createComponent(StandardSlider, [
{
provide: Directionality,
useValue: {value: 'rtl', change: of()},
},
]);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
}));
it('works in RTL languages', fakeAsync(() => {
setValueByClick(slider, input, 25, true);
checkInput(input, {min: 0, max: 100, value: 75, translateX: 75});
}));
});
describe('range slider with direction', () => {
let slider: MatSlider;
let startInput: MatSliderThumb;
let endInput: MatSliderThumb;
beforeEach(waitForAsync(() => {
const fixture = createComponent(StandardRangeSlider, [
{
provide: Directionality,
useValue: {value: 'rtl', change: of()},
},
]);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
}));
it('works in RTL languages', fakeAsync(() => {
setValueByClick(slider, startInput, 90, true);
checkInput(startInput, {min: 0, max: 100, value: 10, translateX: 270});
setValueByClick(slider, endInput, 10, true);
checkInput(endInput, {min: 10, max: 100, value: 90, translateX: 30});
}));
});
describe('slider with ngModel', () => {
let fixture: ComponentFixture<SliderWithNgModel>;
let slider: MatSlider;
let input: MatSliderThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(SliderWithNgModel);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
}));
it('should update the model', fakeAsync(() => {
slideToValue(slider, input, 19);
fixture.detectChanges();
expect(fixture.componentInstance.val).toBe(19);
checkInput(input, {min: 0, max: 100, value: 19, translateX: 57});
}));
it('should update the slider', fakeAsync(() => {
fixture.componentInstance.val = 20;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
flush();
checkInput(input, {min: 0, max: 100, value: 20, translateX: 60});
}));
it('should be able to reset a slider by setting the model back to undefined', fakeAsync(() => {
fixture.componentInstance.val = 5;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
flush();
checkInput(input, {min: 0, max: 100, value: 5, translateX: 15});
fixture.componentInstance.val = undefined;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
flush();
checkInput(input, {min: 0, max: 100, value: 0, translateX: 0});
}));
});
describe('range slider with ngModel', () => {
let slider: MatSlider;
let fixture: ComponentFixture<RangeSliderWithNgModel>;
let startInput: MatSliderThumb;
let endInput: MatSliderThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(RangeSliderWithNgModel);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
}));
it('should update the models on input value changes', fakeAsync(() => {
slideToValue(slider, startInput, 25);
fixture.detectChanges();
flush();
checkInput(startInput, {min: 0, max: 100, value: 25, translateX: 75});
slideToValue(slider, endInput, 75);
fixture.detectChanges();
flush();
checkInput(endInput, {min: 25, max: 100, value: 75, translateX: 225});
}));
it('should update the thumbs on ngModel value change', fakeAsync(() => {
fixture.componentInstance.startVal = 50;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
flush();
checkInput(startInput, {min: 0, max: 100, value: 50, translateX: 150});
fixture.componentInstance.endVal = 75;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
flush();
checkInput(endInput, {min: 50, max: 100, value: 75, translateX: 225});
}));
it('should be able to reset a start input', fakeAsync(() => {
fixture.componentInstance.startVal = 5;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
flush();
checkInput(startInput, {min: 0, max: 100, value: 5, translateX: 15});
fixture.componentInstance.startVal = undefined;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
flush();
checkInput(startInput, {min: 0, max: 100, value: 0, translateX: 0});
}));
it('should be able to reset an end input', fakeAsync(() => {
fixture.componentInstance.endVal = 99;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
flush();
checkInput(endInput, {min: 0, max: 100, value: 99, translateX: 297});
fixture.componentInstance.endVal = undefined;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
flush();
checkInput(endInput, {min: 0, max: 100, value: 0, translateX: 0});
}));
});
describe('range slider w/ NgModel edge case', () => {
it('should initialize correctly despite NgModel `null` bug', fakeAsync(() => {
const fixture = createComponent(RangeSliderWithNgModelEdgeCase);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
const slider = sliderDebugElement.componentInstance;
const startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
const endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
flush();
checkInput(startInput, {min: -1, max: -0.3, value: -0.7, translateX: 90});
checkInput(endInput, {min: -0.7, max: 0, value: -0.3, translateX: 210});
}));
});
describe('slider as a custom form control', () => {
let fixture: ComponentFixture<SliderWithFormControl>;
let slider: MatSlider;
let input: MatSliderThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(SliderWithFormControl);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
}));
it('should update the control on slide', fakeAsync(() => {
expect(fixture.componentInstance.control.value).toBe(0);
slideToValue(slider, input, 19);
expect(fixture.componentInstance.control.value).toBe(19);
}));
it('should update the value when the control is set', () => {
expect(input.value).toBe(0);
fixture.componentInstance.control.setValue(7);
checkInput(input, {min: 0, max: 100, value: 7, translateX: 21});
});
it('should update the disabled state when control is disabled', () => {
expect(slider.disabled).toBe(false);
fixture.componentInstance.control.disable();
expect(slider.disabled).toBe(true);
});
it('should update the disabled state when the control is enabled', () => {
slider.disabled = true;
fixture.componentInstance.control.enable();
expect(slider.disabled).toBe(false);
});
it('should have the correct control state initially and after interaction', fakeAsync(() => {
let sliderControl = fixture.componentInstance.control;
// The control should start off valid, pristine, and untouched.
expect(sliderControl.valid).toBe(true);
expect(sliderControl.pristine).toBe(true);
expect(sliderControl.touched).toBe(false);
// After changing the value, the control should become dirty (not pristine),
// but remain untouched.
setValueByClick(slider, input, 50);
expect(sliderControl.valid).toBe(true);
expect(sliderControl.pristine).toBe(false);
expect(sliderControl.touched).toBe(false);
// If the control has been visited due to interaction, the control should remain
// dirty and now also be touched.
input.blur();
fixture.detectChanges();
expect(sliderControl.valid).toBe(true);
expect(sliderControl.pristine).toBe(false);
expect(sliderControl.touched).toBe(true);
}));
});
describe('slider as a custom form control', () => {
let fixture: ComponentFixture<RangeSliderWithFormControl>;
let slider: MatSlider;
let startInput: MatSliderThumb;
let endInput: MatSliderThumb;
beforeEach(waitForAsync(() => {
fixture = createComponent(RangeSliderWithFormControl);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
}));
it('should update the start input control on slide', fakeAsync(() => {
expect(fixture.componentInstance.startInputControl.value).toBe(0);
slideToValue(slider, startInput, 20);
expect(fixture.componentInstance.startInputControl.value).toBe(20);
}));
it('should update the end input control on slide', fakeAsync(() => {
expect(fixture.componentInstance.endInputControl.value).toBe(100);
slideToValue(slider, endInput, 80);
expect(fixture.componentInstance.endInputControl.value).toBe(80);
}));
it('should update the start input value when the start input control is set', () => {
expect(startInput.value).toBe(0);
fixture.componentInstance.startInputControl.setValue(10);
checkInput(startInput, {min: 0, max: 100, value: 10, translateX: 30});
});
it('should update the end input value when the end input control is set', () => {
expect(endInput.value).toBe(100);
fixture.componentInstance.endInputControl.setValue(90);
checkInput(endInput, {min: 0, max: 100, value: 90, translateX: 270});
});
it('should update the disabled state if the start input control is disabled', () => {
expect(slider.disabled).toBe(false);
fixture.componentInstance.startInputControl.disable();
expect(slider.disabled).toBe(true);
});
it('should update the disabled state if the end input control is disabled', () => {
expect(slider.disabled).toBe(false);
fixture.componentInstance.endInputControl.disable();
expect(slider.disabled).toBe(true);
});
it('should update the disabled state when both input controls are enabled', () => {
slider.disabled = true;
fixture.componentInstance.startInputControl.enable();
expect(slider.disabled).toBe(false);
fixture.componentInstance.endInputControl.enable();
expect(slider.disabled).toBe(false);
});
it('should have the correct start input control state initially and after interaction', fakeAsync(() => {
let sliderControl = fixture.componentInstance.startInputControl;
// The control should start off valid, pristine, and untouched.
expect(sliderControl.valid).toBe(true);
expect(sliderControl.pristine).toBe(true);
expect(sliderControl.touched).toBe(false);
// After changing the value, the control should become dirty (not pristine),
// but remain untouched.
setValueByClick(slider, startInput, 25);
expect(sliderControl.valid).toBe(true);
expect(sliderControl.pristine).toBe(false);
expect(sliderControl.touched).toBe(false);
// If the control has been visited due to interaction, the control should remain
// dirty and now also be touched.
startInput.blur();
fixture.detectChanges();
expect(sliderControl.valid).toBe(true);
expect(sliderControl.pristine).toBe(false);
expect(sliderControl.touched).toBe(true);
}));
it('should have the correct start input control state initially and after interaction', fakeAsync(() => {
let sliderControl = fixture.componentInstance.endInputControl;
// The control should start off valid, pristine, and untouched.
expect(sliderControl.valid).toBe(true);
expect(sliderControl.pristine).toBe(true);
expect(sliderControl.touched).toBe(false);
// After changing the value, the control should become dirty (not pristine),
// but remain untouched.
setValueByClick(slider, endInput, 75);
expect(sliderControl.valid).toBe(true);
expect(sliderControl.pristine).toBe(false);
expect(sliderControl.touched).toBe(false);
// If the control has been visited due to interaction, the control should remain
// dirty and now also be touched.
endInput.blur();
fixture.detectChanges();
expect(sliderControl.valid).toBe(true);
expect(sliderControl.pristine).toBe(false);
expect(sliderControl.touched).toBe(true);
}));
});
describe('slider with a two-way binding', () => {
let input: MatSliderThumb;
let slider: MatSlider;
let fixture: ComponentFixture<SliderWithTwoWayBinding>;
beforeEach(() => {
fixture = createComponent(SliderWithTwoWayBinding);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
input = slider._getInput(_MatThumb.END) as MatSliderThumb;
});
it('should sync the value binding in both directions', fakeAsync(() => {
checkInput(input, {min: 0, max: 100, value: 0, step: 1, translateX: 0});
slideToValue(slider, input, 10);
expect(fixture.componentInstance.value).toBe(10);
checkInput(input, {min: 0, max: 100, value: 10, step: 1, translateX: 30});
fixture.componentInstance.value = 20;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(fixture.componentInstance.value).toBe(20);
checkInput(input, {min: 0, max: 100, value: 20, step: 1, translateX: 60});
}));
});
describe('range slider with a two-way binding', () => {
let slider: MatSlider;
let startInput: MatSliderRangeThumb;
let endInput: MatSliderRangeThumb;
let fixture: ComponentFixture<RangeSliderWithTwoWayBinding>;
beforeEach(waitForAsync(() => {
fixture = createComponent(RangeSliderWithTwoWayBinding);
fixture.detectChanges();
const sliderDebugElement = fixture.debugElement.query(By.directive(MatSlider));
slider = sliderDebugElement.componentInstance;
endInput = slider._getInput(_MatThumb.END) as MatSliderRangeThumb;
startInput = slider._getInput(_MatThumb.START) as MatSliderRangeThumb;
}));
it('should sync the start value binding in both directions', fakeAsync(() => {
expect(fixture.componentInstance.startValue).toBe(0);
expect(startInput.value).toBe(0);
slideToValue(slider, startInput, 10);
expect(fixture.componentInstance.startValue).toBe(10);
expect(startInput.value).toBe(10);
fixture.componentInstance.startValue = 20;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(fixture.componentInstance.startValue).toBe(20);
expect(startInput.value).toBe(20);
}));
it('should sync the end value binding in both directions', fakeAsync(() => {
expect(fixture.componentInstance.endValue).toBe(100);
expect(endInput.value).toBe(100);
slideToValue(slider, endInput, 90);
expect(fixture.componentInstance.endValue).toBe(90);
expect(endInput.value).toBe(90);
fixture.componentInstance.endValue = 80;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(fixture.componentInstance.endValue).toBe(80);
expect(endInput.value).toBe(80);
}));
});
});
const SLIDER_STYLES = ['.mat-mdc-slider { width: 300px; }'];
@Component({
template: `
<mat-slider>
<input matSliderThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class StandardSlider {}
@Component({
template: `
<mat-slider>
<input matSliderStartThumb>
<input matSliderEndThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class StandardRangeSlider {}
@Component({
template: `
<mat-slider disabled>
<input matSliderThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class DisabledSlider {}
@Component({
template: `
<mat-slider disabled>
<input matSliderStartThumb>
<input matSliderEndThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class DisabledRangeSlider {}
@Component({
template: `
<mat-slider [min]="min" [max]="max">
<input matSliderThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class SliderWithMinAndMax {
min = 25;
max = 75;
}
@Component({
template: `
<mat-slider [min]="min" [max]="max">
<input matSliderStartThumb>
<input matSliderEndThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class RangeSliderWithMinAndMax {
min = 25;
max = 75;
}
@Component({
template: `
<mat-slider>
<input value="50" matSliderThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class SliderWithValue {}
@Component({
template: `
<mat-slider>
<input value="25" matSliderStartThumb>
<input value="75" matSliderEndThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class RangeSliderWithValue {}
@Component({
template: `
<mat-slider [step]="step">
<input matSliderThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class SliderWithStep {
step = 25;
}
@Component({
template: `
<mat-slider [step]="step">
<input matSliderStartThumb>
<input matSliderEndThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class RangeSliderWithStep {
step = 25;
}
@Component({
template: `
<mat-slider [displayWith]="displayWith" min="1" max="200" discrete>
<input matSliderThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class DiscreteSliderWithDisplayWith {
displayWith(v: number) {
return `$${v}`;
}
}
@Component({
template: `
<mat-slider [displayWith]="displayWith" min="1" max="200" discrete>
<input matSliderStartThumb>
<input matSliderEndThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class DiscreteRangeSliderWithDisplayWith {
displayWith(v: number) {
return `$${v}`;
}
}
@Component({
template: `
<mat-slider>
<input [value]="value" matSliderThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class SliderWithOneWayBinding {
value = 50;
}
@Component({
template: `
<mat-slider>
<input [value]="startValue" matSliderStartThumb>
<input [value]="endValue" matSliderEndThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class RangeSliderWithOneWayBinding {
startValue = 25;
endValue = 75;
}
@Component({
template: `
<mat-slider>
<input [(ngModel)]="val" matSliderThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class SliderWithNgModel {
@ViewChild(MatSlider) slider: MatSlider;
val: number | undefined = 0;
}
@Component({
template: `
<mat-slider>
<input [(ngModel)]="startVal" matSliderStartThumb>
<input [(ngModel)]="endVal" matSliderEndThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class RangeSliderWithNgModel {
@ViewChild(MatSlider) slider: MatSlider;
startVal: number | undefined = 0;
endVal: number | undefined = 100;
}
@Component({
template: `
<mat-slider min="-1" max="0" step="0.1">
<input [(ngModel)]="startValue" matSliderStartThumb />
<input [(ngModel)]="endValue" matSliderEndThumb />
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class RangeSliderWithNgModelEdgeCase {
@ViewChild(MatSlider) slider: MatSlider;
startValue: number = -0.7;
endValue: number = -0.3;
}
@Component({
template: `
<mat-slider>
<input [formControl]="control" matSliderThumb>
</mat-slider>`,
styles: SLIDER_STYLES,
standalone: false,
})
class SliderWithFormControl {
control = new FormControl(0);
}
@Component({
template: `
<mat-slider>
<input [formControl]="startInputControl" matSliderStartThumb>
<input [formControl]="endInputControl" matSliderEndThumb>
</mat-slider>`,
styles: SLIDER_STYLES,
standalone: false,
})
class RangeSliderWithFormControl {
startInputControl = new FormControl(0);
endInputControl = new FormControl(100);
}
@Component({
template: `
<mat-slider>
<input [(value)]="value" matSliderThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class SliderWithTwoWayBinding {
value = 0;
}
@Component({
template: `
<mat-slider>
<input [(value)]="startValue" matSliderStartThumb>
<input [(value)]="endValue" matSliderEndThumb>
</mat-slider>
`,
styles: SLIDER_STYLES,
standalone: false,
})
class RangeSliderWithTwoWayBinding {
@ViewChild(MatSlider) slider: MatSlider;
@ViewChildren(MatSliderThumb) sliderInputs: QueryList<MatSliderThumb>;
startValue = 0;
endValue = 100;
}
/** Clicks on the MatSlider at the coordinates corresponding to the given value. */
function setValueByClick(
slider: MatSlider,
input: MatSliderThumb,
value: number,
isRtl: boolean = false,
) {
const inputElement = input._elementRef.nativeElement;
const val = isRtl ? slider.max - value : value;
const {x, y} = getCoordsForValue(slider, value);
dispatchPointerEvent(inputElement, 'pointerdown', x, y);
input.value = val;
dispatchEvent(input._hostElement, new Event('input'));
input.focus();
dispatchPointerEvent(inputElement, 'pointerup', x, y);
dispatchEvent(input._hostElement, new Event('change'));
flush();
}
/** Slides the MatSlider's thumb to the given value. */
function slideToValue(slider: MatSlider, input: MatSliderThumb, value: number) {
const sliderElement = slider._elementRef.nativeElement;
const {x: startX, y: startY} = getCoordsForValue(slider, input.value);
const {x: endX, y: endY} = getCoordsForValue(slider, value);
dispatchPointerEvent(sliderElement, 'pointerdown', startX, startY);
input.focus();
dispatchPointerEvent(sliderElement, 'pointermove', endX, endY);
input._hostElement.value = `${value}`;
dispatchEvent(input._hostElement, new Event('input'));
dispatchPointerEvent(sliderElement, 'pointerup', endX, endY);
dispatchEvent(input._hostElement, new Event('change'));
tick(10);
}
/** Returns the x and y coordinates for the given slider value. */
function getCoordsForValue(slider: MatSlider, value: number): Point {
const {min, max} = slider;
const percent = (value - min) / (max - min);
const {top, left, width, height} = slider._elementRef.nativeElement.getBoundingClientRect();
const x = width * percent + left;
const y = top + height / 2;
return {x, y};
}