diff --git a/.vnurc b/.vnurc index 460f3625dc..01d8027396 100644 --- a/.vnurc +++ b/.vnurc @@ -20,3 +20,6 @@ The “row” role is unnecessary for element “tr”. Attribute “aria-activedescendant” value should either refer to a descendant element, or should be accompanied by attribute “aria-owns”. # https://github.com/w3c/aria-practices/issues/1678 Section lacks heading. Consider using “h2”-“h6” elements to add identifying headings to all sections. +# https://github.com/validator/validator/issues/1096 +Bad value “none” for attribute “role” on element “svg”. +Bad value “presentation” for attribute “role” on element “svg”. diff --git a/aria-practices.html b/aria-practices.html index 083c41a550..72ba1faafb 100644 --- a/aria-practices.html +++ b/aria-practices.html @@ -2361,8 +2361,9 @@

Slider

Examples

diff --git a/examples/index.html b/examples/index.html index c3d9417e82..0f478b3984 100644 --- a/examples/index.html +++ b/examples/index.html @@ -262,6 +262,7 @@

Examples by Role

@@ -344,6 +345,7 @@

Examples by Role

  • Horizontal Multi-Thumb Slider
  • Color Viewer Slider (HC)
  • Rating Slider (HC)
  • +
  • Vertical Temperature Slider (HC)
  • @@ -603,6 +605,7 @@

    Examples By Properties and States

  • Button (IDL Version)
  • Color Viewer Slider (HC)
  • Rating Slider (HC)
  • +
  • Vertical Temperature Slider (HC)
  • Date Picker Spin Button
  • Toolbar
  • @@ -660,6 +663,7 @@

    Examples By Properties and States

  • Radio Group Using Roving tabindex (HC)
  • Color Viewer Slider (HC)
  • Rating Slider (HC)
  • +
  • Vertical Temperature Slider (HC)
  • Date Picker Spin Button
  • Tabs with Automatic Activation
  • Tabs with Manual Activation
  • @@ -711,6 +715,10 @@

    Examples By Properties and States

    aria-multiselectable Listboxes with Rearrangeable Options + + aria-orientation + Vertical Temperature Slider + aria-owns Navigation Treeview @@ -805,6 +813,7 @@

    Examples By Properties and States

  • Horizontal Multi-Thumb Slider
  • Color Viewer Slider (HC)
  • Rating Slider (HC)
  • +
  • Vertical Temperature Slider (HC)
  • Date Picker Spin Button
  • Toolbar
  • @@ -818,6 +827,7 @@

    Examples By Properties and States

  • Horizontal Multi-Thumb Slider
  • Color Viewer Slider (HC)
  • Rating Slider (HC)
  • +
  • Vertical Temperature Slider (HC)
  • Date Picker Spin Button
  • Toolbar
  • @@ -831,6 +841,7 @@

    Examples By Properties and States

  • Horizontal Multi-Thumb Slider
  • Color Viewer Slider (HC)
  • Rating Slider (HC)
  • +
  • Vertical Temperature Slider (HC)
  • Date Picker Spin Button
  • Toolbar
  • @@ -842,6 +853,7 @@

    Examples By Properties and States

    diff --git a/examples/slider/css/slider-temperature.css b/examples/slider/css/slider-temperature.css new file mode 100644 index 0000000000..55a2d52d7a --- /dev/null +++ b/examples/slider/css/slider-temperature.css @@ -0,0 +1,107 @@ +/* CSS Document */ + +.slider-valuetext h3 { + color: black; + font-weight: bold; + font-size: 150%; +} + +.slider-temperature .label, +.slider-seek .label { + font-weight: bold; + font-size: 125%; +} + +.slider-temperature svg, +.slider-seek svg { + forced-color-adjust: auto; +} + +.slider-temperature text, +.slider-seek text { + font-weight: bold; + fill: currentColor; + font-family: sans-serif; +} + +.slider-temperature { + width: 12em; +} + +.slider-temperature, +.slider-seek { + margin-top: 1em; + padding: 6px; + color: black; +} + +.slider-temperature .value, +.slider-slider .value { + position: relative; + top: 20px; + height: 1.5em; + font-size: 80%; +} + +.slider-temperature .temp-value, +.slider-seek .temp-value { + padding-left: 24px; + font-size: 200%; +} + +.slider-temperature .rail, +.slider-seek .rail { + stroke: currentColor; + stroke-width: 2px; + fill: currentColor; + fill-opacity: 25%; +} + +.slider-temperature .thumb, +.slider-seek .thumb { + stroke-width: 0; + fill: currentColor; +} + +.slider-temperature .focus-ring, +.slider-seek .focus-ring { + stroke: currentColor; + stroke-opacity: 0; + fill-opacity: 0; + stroke-width: 3px; + display: none; +} + +.slider-temperature .slider-group { + touch-action: pan-x; +} + +.slider-seek .slider-group { + touch-action: pan-y; +} + +.slider-seek .slider-group .value { + display: none; +} + +/* Focus and hover styling */ + +.slider-seek.focus [role="slider"], +.slider-temperature.focus [role="slider"] { + color: #005a9c; +} + +.slider-temperature [role="slider"]:focus, +.slider-seek [role="slider"]:focus { + outline: none; +} + +.slider-temperature [role="slider"]:focus .focus-ring, +.slider-seek [role="slider"]:focus .focus-ring { + display: block; + stroke-opacity: 1; +} + +.slider-seek [role="slider"]:focus .value { + display: block; +} diff --git a/examples/slider/js/slider-temperature.js b/examples/slider/js/slider-temperature.js new file mode 100644 index 0000000000..9f4770c338 --- /dev/null +++ b/examples/slider/js/slider-temperature.js @@ -0,0 +1,256 @@ +/* + * This content is licensed according to the W3C Software License at + * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document + * + * File: slider-valuetext.js + * + * Desc: Slider widgets using aria-valuetext that implements ARIA Authoring Practices + */ + +'use strict'; + +class SliderTemperature { + constructor(domNode) { + this.labelCelsiusAbbrev = '°C'; + this.labelCelsius = ' degrees Celsius'; + this.changeValue = 0.1; + this.bigChangeValue = 20 * this.changeValue; + + this.domNode = domNode; + + this.isMoving = false; + + this.svgNode = domNode.querySelector('svg'); + this.svgPoint = this.svgNode.createSVGPoint(); + + this.borderWidth = 2; + this.borderWidth2 = 2 * this.borderWidth; + + this.railNode = domNode.querySelector('.rail'); + this.sliderNode = domNode.querySelector('[role=slider]'); + this.sliderValueNode = this.sliderNode.querySelector('.value'); + this.sliderFocusNode = this.sliderNode.querySelector('.focus-ring'); + this.sliderThumbNode = this.sliderNode.querySelector('.thumb'); + + // The input elements are optional to support mobile devices, + // when a keyboard is not present + this.valueNode = domNode.querySelector('.temp-value'); + + // Dimensions of the slider focus ring, thumb and rail + + this.valueX = parseInt(this.sliderValueNode.getAttribute('x')); + this.valueHeight = this.sliderValueNode.getBoundingClientRect().height; + + this.railHeight = parseInt(this.railNode.getAttribute('height')); + this.railWidth = parseInt(this.railNode.getAttribute('width')); + this.railY = parseInt(this.railNode.getAttribute('y')); + this.railX = parseInt(this.railNode.getAttribute('x')); + + this.thumbY = parseInt(this.sliderThumbNode.getAttribute('y')); + this.thumbWidth = parseInt(this.sliderThumbNode.getAttribute('width')); + this.thumbHeight = parseInt(this.sliderThumbNode.getAttribute('height')); + + this.focusX = parseInt(this.sliderFocusNode.getAttribute('x')); + this.focusWidth = parseInt(this.sliderFocusNode.getAttribute('width')); + this.focusHeight = parseInt(this.sliderFocusNode.getAttribute('height')); + + this.thumbX = this.railX + this.railWidth / 2 - this.thumbWidth / 2; + this.sliderThumbNode.setAttribute('x', this.thumbX); + this.sliderValueNode.setAttribute('x', this.valueX); + this.sliderFocusNode.setAttribute('x', this.focusX); + + this.svgNode.addEventListener('click', this.onRailClick.bind(this)); + this.sliderNode.addEventListener( + 'keydown', + this.onSliderKeydown.bind(this) + ); + this.sliderNode.addEventListener( + 'pointerdown', + this.onSliderPointerDown.bind(this) + ); + + // bind a pointermove event handler to move pointer + this.svgNode.addEventListener('pointermove', this.onPointerMove.bind(this)); + + // bind a pointerup event handler to stop tracking pointer movements + document.addEventListener('pointerup', this.onPointerUp.bind(this)); + + this.sliderNode.addEventListener('focus', this.onSliderFocus.bind(this)); + this.sliderNode.addEventListener('blur', this.onSliderBlur.bind(this)); + + this.moveSliderTo(this.getValue()); + } + + // Get point in global SVG space + getSVGPoint(event) { + this.svgPoint.x = event.clientX; + this.svgPoint.y = event.clientY; + return this.svgPoint.matrixTransform(this.svgNode.getScreenCTM().inverse()); + } + + getValue() { + return parseFloat(this.sliderNode.getAttribute('aria-valuenow')); + } + + getValueMin() { + return parseFloat(this.sliderNode.getAttribute('aria-valuemin')); + } + + getValueMax() { + return parseFloat(this.sliderNode.getAttribute('aria-valuemax')); + } + + isInRange(value) { + let valueMin = this.getValueMin(); + let valueMax = this.getValueMax(); + return value <= valueMax && value >= valueMin; + } + + moveSliderTo(value) { + var valueMax, valueMin, pos; + + valueMin = this.getValueMin(); + valueMax = this.getValueMax(); + + value = Math.min(Math.max(value, valueMin), valueMax); + + let valueOutput = value.toFixed(1) + this.labelCelsiusAbbrev; + + let valueText = value.toFixed(1) + this.labelCelsius; + + this.valueNode.textContent = valueOutput; + this.sliderNode.setAttribute('aria-valuenow', value.toFixed(1)); + this.sliderNode.setAttribute('aria-valuetext', valueText); + + let height = this.railHeight - this.thumbHeight + this.borderWidth2; + + pos = this.railY + height - 1; + pos -= Math.round(((value - valueMin) * height) / (valueMax - valueMin)); + this.sliderNode.setAttribute('y', pos); + + // update INPUT, label and ARIA attributes + this.sliderValueNode.textContent = valueOutput; + + // move the SVG focus ring and thumb elements + this.sliderFocusNode.setAttribute( + 'y', + pos - (this.focusHeight - this.thumbHeight) / 2 + ); + this.sliderThumbNode.setAttribute('y', pos); + + // Position value + this.sliderValueNode.setAttribute( + 'y', + pos - + this.borderWidth + + this.thumbHeight - + (this.valueHeight - this.thumbHeight) / 2 + ); + } + + onSliderKeydown(event) { + var flag = false; + var value = this.getValue(); + var valueMin = this.getValueMin(); + var valueMax = this.getValueMax(); + + switch (event.key) { + case 'ArrowLeft': + case 'ArrowDown': + this.moveSliderTo(value - this.changeValue); + flag = true; + break; + + case 'ArrowRight': + case 'ArrowUp': + this.moveSliderTo(value + this.changeValue); + flag = true; + break; + + case 'PageDown': + this.moveSliderTo(value - this.bigChangeValue); + flag = true; + break; + + case 'PageUp': + this.moveSliderTo(value + this.bigChangeValue); + flag = true; + break; + + case 'Home': + this.moveSliderTo(valueMin); + flag = true; + break; + + case 'End': + this.moveSliderTo(valueMax); + flag = true; + break; + + default: + break; + } + + if (flag) { + event.preventDefault(); + event.stopPropagation(); + } + } + + onSliderFocus() { + this.domNode.classList.add('focus'); + } + + onSliderBlur() { + this.domNode.classList.remove('focus'); + } + + calcValue(y) { + let min = this.getValueMin(); + let max = this.getValueMax(); + let diffY = y - this.railY; + return max - (diffY * (max - min)) / this.railHeight; + } + + onRailClick(event) { + var value = this.calcValue(this.getSVGPoint(event).y); + this.moveSliderTo(value); + + event.preventDefault(); + event.stopPropagation(); + + // Set focus to the clicked handle + this.sliderNode.focus(); + } + + onSliderPointerDown(event) { + this.isMoving = true; + + event.preventDefault(); + event.stopPropagation(); + + // Set focus to the clicked handle + this.sliderNode.focus(); + } + + onPointerMove(event) { + if (this.isMoving) { + var value = this.calcValue(this.getSVGPoint(event).y); + this.moveSliderTo(value); + + event.preventDefault(); + event.stopPropagation(); + } + } + + onPointerUp() { + this.isMoving = false; + } +} + +window.addEventListener('load', function () { + var sliders = document.querySelectorAll('.slider-temperature'); + for (let i = 0; i < sliders.length; i++) { + new SliderTemperature(sliders[i]); + } +}); diff --git a/examples/slider/slider-color-viewer.html b/examples/slider/slider-color-viewer.html index 17fd7ee5b3..b278585962 100644 --- a/examples/slider/slider-color-viewer.html +++ b/examples/slider/slider-color-viewer.html @@ -43,10 +43,9 @@

    Color Viewer Slider Example

    Similar examples include:

    diff --git a/examples/slider/slider-rating.html b/examples/slider/slider-rating.html index 959ab4a5e4..d716bdb415 100644 --- a/examples/slider/slider-rating.html +++ b/examples/slider/slider-rating.html @@ -45,7 +45,9 @@

    Rating Slider Example

    Similar examples include:

    diff --git a/examples/slider/slider-temperature.html b/examples/slider/slider-temperature.html new file mode 100644 index 0000000000..b83c1b0f50 --- /dev/null +++ b/examples/slider/slider-temperature.html @@ -0,0 +1,311 @@ + + + + + + + Vertical Temperature Slider Example | WAI-ARIA Authoring Practices 1.2 + + + + + + + + + + + + + +
    +

    Vertical Temperature Slider Example

    +
    +

    + WARNING! Some users of touch-based assistive technologies may experience difficulty utilizing widgets that implement this slider pattern because the gestures their assistive technology provides for operating sliders may not yet generate the necessary output. + To change the slider value, touch-based assistive technologies need to respond to user gestures for increasing and decreasing the value by synthesizing key events. + This is a new convention that may not be fully implemented by some assistive technologies. + Authors should fully test slider widgets using assistive technologies on devices where touch is a primary input mechanism before considering incorporation into production systems. +

    +
    +

    + The following example is a vertically oriented temperature control that implements the + slider design pattern. + The slider illustrates use of aria-orientation to specify vertical orientation and use of aria-valuetext to convey unit of measure for numeric values to assistive technology users by appending degrees Celsius to the current value. +

    +

    Similar examples include:

    +
      +
    • Color Viewer Slider Example: Basic horizontal sliders that illustrate setting numeric values for a color picker.
    • +
    • Rating Slider Example: Horizontal slider that demonstrates using aria-valuetext to communicate current and maximum value of a rating input for a five star rating scale.
    • +
    • Horizontal Multi-Thumb Slider Example: Demonstrates using sliders with two thumbs to provide inputs for numeric ranges, such as for searching in a price range.
    • +
    + +
    +
    +

    Example

    +
    + +
    +
    +
    Temperature
    + + + 25°C + + + 25°C + + + + +
    +
    + +
    + +
    +

    Accessibility Features

    +
      +
    • The display of the slider's current value remains adjacent to the thumb as the thumb is moved, so people with a small field of view (e.g., due to magnification) can easily see the value while focusing on the thumb as they move it.
    • +
    • + To ensure the borders of the slider rail, thumb and focus ring have sufficient contrast with the background when high contrast settings invert colors, the color of the borders are synchronized with the color of the text content. + For example, the color of the focus ring border is set to match the foreground color of high contrast mode text by specifying the CSS currentColor value for the stroke property of the inline SVG rect element used to draw the focus ring border. + To make the background of the rect match the high contrast background color, the fill-opacity attribute of the rect is set to zero. + If specific colors were instead used to specify the stroke and fill properties, those colors would remain the same in high contrast mode, which could lead to insufficient contrast between the rail and the thumb or even make them invisible if their color matched the high contrast mode background.
      + Note: The SVG element needs to have the CSS forced-color-adjust property set to auto for the currentColor value to be updated in high contrast mode. + Some browsers do not use auto for the default value. +
    • +
    +
    + +
    +

    Keyboard Support

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    KeyFunction
    + Right Arrow + Increases slider value one step.
    + Up Arrow + Increases slider value one step.
    + Left Arrow + Decreases slider value one step.
    + Down Arrow + Decreases slider value one step.
    + Page Up + Increases temperature slider value multiple steps. In this slider, jumps twenty steps (e.g. 2°C).
    + Page Down + Decreases temperature slider value multiple steps. In this slider, jumps twenty steps (e.g. 2°C).
    + Home + Sets slider to its minimum value.
    + End + Sets slider to its maximum value.
    + +
    + +
    +

    Role, Property, State, and Tabindex Attributes

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    RoleAttributeElementUsage
    + none + + svg + + ensures assistive technologies do not present the SVG element as an image or any other type of meaningful element. +
    + slider + + g + +
      +
    • Identifies the element as a slider.
    • +
    • Set on the g element that represents the movable thumb because it is the operable element that receives focus and conveys the value.
    • +
    +
    + tabindex="0" + + g + Includes the slider thumb in the page tab sequence.
    + aria-orientation + + g + + Indicates the slider is vertically oriented. +
    + aria-valuemax="NUMBER" + + g + Specifies the numeric value that is the maximum allowed value of the slider.
    + aria-valuemin="NUMBER" + + g + Specifies the numeric value that is the minimum allowed value of the slider.
    + aria-valuenow="NUMBER" + + g + A numeric value that is the current value of the slider.
    + aria-valuetext="STRING" + + g + + Provides a more user-friendly name for the current value of the slider by combining the current value with the string degrees Celsius. +
    + aria-labelledby="IDREF" + + g + Refers to the element containing the name (e.g. label) of the slider.
    + aria-hidden="true" + + rect + Removes the SVG rect element from the accessibility tree to prevent screen readers from presenting it as a meaningful image.
    +
    + +
    +

    Javascript and CSS Source Code

    + +
    + +
    +

    HTML Source Code

    + +
    + + +
    +
    + + + diff --git a/test/tests/slider_slider-temperature.js b/test/tests/slider_slider-temperature.js new file mode 100644 index 0000000000..9cfe953e59 --- /dev/null +++ b/test/tests/slider_slider-temperature.js @@ -0,0 +1,473 @@ +const { ariaTest } = require('..'); +const { By, Key } = require('selenium-webdriver'); +const assertAttributeValues = require('../util/assertAttributeValues'); +const assertAriaLabelledby = require('../util/assertAriaLabelledby'); +const assertAriaRoles = require('../util/assertAriaRoles'); + +const exampleFile = 'slider/slider-temperature.html'; + +const ex = { + railRects: '#ex1 rect.rail', + labelG: '#ex1 g.value-label', + sliderSelector: '#ex1 [role="slider"]', + tempSelector: '#id-temp-slider', + tempValueSelector: '#id-temp-slider .value', + tempMax: '38.0', + tempMin: '10.0', + tempDefault: '25.0', + tempInc: '.1', + tempPageInc: '2.0', + tempSuffix: '°C', +}; + +const sendAllSlidersToEnd = async function (t) { + const sliders = await t.context.queryElements(t, ex.sliderSelector); + + for (let slider of sliders) { + await slider.sendKeys(Key.END); + } +}; + +// Attributes + +ariaTest('role="none" on SVG element', exampleFile, 'svg-none', async (t) => { + await assertAriaRoles(t, 'ex1', 'none', '1', 'svg'); +}); + +ariaTest( + 'SVG rects used for the rail have aria-hidden', + exampleFile, + 'aria-hidden-rect', + async (t) => { + await assertAttributeValues(t, ex.railRects, 'aria-hidden', 'true'); + } +); + +ariaTest( + 'role="slider" on SVG g element', + exampleFile, + 'slider-role', + async (t) => { + await assertAriaRoles(t, 'ex1', 'slider', '1', 'g'); + } +); + +ariaTest( + '"tabindex" set to "0" on sliders', + exampleFile, + 'slider-tabindex', + async (t) => { + await assertAttributeValues(t, ex.sliderSelector, 'tabindex', '0'); + } +); + +ariaTest( + '"aria-orientation" set on sliders', + exampleFile, + 'aria-orientation', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-orientation', + 'vertical' + ); + } +); + +ariaTest( + '"aria-valuemax" set on sliders', + exampleFile, + 'aria-valuemax', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-valuemax', + ex.tempMax + ); + } +); + +ariaTest( + '"aria-valuemin" set on sliders', + exampleFile, + 'aria-valuemin', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-valuemin', + ex.tempMin + ); + } +); + +ariaTest( + '"aria-valuenow" reflects slider value', + exampleFile, + 'aria-valuenow', + async (t) => { + await assertAttributeValues( + t, + ex.tempSelector, + 'aria-valuenow', + ex.tempDefault + ); + } +); + +ariaTest( + '"aria-labelledby" set on sliders', + exampleFile, + 'aria-labelledby', + async (t) => { + await assertAriaLabelledby(t, ex.sliderSelector); + } +); + +// Keys + +ariaTest( + 'Right arrow increases slider value by 1', + exampleFile, + 'key-right-arrow', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_RIGHT); + + let sliderVal = parseFloat(ex.tempDefault) + ex.tempInc; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 arrow right key to the temp slider, "aria-valuenow": ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + sliderVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send 200 more keys to temp slider + for (let i = 0; i < 200; i++) { + await tempSlider.sendKeys(Key.ARROW_RIGHT); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending 200 arrow right key, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMax + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMax + ); + } +); + +ariaTest( + 'up arrow increases slider value by 1', + exampleFile, + 'key-up-arrow', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_UP); + + let sliderVal = parseFloat(ex.tempDefault) + ex.tempInc; + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 arrow up key to the temp slider, "aria-valuenow": ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + sliderVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send 200 more keys to temp slider + for (let i = 0; i < 200; i++) { + await tempSlider.sendKeys(Key.ARROW_UP); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending 200 arrow up key, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMax + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMax + ); + } +); + +ariaTest( + 'page up increases slider value by big step', + exampleFile, + 'key-page-up', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.PAGE_UP); + + let sliderVal = ( + parseFloat(ex.tempDefault) + parseFloat(ex.tempPageInc) + ).toFixed(1); + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 page up key to the temp slider, the value of the temp slider should be: ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + sliderVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send more than 10 keys to temp slider + for (let i = 0; i < 10; i++) { + await tempSlider.sendKeys(Key.PAGE_UP); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending 10 page up key, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMax + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMax + ); + } +); + +ariaTest( + 'key end set slider at max value', + exampleFile, + 'key-end', + async (t) => { + // Send key end to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.END); + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMax, + 'After sending key END, the value of the temp slider should be: ' + + ex.tempMax + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMax + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMax + ); + } +); + +ariaTest( + 'left arrow decreases slider value by 1', + exampleFile, + 'key-left-arrow', + async (t) => { + await sendAllSlidersToEnd(t); + + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_LEFT); + + let tempVal = (parseFloat(ex.tempMax) - parseFloat(ex.tempInc)).toFixed(1); + t.is( + await tempSlider.getAttribute('aria-valuenow'), + tempVal.toString(), + 'After sending 1 arrow left key, the value of the temp slider should be: ' + + tempVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + tempVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + tempVal + ); + + // Send 300 more keys to temp slider + for (let i = 0; i < 300; i++) { + await tempSlider.sendKeys(Key.ARROW_LEFT); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin.toString(), + 'After sending 300 arrow left key to the temp slider, "aria-valuenow": ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMin.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMin + ); + } +); + +ariaTest( + 'down arrow decreases slider value by 1', + exampleFile, + 'key-down-arrow', + async (t) => { + await sendAllSlidersToEnd(t); + + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.ARROW_DOWN); + + let tempVal = (parseFloat(ex.tempMax) - parseFloat(ex.tempInc)).toFixed(1); + t.is( + await tempSlider.getAttribute('aria-valuenow'), + tempVal.toString(), + 'After sending 1 arrow down key, the value of the temp slider should be: ' + + tempVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + tempVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + tempVal + ); + + // Send 300 more keys to temp slider + for (let i = 0; i < 300; i++) { + await tempSlider.sendKeys(Key.ARROW_DOWN); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin.toString(), + 'After sending 300 arrow down key to the temp slider, "aria-valuenow": ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMin.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMin + ); + } +); + +ariaTest( + 'page down decreases slider value by big step', + exampleFile, + 'key-page-down', + async (t) => { + // Send 1 key to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.PAGE_DOWN); + + let sliderVal = ( + parseFloat(ex.tempDefault) - parseFloat(ex.tempPageInc) + ).toFixed(1); + t.is( + await tempSlider.getAttribute('aria-valuenow'), + sliderVal.toString(), + 'After sending 1 page down key to the temp slider, the value of the temp slider should be: ' + + sliderVal + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + sliderVal.toString() + ex.tempSuffix, + 'Temp display should match value of slider: ' + sliderVal + ); + + // Send more than 20 keys to temp slider + for (let i = 0; i < 20; i++) { + await tempSlider.sendKeys(Key.PAGE_DOWN); + } + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin, + 'After sending 20 page down key, the value of the temp slider should be: ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMin + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMin + ); + } +); + +ariaTest( + 'home set slider value to minimum', + exampleFile, + 'key-home', + async (t) => { + // Send key home to temp slider + const tempSlider = await t.context.session.findElement( + By.css(ex.tempSelector) + ); + await tempSlider.sendKeys(Key.HOME); + + t.is( + await tempSlider.getAttribute('aria-valuenow'), + ex.tempMin, + 'After sending key HOME, the value of the temp slider should be: ' + + ex.tempMin + ); + t.is( + await t.context.session + .findElement(By.css(ex.tempValueSelector)) + .getText(), + ex.tempMin + ex.tempSuffix, + 'Temp display should match value of slider: ' + ex.tempMin + ); + } +);