/** * @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 {$, browser, by, element, ElementFinder} from 'protractor'; import {logging} from 'selenium-webdriver'; describe('MatSlider', () => { const getStandardSlider = () => element(by.id('standard-slider')); const getDisabledSlider = () => element(by.id('disabled-slider')); const getRangeSlider = () => element(by.id('range-slider')); beforeEach(async () => await browser.get('/slider')); describe('standard slider', async () => { let slider: ElementFinder; beforeEach(() => { slider = getStandardSlider(); }); it('should update the value on click', async () => { await setValueByClick(slider, 49); expect(await getSliderValue(slider, Thumb.END)).toBe(49); }); it('should update the value on slide', async () => { await slideToValue(slider, 35, Thumb.END); expect(await getSliderValue(slider, Thumb.END)).toBe(35); }); it('should display the value indicator when focused', async () => { await focusSliderThumb(slider, Thumb.END); const rect: DOMRect = await browser.executeScript( 'return arguments[0].getBoundingClientRect();', $('.mdc-slider__value-indicator'), ); expect(rect.width).not.toBe(0); expect(rect.height).not.toBe(0); await browser.actions().mouseUp().perform(); }); it('should not cause passive event listener errors when changing the value', async () => { // retrieving the logs clears the collection await browser.manage().logs().get('browser'); await setValueByClick(slider, 15); expect(await browser.manage().logs().get('browser')).not.toContain( jasmine.objectContaining({level: logging.Level.SEVERE}), ); }); }); describe('disabled slider', async () => { let slider: ElementFinder; beforeEach(() => { slider = getDisabledSlider(); }); it('should not update the value on click', async () => { await setValueByClick(slider, 15); expect(await getSliderValue(slider, Thumb.END)).not.toBe(15); }); it('should not update the value on slide', async () => { await slideToValue(slider, 35, Thumb.END); expect(await getSliderValue(slider, Thumb.END)).not.toBe(35); }); }); describe('range slider', async () => { let slider: ElementFinder; beforeEach(() => { slider = getRangeSlider(); }); it('should update the start thumb value on slide', async () => { await slideToValue(slider, 35, Thumb.START); expect(await getSliderValue(slider, Thumb.START)).toBe(35); }); it('should update the end thumb value on slide', async () => { await slideToValue(slider, 55, Thumb.END); expect(await getSliderValue(slider, Thumb.END)).toBe(55); }); it( 'should update the start thumb value on click between thumbs ' + 'but closer to the start thumb', async () => { await setValueByClick(slider, 49); expect(await getSliderValue(slider, Thumb.START)).toBe(49); expect(await getSliderValue(slider, Thumb.END)).toBe(100); }, ); it( 'should update the end thumb value on click between thumbs ' + 'but closer to the end thumb', async () => { await setValueByClick(slider, 51); expect(await getSliderValue(slider, Thumb.START)).toBe(0); expect(await getSliderValue(slider, Thumb.END)).toBe(51); }, ); }); }); /** Returns the current value of the slider. */ async function getSliderValue(slider: ElementFinder, thumbPosition: Thumb): Promise { const inputs = await slider.all(by.css('.mdc-slider__input')); return thumbPosition === Thumb.END ? Number(await inputs[inputs.length - 1].getAttribute('value')) : Number(await inputs[0].getAttribute('value')); } /** Focuses on the MatSlider at the coordinates corresponding to the given thumb. */ async function focusSliderThumb(slider: ElementFinder, thumbPosition: Thumb): Promise { const webElement = await slider.getWebElement(); const coords = await getCoordsForValue(slider, await getSliderValue(slider, thumbPosition)); return await browser.actions().mouseMove(webElement, coords).mouseDown().perform(); } /** Clicks on the MatSlider at the coordinates corresponding to the given value. */ async function setValueByClick(slider: ElementFinder, value: number): Promise { return clickElementAtPoint(slider, await getCoordsForValue(slider, value)); } /** Clicks on the MatSlider at the coordinates corresponding to the given value. */ async function slideToValue( slider: ElementFinder, value: number, thumbPosition: Thumb, ): Promise { const webElement = await slider.getWebElement(); const startCoords = await getCoordsForValue(slider, await getSliderValue(slider, thumbPosition)); const endCoords = await getCoordsForValue(slider, value); return await browser .actions() .mouseMove(webElement, startCoords) .mouseDown() .mouseMove(webElement, endCoords) .mouseUp() .perform(); } /** Returns the x and y coordinates for the given slider value. */ async function getCoordsForValue(slider: ElementFinder, value: number): Promise { const inputs = await slider.all(by.css('.mdc-slider__input')); const min = Number(await inputs[0].getAttribute('min')); const max = Number(await inputs[inputs.length - 1].getAttribute('max')); const percent = (value - min) / (max - min); const {width, height} = await slider.getSize(); // NOTE: We use Math.round here because protractor silently breaks if you pass in an imprecise // floating point number with lots of decimals. This allows us to avoid the headache but it may // cause some innaccuracies in places where these decimals mean the difference between values. const x = Math.round(width * percent); const y = Math.round(height / 2); return {x, y}; } enum Thumb { START = 1, END = 2, } interface Point { x: number; y: number; } /** * Clicks an element at a specific point. Useful if there's another element * that covers part of the target and can catch the click. */ async function clickElementAtPoint(target: ElementFinder, coords: Point) { const webElement = await target.getWebElement(); await browser.actions().mouseMove(webElement, coords).click().perform(); }