From 7b78c16275cfa5a1697bfa7bc6e171d9f5a97ad9 Mon Sep 17 00:00:00 2001 From: Nikolay Deshev Date: Mon, 11 Jan 2021 16:46:06 +0200 Subject: [PATCH] feat(ui5-slider): focus and keyboard handling implementation (#2614) --- packages/base/src/Keys.js | 18 ++ packages/main/src/RangeSlider.js | 7 +- packages/main/src/Slider.hbs | 8 +- packages/main/src/Slider.js | 54 ++++- packages/main/src/SliderBase.hbs | 4 +- packages/main/src/SliderBase.js | 191 +++++++++++++++++- packages/main/src/themes/SliderBase.css | 7 +- .../src/themes/base/SliderBase-parameters.css | 2 + packages/main/test/pages/Slider.html | 3 + packages/main/test/specs/Slider.spec.js | 183 ++++++++++++++++- 10 files changed, 458 insertions(+), 19 deletions(-) diff --git a/packages/base/src/Keys.js b/packages/base/src/Keys.js index be7d4b69bf76..25c3ae34c602 100644 --- a/packages/base/src/Keys.js +++ b/packages/base/src/Keys.js @@ -119,6 +119,14 @@ const isUp = event => (event.key ? (event.key === "ArrowUp" || event.key === "Up const isDown = event => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && !hasModifierKeys(event); +const isLeftCtrl = event => (event.key ? (event.key === "ArrowLeft" || event.key === "Left") : event.keyCode === KeyCodes.ARROW_LEFT) && checkModifierKeys(event, true, false, false); + +const isRightCtrl = event => (event.key ? (event.key === "ArrowRight" || event.key === "Right") : event.keyCode === KeyCodes.ARROW_RIGHT) && checkModifierKeys(event, true, false, false); + +const isUpCtrl = event => (event.key ? (event.key === "ArrowUp" || event.key === "Up") : event.keyCode === KeyCodes.ARROW_UP) && checkModifierKeys(event, true, false, false); + +const isDownCtrl = event => (event.key ? (event.key === "ArrowDown" || event.key === "Down") : event.keyCode === KeyCodes.ARROW_DOWN) && checkModifierKeys(event, true, false, false); + const isHome = event => (event.key ? event.key === "Home" : event.keyCode === KeyCodes.HOME) && !hasModifierKeys(event); const isEnd = event => (event.key ? event.key === "End" : event.keyCode === KeyCodes.END) && !hasModifierKeys(event); @@ -149,6 +157,10 @@ const isPageUpShiftCtrl = event => (event.key ? event.key === "PageUp" : event.k const isPageDownShiftCtrl = event => (event.key ? event.key === "PageDown" : event.keyCode === KeyCodes.PAGE_DOWN) && checkModifierKeys(event, true, false, true); +const isPlus = event => (event.key ? event.key === "+" : event.keyCode === KeyCodes.PLUS) || (event.keyCode === KeyCodes.NUMPAD_PLUS && !hasModifierKeys(event)); + +const isMinus = event => (event.key ? event.key === "-" : event.keyCode === KeyCodes.MINUS) || (event.keyCode === KeyCodes.NUMPAD_MINUS && !hasModifierKeys(event)); + const isShow = event => { if (event.key) { return isF4(event) || isShowByArrows(event); @@ -182,8 +194,14 @@ export { isRight, isUp, isDown, + isLeftCtrl, + isRightCtrl, + isUpCtrl, + isDownCtrl, isHome, isEnd, + isPlus, + isMinus, isHomeCtrl, isEndCtrl, isEscape, diff --git a/packages/main/src/RangeSlider.js b/packages/main/src/RangeSlider.js index c3815f472dc9..83e221f8aca5 100644 --- a/packages/main/src/RangeSlider.js +++ b/packages/main/src/RangeSlider.js @@ -1,8 +1,7 @@ import Float from "@ui5/webcomponents-base/dist/types/Float.js"; import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import SliderBase from "./SliderBase.js"; - -// Template import RangeSliderTemplate from "./generated/templates/RangeSliderTemplate.lit.js"; /** @@ -99,6 +98,10 @@ class RangeSlider extends SliderBase { this.i18nBundle = getI18nBundle("@ui5/webcomponents"); } + onEnterDOM() { + ResizeHandler.register(this, this._resizeHandler); + } + get tooltipStartValue() { const stepPrecision = this.constructor._getDecimalPrecisionOfNumber(this._effectiveStep); return this.startValue.toFixed(stepPrecision); diff --git a/packages/main/src/Slider.hbs b/packages/main/src/Slider.hbs index eea8490a5ea0..e2d9e62937ad 100644 --- a/packages/main/src/Slider.hbs +++ b/packages/main/src/Slider.hbs @@ -1,7 +1,13 @@ {{>include "./SliderBase.hbs"}} {{#*inline "handles"}} -
+
{{#if showTooltip}}
{{tooltipValue}} diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index 9d722e1c7c63..57b725cd4806 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -1,5 +1,6 @@ import Float from "@ui5/webcomponents-base/dist/types/Float.js"; import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import { isEscape } from "@ui5/webcomponents-base/dist/Keys.js"; import SliderBase from "./SliderBase.js"; // Template @@ -82,6 +83,7 @@ class Slider extends SliderBase { constructor() { super(); this._stateStorage.value = null; + this._setInitialValue("value", null); this.i18nBundle = getI18nBundle("@ui5/webcomponents"); } @@ -121,6 +123,12 @@ class Slider extends SliderBase { const newValue = this.handleDownBase(event); this._valueOnInteractionStart = this.value; + // Set initial value if one is not set previously on focus in. + // It will be restored if ESC key is pressed. + if (this._getInitialValue("value") === null) { + this._setInitialValue("value", this.value); + } + // Do not yet update the Slider if press is over a handle. It will be updated if the user drags the mouse. if (!this._isHandlePressed(this.constructor.getPageXValueFromEvent(event))) { this._updateHandleAndProgress(newValue); @@ -128,6 +136,28 @@ class Slider extends SliderBase { } } + _onfocusin(event) { + // Set initial value if one is not set previously on focus in. + // It will be restored if ESC key is pressed. + if (this._getInitialValue("value") === null) { + this._setInitialValue("value", this.value); + } + } + + _onfocusout(event) { + // Prevent focusout when the focus is getting set within the slider internal + // element (on the handle), before the Slider' customElement itself is finished focusing + if (this._isFocusing()) { + this._preventFocusOut(); + return; + } + + // Reset focus state and the stored Slider's initial + // value that was saved when it was first focused in + this._setInitialValue("value", null); + } + + /** * Called when the user moves the slider * @@ -166,9 +196,7 @@ class Slider extends SliderBase { * @private */ _isHandlePressed(clientX) { - const sliderHandle = this.shadowRoot.querySelector(".ui5-slider-handle"); - const sliderHandleDomRect = sliderHandle.getBoundingClientRect(); - + const sliderHandleDomRect = this._sliderHandle.getBoundingClientRect(); return clientX >= sliderHandleDomRect.left && clientX <= sliderHandleDomRect.right; } @@ -187,6 +215,18 @@ class Slider extends SliderBase { this._handlePositionFromStart = this._progressPercentage * 100; } + _handleActionKeyPress(event) { + const min = this._effectiveMin; + const max = this._effectiveMax; + const currentValue = this.value; + const newValue = isEscape(event) ? this._getInitialValue("value") : this.constructor.clipValue(this._handleActionKeyPressBase(event, "value") + currentValue, min, max); + + if (newValue !== currentValue) { + this._updateHandleAndProgress(newValue); + this.updateValue("value", newValue); + } + } + get styles() { return { progress: { @@ -212,6 +252,10 @@ class Slider extends SliderBase { }; } + get _sliderHandle() { + return this.shadowRoot.querySelector(".ui5-slider-handle"); + } + get labelItems() { return this._labelItems; } @@ -221,6 +265,10 @@ class Slider extends SliderBase { return this.value.toFixed(stepPrecision); } + get tabIndexProgress() { + return "-1"; + } + static async onDefine() { await fetchI18nBundle("@ui5/webcomponents"); } diff --git a/packages/main/src/SliderBase.hbs b/packages/main/src/SliderBase.hbs index ed6dd2eff017..fa6f00e89de5 100644 --- a/packages/main/src/SliderBase.hbs +++ b/packages/main/src/SliderBase.hbs @@ -4,6 +4,8 @@ @touchstart="{{_ontouchstart}}" @mouseover="{{_onmouseover}}" @mouseout="{{_onmouseout}}" + @keydown="{{_onkeydown}}" + @keyup="{{_onkeyup}}" dir="{{effectiveDir}}" >
@@ -21,7 +23,7 @@ {{/if}}
-
+
{{> handles}}
diff --git a/packages/main/src/SliderBase.js b/packages/main/src/SliderBase.js index 9836de41527d..7b7a4869c26e 100644 --- a/packages/main/src/SliderBase.js +++ b/packages/main/src/SliderBase.js @@ -4,7 +4,9 @@ import Float from "@ui5/webcomponents-base/dist/types/Float.js"; import Integer from "@ui5/webcomponents-base/dist/types/Integer.js"; import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js"; import { isPhone } from "@ui5/webcomponents-base/dist/Device.js"; - +import { + isEscape, isHome, isEnd, isUp, isDown, isRight, isLeft, isUpCtrl, isDownCtrl, isRightCtrl, isLeftCtrl, isPlus, isMinus, isPageUp, isPageDown, +} from "@ui5/webcomponents-base/dist/Keys.js"; import { getTheme } from "@ui5/webcomponents-base/dist/config/Theme.js"; // Styles @@ -97,6 +99,8 @@ const metadata = { disabled: { type: Boolean, }, + + /** * @private */ @@ -110,6 +114,11 @@ const metadata = { _hiddenTickmarks: { type: Boolean, }, + _tabIndex: { + type: String, + defaultValue: "0", + noAttribute: true, + }, }, events: /** @lends sap.ui.webcomponents.main.SliderBase.prototype */ { /** @@ -192,6 +201,26 @@ class SliderBase extends UI5Element { }; } + static get ACTION_KEYS() { + return [ + isLeft, + isRight, + isUp, + isDown, + isLeftCtrl, + isRightCtrl, + isUpCtrl, + isDownCtrl, + isPlus, + isMinus, + isHome, + isEnd, + isPageUp, + isPageDown, + isEscape, + ]; + } + static get MIN_SPACE_BETWEEN_TICKMARKS() { return 8; } @@ -245,6 +274,95 @@ class SliderBase extends UI5Element { } } + /** + * Sets initial value when the component is focused in, can be restored with ESC key + * + * @private + */ + _setInitialValue(valueType, value) { + this[`_${valueType}Initial`] = value; + } + + _getInitialValue(valueType) { + return this[`_${valueType}Initial`]; + } + + _onkeydown(event) { + if (this.disabled || this._effectiveStep === 0) { + return; + } + + if (SliderBase._isActionKey(event)) { + event.preventDefault(); + + this._isUserInteraction = true; + this._handleActionKeyPress(event); + } + } + + _onkeyup(event) { + if (this.disabled) { + return; + } + + this._isUserInteraction = false; + } + + /** + * Flags if an inner element is currently being focused + * + * @private + */ + _preserveFocus(isFocusing) { + this._isInnerElementFocusing = isFocusing; + } + + /** + * Return if an inside element within the component is currently being focused + * + * @private + */ + _isFocusing() { + return this._isInnerElementFocusing; + } + + /** + * Prevent focus out when inner element within the component is currently being in process of focusing in. + * In theory this can be achieved either if the shadow root is focusable and 'delegatesFocus' attribute of + * the .attachShadow() customElement method is set to true, or if we forward it manually. + + * As we use lit-element as base of our core UI5 element class that 'delegatesFocus' property is not set to 'true' and + * we have to manage the focus here. If at some point in the future this changes, the focus delegating logic could be + * removed as it will become redundant. + * + * When we manually set the focus on mouseDown to the first focusable element inside the shadowDom, + * that inner focus (shadowRoot.activeElement) is set a moment before the global document.activeElement + * is set to the customElement (ui5-slider) causing a 'race condition'. + * + * In order for a element within the shadowRoot to be focused, the global document.activeElement MUST be the parent + * customElement of the shadow root, in our case the ui5-slider component. Because of that after our focusin of the handle, + * a focusout event fired by the browser immidiatly after, resetting the focus. Focus out must be manually prevented + * in both initial focusing and switching the focus between inner elements of the component cases. + + * Note: If we set the focus to the handle with a timeout or a bit later in time, on a mouseup or click event it will + * work fine and we will avoid the described race condition as our host customElement will be already finished focusing. + * However, that does not work for us as we need the focus to be set to the handle exactly on mousedown, + * because of the nature of the component and its available drag interactions. + * + * @private + */ + _preventFocusOut() { + this.focusInnerElement(); + } + + /** + * Manages the focus between the component's inner elements + * @protected + */ + focusInnerElement() { + this.focus(); + } + /** * Handle the responsiveness of the Slider's UI elements when resizing * @@ -275,7 +393,6 @@ class SliderBase extends UI5Element { return; } - // Check if there are any overlapping labels. // If so - only the first and the last one should be visible const labelItems = this.shadowRoot.querySelectorAll(".ui5-slider-labels li"); @@ -309,9 +426,24 @@ class SliderBase extends UI5Element { SliderBase.UP_EVENTS.forEach(upEventType => window.addEventListener(upEventType, this._upHandler)); window.addEventListener(this._moveEventType, this._moveHandler); + this._handleFocusOnMouseDown(event); return newValue; } + /** + * Forward the focus to an inner inner part within the component on press + * + * @private + */ + _handleFocusOnMouseDown(event) { + const focusedElement = this.shadowRoot.activeElement; + + if (!focusedElement || focusedElement !== event.target) { + this._preserveFocus(true); + this.focusInnerElement(); + } + } + /** * Called when the user finish interacting with the slider * Fires an change event indicating a final value change, after user interaction is finished. @@ -328,6 +460,7 @@ class SliderBase extends UI5Element { this._moveEventType = null; this._isUserInteraction = false; + this._preserveFocus(false); } /** @@ -344,6 +477,15 @@ class SliderBase extends UI5Element { } } + /** + * Goes through the key shortcuts available for the component and returns 'true' if the event is triggered by one. + * + * @private + */ + static _isActionKey(event) { + return this.ACTION_KEYS.some(actionKey => actionKey(event)); + } + /** * Locks the given value between min and max boundaries based on slider properties * @@ -596,8 +738,41 @@ class SliderBase extends UI5Element { } } - get _labels() { - return this._labelValues || []; + _handleActionKeyPressBase(event, affectedValue) { + const isUpAction = SliderBase._isIncreaseValueAction(event); + const isBigStep = SliderBase._isBigStepAction(event); + + const currentValue = this[affectedValue]; + const min = this._effectiveMin; + const max = this._effectiveMax; + + // If the action key corresponds to a long step and the slider has more than 10 normal steps, + // make a jump of 1/10th of the Slider's length, otherwise just use the normal step property. + let step = this._effectiveStep; + step = isBigStep && ((max - min) / step > 10) ? (max - min) / 10 : step; + + + if (isEnd(event)) { + return max - currentValue; + } + + if (isHome(event)) { + return (currentValue - min) * -1; + } + + return isUpAction ? step : step * -1; + } + + static _isDecreaseValueAction(event) { + return isDown(event) || isDownCtrl(event) || isLeft(event) || isLeftCtrl(event) || isMinus(event) || isPageDown(event); + } + + static _isIncreaseValueAction(event) { + return isUp(event) || isUpCtrl(event) || isRight(event) || isRightCtrl(event) || isPlus(event) || isPageUp(event); + } + + static _isBigStepAction(event) { + return isDownCtrl(event) || isUpCtrl(event) || isLeftCtrl(event) || isRightCtrl(event) || isPageUp(event) || isPageDown(event); } /** @@ -631,6 +806,10 @@ class SliderBase extends UI5Element { } } + get _labels() { + return this._labelValues || []; + } + /** * Normalizes a new step property value. * If tickmarks are enabled recreates them according to it. @@ -658,6 +837,10 @@ class SliderBase extends UI5Element { get _effectiveMax() { return Math.max(this.min, this.max); } + + get tabIndex() { + return this.disabled ? "-1" : this._tabIndex; + } } export default SliderBase; diff --git a/packages/main/src/themes/SliderBase.css b/packages/main/src/themes/SliderBase.css index 57cbaec686a0..091fd15382aa 100644 --- a/packages/main/src/themes/SliderBase.css +++ b/packages/main/src/themes/SliderBase.css @@ -20,6 +20,7 @@ box-sizing: border-box; height: 3.3125rem; padding: 1rem 0; + outline: none; touch-action: none; -ms-touch-action: pan-y; } @@ -68,11 +69,15 @@ width: var(--_ui5_slider_handle_width); } +.ui5-slider-handle:focus { + outline: var(--_ui5_slider_handle_outline); + outline-offset: var(--_ui5_slider_handle_outline_offset); +} + .ui5-slider-handle--start, .ui5-slider-handle--end { background: var(--_ui5_range_slider_handle_background); } - [dir="rtl"] .ui5-slider-handle { margin-right: var(--_ui5_slider_handle_margin_left); } diff --git a/packages/main/src/themes/base/SliderBase-parameters.css b/packages/main/src/themes/base/SliderBase-parameters.css index 112d0e7973bc..4f23504852ee 100644 --- a/packages/main/src/themes/base/SliderBase-parameters.css +++ b/packages/main/src/themes/base/SliderBase-parameters.css @@ -14,6 +14,8 @@ --_ui5_slider_handle_margin_left: -0.9725rem; --_ui5_slider_handle_hover_background: var(--sapButton_Hover_Background); --_ui5_slider_handle_hover_border: var(--sapButton_Hover_BorderColor); + --_ui5_slider_handle_outline: 1px dotted var(--sapContent_FocusColor); + --_ui5_slider_handle_outline_offset: 0.075rem; --_ui5_range_slider_handle_hover_background: rgba(var(--sapButton_Background), 0.25); --_ui5_slider_tickmark_color: #89919a; --_ui5_slider_tickmark_top: -0.375rem; diff --git a/packages/main/test/pages/Slider.html b/packages/main/test/pages/Slider.html index 2c4cf6f33933..d196f489fa92 100644 --- a/packages/main/test/pages/Slider.html +++ b/packages/main/test/pages/Slider.html @@ -65,6 +65,9 @@

Slider with steps, tooltips, tickmarks and label

Slider with float number step (1.25), tooltips, tickmarks and labels between 3 steps (3.75 value)

+ +

Basic RTL Slider

+
diff --git a/packages/main/test/specs/Slider.spec.js b/packages/main/test/specs/Slider.spec.js index 6f38c857badd..c4dcdaa7f010 100644 --- a/packages/main/test/specs/Slider.spec.js +++ b/packages/main/test/specs/Slider.spec.js @@ -185,21 +185,190 @@ describe("Testing events", () => { }); }); -describe("Testing resize handling and RTL support", () => { - it("Testing RTL support", () => { +describe("Accessibility: Testing focus", () => { + it("Click anywhere in the Slider should focus the Slider's handle", () => { + browser.url("http://localhost:8080/test-resources/pages/Slider.html"); + const slider = browser.$("#basic-slider"); const sliderHandle = slider.shadow$(".ui5-slider-handle"); - slider.setAttribute("dir", "rtl"); - slider.setProperty("min", 0); - slider.setProperty("max", 10); - slider.setProperty("step", 1); + slider.click(); + + const innerFocusedElement = browser.execute(() => { + return document.getElementById("basic-slider").shadowRoot.activeElement; + }); + + assert.strictEqual(slider.isFocused(), true, "Slider component is focused"); + assert.strictEqual($(innerFocusedElement).getAttribute("class"), sliderHandle.getAttribute("class"), "Slider handle has the shadowDom focus"); + }); + + it("Tab should focus the Slider and move the visible focus outline to the slider's handle", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + const sliderHandle = slider.shadow$(".ui5-slider-handle"); + + browser.keys("Tab"); + + const innerFocusedElement = browser.execute(() => { + return document.getElementById("basic-slider-with-tooltip").shadowRoot.activeElement; + }); + + assert.strictEqual(slider.isFocused(), true, "Slider component is focused"); + assert.strictEqual($(innerFocusedElement).getAttribute("class"), sliderHandle.getAttribute("class"), "Slider handle has the shadowDom focus"); + }); + + it("Shift+Tab should focus the previous Slider and move the visible focus outline to the previous slider's handle", () => { + const slider = browser.$("#basic-slider"); + const sliderHandle = slider.shadow$(".ui5-slider-handle"); + + browser.keys(["Shift", "Tab"]); + + const innerFocusedElement = browser.execute(() => { + return document.getElementById("basic-slider").shadowRoot.activeElement; + }); + + assert.strictEqual(slider.isFocused(), true, "Slider component is focused"); + assert.strictEqual($(innerFocusedElement).getAttribute("class"), sliderHandle.getAttribute("class"), "Slider handle has the shadowDom focus"); + }); +}); + + +describe("Accessibility: Testing keyboard handling", () => { + it("Right arrow should increase the value of the slider with a small increment step", () => { + const slider = browser.$("#basic-slider"); + slider.setProperty("value", 0); + browser.keys("ArrowRight"); + + assert.strictEqual(slider.getProperty("value"), 1, "Value is increased"); + }); + + it("Left arrow should decrease the value of the slider with a small increment step", () => { + const slider = browser.$("#basic-slider"); + + browser.keys("ArrowLeft"); + assert.strictEqual(slider.getProperty("value"), 0, "Value is decreased"); + }); + + it("Up arrow should increase the value of the slider with a small increment step", () => { + const slider = browser.$("#basic-slider"); + + browser.keys("ArrowUp"); + assert.strictEqual(slider.getProperty("value"), 1, "Value is increased"); + }); + + it("Down arrow should increase the value of the slider with a small increment step", () => { + const slider = browser.$("#basic-slider"); + + browser.keys("ArrowDown"); + assert.strictEqual(slider.getProperty("value"), 0, "Value is decreased"); + }); + + it("Ctrl + Right arrow should increase the value of the slider with a big increment step", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + browser.keys("Tab"); + browser.keys(["Control", "ArrowRight"]); + + assert.strictEqual(slider.getProperty("value"), 2, "Value is increased"); + }); + + it("Ctrl + Left arrow should decrease the value of the slider with a big increment step", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + browser.keys(["Control", "ArrowLeft"]); + assert.strictEqual(slider.getProperty("value"), 0, "Value is decreased"); + }); + + it("Ctrl + Up arrow should increase the value of the slider with a big increment step", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + browser.keys(["Control", "ArrowUp"]); + assert.strictEqual(slider.getProperty("value"), 2, "Value is increased"); + }); + + it("Ctrl + Down arrow should increase the value of the slider with a big increment step", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + browser.keys(["Control", "ArrowDown"]); + assert.strictEqual(slider.getProperty("value"), 0, "Value is decreased"); + }); + + it("PageUp should increase the value of the slider with a big increment step", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + browser.keys("PageUp"); + assert.strictEqual(slider.getProperty("value"), 2, "Value is increased"); + }); + + it("PageDown should decrease the value of the slider with a big increment step", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + browser.keys("PageDown"); + assert.strictEqual(slider.getProperty("value"), 0, "Value is decreased"); + }); + + it("A '+' key press should increase the value of the slider with a small increment step", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + browser.keys("+"); + assert.strictEqual(slider.getProperty("value"), 1, "Value is increased"); + }); + + it("A '-' key press should decrease the value of the slider with a small increment step", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + browser.keys("-"); + assert.strictEqual(slider.getProperty("value"), 0, "Value is decreased"); + }); + + it("A numpad '+' key press should increase the value of the slider with a small increment step", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + const numpadAdd = "\uE025"; + + browser.keys(numpadAdd); + assert.strictEqual(slider.getProperty("value"), 1, "Value is increased"); + }); + + it("A numpad '-' key press should decrease the value of the slider with a small increment step", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + const numpadSubtract = "\uE027"; + + browser.keys(numpadSubtract); + assert.strictEqual(slider.getProperty("value"), 0, "Value is decreased"); + }); + + it("An 'End' key press should increase the value of the slider to its max", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + browser.keys("End"); + assert.strictEqual(slider.getProperty("value"), 20, "Value is decreased"); + }); + + it("A 'Home' key press should set the value of the slider to its minimum", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + browser.keys("Home"); + assert.strictEqual(slider.getProperty("value"), 0, "Value is increased"); + }); + + it("A 'Esc' key press should return the value of the slider at its initial point at the time of its focusing", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + + slider.setProperty("value", 12); + + browser.keys("Escape"); + assert.strictEqual(slider.getProperty("value"), 0, "Value is increased"); + }); +}); + +describe("Testing resize handling and RTL support", () => { + it("Testing RTL support", () => { + const slider = browser.$("#basic-slider-rtl"); + const sliderHandle = slider.shadow$(".ui5-slider-handle"); assert.strictEqual(sliderHandle.getAttribute("style"), "right: 0%;", "Initially if no value is set, the Slider handle is at the right of the Slider"); slider.setProperty("value", 3); - assert.strictEqual(sliderHandle.getAttribute("style"), "right: 30%;", "Slider handle should be 30% from the right"); slider.click();