Tests: Import WPT accname/name/comp_embedded_control.html test
This change imports the WPT accname/name/comp_embedded_control.html test, along with related resources files it depends on. Note that in the wai-aria/scripts/aria-utils.js file, this changes the get_computed_label call to use our window.internals.getComputedLabel.
This commit is contained in:
parent
1b4e609eb2
commit
120bc52f23
Notes:
github-actions[bot]
2024-10-31 01:17:42 +00:00
Author: https://github.com/sideshowbarker Commit: https://github.com/LadybirdBrowser/ladybird/commit/120bc52f238 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2033 Reviewed-by: https://github.com/awesomekling Reviewed-by: https://github.com/tcl3 ✅
6 changed files with 2227 additions and 0 deletions
|
@ -0,0 +1,36 @@
|
|||
Summary
|
||||
|
||||
Harness status: OK
|
||||
|
||||
Rerun
|
||||
|
||||
Found 26 tests
|
||||
|
||||
26 Pass
|
||||
Details
|
||||
Result Test Name MessagePass checkbox label with embedded textfield
|
||||
Pass label of embedded textfield inside checkbox label
|
||||
Pass checkbox label with embedded select:not([size])
|
||||
Pass label of embedded select:not([size]) inside checkbox label
|
||||
Pass checkbox label with embedded select[size]
|
||||
Pass label of embedded select[size] inside checkbox label
|
||||
Pass checkbox label with embedded combobox (input[type=text])
|
||||
Pass label of embedded combobox (input[type=text]) inside checkbox label
|
||||
Pass checkbox label with embedded combobox (span)
|
||||
Pass label of embedded combobox (span) inside checkbox label
|
||||
Pass checkbox label with embedded combobox (div)
|
||||
Pass label of embedded combobox (div) inside checkbox label
|
||||
Pass checkbox label with embedded listbox>option[aria-selected=true]
|
||||
Pass label of embedded listbox>option[aria-selected=true] inside checkbox label
|
||||
Pass checkbox label with embedded input[type=range]
|
||||
Pass label of embedded input[type=range] inside checkbox label
|
||||
Pass checkbox label with embedded input[type=number]
|
||||
Pass label of embedded input[type=number] inside checkbox label
|
||||
Pass checkbox label with embedded ARIA slider (aria-valuenow)
|
||||
Pass label of embedded ARIA slider (aria-valuenow) inside checkbox label
|
||||
Pass checkbox label with embedded ARIA slider (aria-valuetext)
|
||||
Pass label of embedded ARIA slider (aria-valuetext) inside checkbox label
|
||||
Pass checkbox label with embedded ARIA spinbutton (aria-valuenow)
|
||||
Pass label of embedded ARIA spinbutton (aria-valuenow) inside checkbox label
|
||||
Pass checkbox label with embedded ARIA spinbutton (aria-valuetext)
|
||||
Pass label of embedded ARIA spinbutton (aria-valuetext) inside checkbox label
|
|
@ -0,0 +1,134 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Name Comp: Embedded Control</title>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
<script src="../../resources/testdriver.js"></script>
|
||||
<script src="../../resources/testdriver-vendor.js"></script>
|
||||
<script src="../../resources/testdriver-actions.js"></script>
|
||||
<script src="../../wai-aria/scripts/aria-utils.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Tests the <a href="https://w3c.github.io/accname/#comp_embedded_control">#comp_embedded_control</a> portions of the AccName <em>Name Computation</em> algorithm.</p>
|
||||
|
||||
<!-- Textfield (textbox) -->
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded textfield" class="ex">
|
||||
Flash the screen
|
||||
<input value="3" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded textfield inside checkbox label" class="ex"> times
|
||||
</label>
|
||||
<br><br>
|
||||
|
||||
<!-- HTML select:not([size]) element renders as menu button (on Mac) or single-row listbox (on Windows/Linux) -->
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded select:not([size])" class="ex">
|
||||
Flash the screen
|
||||
<select aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded select:not([size]) inside checkbox label" class="ex">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3" selected>3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
</select>
|
||||
times
|
||||
</label>
|
||||
<br><br>
|
||||
|
||||
<!-- HTML select[size] element renders as multi-row listbox -->
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded select[size]" class="ex">
|
||||
Flash the screen
|
||||
<select size=5 aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded select[size] inside checkbox label" class="ex">
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3" selected>3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
</select>
|
||||
times
|
||||
</label>
|
||||
<br><br>
|
||||
|
||||
<!-- ARIA combobox examples (not interactive) -->
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded combobox (input[type=text])" class="ex">
|
||||
Flash the screen
|
||||
<input role="combobox" value="3" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded combobox (input[type=text]) inside checkbox label" class="ex"> times
|
||||
</label>
|
||||
<br><br>
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded combobox (span)" class="ex">
|
||||
Flash the screen
|
||||
<span role="combobox" tabindex="0" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded combobox (span) inside checkbox label" class="ex">3</span> times
|
||||
</label>
|
||||
<br><br>
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded combobox (div)" class="ex">
|
||||
Flash the screen
|
||||
<div style="display: inline-block;" role="combobox" tabindex="0" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded combobox (div) inside checkbox label" class="ex">3</div> times
|
||||
</label>
|
||||
<br><br>
|
||||
<!-- Todo: we may want another combobox example using aria-activedescendant? -->
|
||||
|
||||
|
||||
<!-- ARIA listbox examples (not interactive) -->
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded listbox>option[aria-selected=true]" class="ex">
|
||||
Flash the screen
|
||||
<ul role="listbox" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded listbox>option[aria-selected=true] inside checkbox label" class="ex" style="padding:0; list-style:none; border: solid 1px gray; width: 15em; display: inline-block;">
|
||||
<li role="option" tabindex="-1" style="padding:0.2em 0.8em; list-style:none;">1</li>
|
||||
<li role="option" tabindex="-1" style="padding:0.2em 0.8em; list-style:none;">2</li>
|
||||
<li role="option" tabindex="0" aria-selected="true" style="padding:0.2em 0.8em; list-style:none; color: white; background-color: #555;">3</li>
|
||||
<li role="option" tabindex="-1" style="padding:0.2em 0.8em; list-style:none;">4</li>
|
||||
<li role="option" tabindex="-1" style="padding:0.2em 0.8em; list-style:none;">5</li>
|
||||
</ul> times
|
||||
</label>
|
||||
<br><br>
|
||||
|
||||
|
||||
<!-- Ranges: HTML native and ARIA sliders and steppers (valuetext, valuenow, host language specific) -->
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded input[type=range]" class="ex">
|
||||
Flash the screen
|
||||
<input type ="range" min="1" max="5" value="3" step="1" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded input[type=range] inside checkbox label" class="ex"> times
|
||||
</label>
|
||||
<br><br>
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded input[type=number]" class="ex">
|
||||
Flash the screen
|
||||
<input type ="number" min="1" max="5" value="3" step="1" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded input[type=number] inside checkbox label" class="ex"> times
|
||||
</label>
|
||||
<br><br>
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded ARIA slider (aria-valuenow)" class="ex">
|
||||
Flash the screen
|
||||
<span tabindex="0" role="slider" aria-valuemin="1" aria-valuemax="5" aria-valuenow="3" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded ARIA slider (aria-valuenow) inside checkbox label" class="ex">3.0</span> times
|
||||
</label>
|
||||
<br><br>
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded ARIA slider (aria-valuetext)" class="ex">
|
||||
Flash the screen
|
||||
<span tabindex="0" role="slider" aria-valuemin="1.0" aria-valuemax="5.0" aria-valuenow="3.0" aria-valuetext="3" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded ARIA slider (aria-valuetext) inside checkbox label" class="ex">3.0</span> times
|
||||
</label>
|
||||
<br><br>
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded ARIA spinbutton (aria-valuenow)" class="ex">
|
||||
Flash the screen
|
||||
<span tabindex="0" role="spinbutton" aria-valuemin="1" aria-valuemax="5" aria-valuenow="3" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded ARIA spinbutton (aria-valuenow) inside checkbox label" class="ex">3.0</span> times
|
||||
</label>
|
||||
<br><br>
|
||||
<label>
|
||||
<input type="checkbox" data-expectedlabel="Flash the screen 3 times" data-testname="checkbox label with embedded ARIA spinbutton (aria-valuetext)" class="ex">
|
||||
Flash the screen
|
||||
<span tabindex="0" role="spinbutton" aria-valuemin="1.0" aria-valuemax="5.0" aria-valuenow="3.0" aria-valuetext="3" aria-label="number of times" data-expectedlabel="number of times" data-testname="label of embedded ARIA spinbutton (aria-valuetext) inside checkbox label" class="ex">3.0</span> times
|
||||
</label>
|
||||
<br><br>
|
||||
|
||||
|
||||
<script>
|
||||
AriaUtils.verifyLabelsBySelector(".ex");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,599 @@
|
|||
(function() {
|
||||
let sourceNameIdx = 0;
|
||||
|
||||
/**
|
||||
* @class
|
||||
* Builder for creating a sequence of actions
|
||||
*
|
||||
*
|
||||
* The actions are dispatched once
|
||||
* :js:func:`test_driver.Actions.send` is called. This returns a
|
||||
* promise which resolves once the actions are complete.
|
||||
*
|
||||
* The other methods on :js:class:`test_driver.Actions` object are
|
||||
* used to build the sequence of actions that will be sent. These
|
||||
* return the `Actions` object itself, so the actions sequence can
|
||||
* be constructed by chaining method calls.
|
||||
*
|
||||
* Internally :js:func:`test_driver.Actions.send` invokes
|
||||
* :js:func:`test_driver.action_sequence`.
|
||||
*
|
||||
* @example
|
||||
* let text_box = document.getElementById("text");
|
||||
*
|
||||
* let actions = new test_driver.Actions()
|
||||
* .pointerMove(0, 0, {origin: text_box})
|
||||
* .pointerDown()
|
||||
* .pointerUp()
|
||||
* .addTick()
|
||||
* .keyDown("p")
|
||||
* .keyUp("p");
|
||||
*
|
||||
* await actions.send();
|
||||
*
|
||||
* @param {number} [defaultTickDuration] - The default duration of a
|
||||
* tick. Be default this is set ot 16ms, which is one frame time
|
||||
* based on 60Hz display.
|
||||
*/
|
||||
function Actions(defaultTickDuration=16) {
|
||||
this.sourceTypes = new Map([["key", KeySource],
|
||||
["pointer", PointerSource],
|
||||
["wheel", WheelSource],
|
||||
["none", GeneralSource]]);
|
||||
this.sources = new Map();
|
||||
this.sourceOrder = [];
|
||||
for (let sourceType of this.sourceTypes.keys()) {
|
||||
this.sources.set(sourceType, new Map());
|
||||
}
|
||||
this.currentSources = new Map();
|
||||
for (let sourceType of this.sourceTypes.keys()) {
|
||||
this.currentSources.set(sourceType, null);
|
||||
}
|
||||
this.createSource("none");
|
||||
this.tickIdx = 0;
|
||||
this.defaultTickDuration = defaultTickDuration;
|
||||
this.context = null;
|
||||
}
|
||||
|
||||
Actions.prototype = {
|
||||
ButtonType: {
|
||||
LEFT: 0,
|
||||
MIDDLE: 1,
|
||||
RIGHT: 2,
|
||||
BACK: 3,
|
||||
FORWARD: 4,
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate the action sequence suitable for passing to
|
||||
* test_driver.action_sequence
|
||||
*
|
||||
* @returns {Array} Array of WebDriver-compatible actions sequences
|
||||
*/
|
||||
serialize: function() {
|
||||
let actions = [];
|
||||
for (let [sourceType, sourceName] of this.sourceOrder) {
|
||||
let source = this.sources.get(sourceType).get(sourceName);
|
||||
let serialized = source.serialize(this.tickIdx + 1, this.defaultTickDuration);
|
||||
if (serialized) {
|
||||
serialized.id = sourceName;
|
||||
actions.push(serialized);
|
||||
}
|
||||
}
|
||||
return actions;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate and send the action sequence
|
||||
*
|
||||
* @returns {Promise} fulfilled after the sequence is executed,
|
||||
* rejected if any actions fail.
|
||||
*/
|
||||
send: function() {
|
||||
let actions;
|
||||
try {
|
||||
actions = this.serialize();
|
||||
} catch(e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
return test_driver.action_sequence(actions, this.context);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the context for the actions
|
||||
*
|
||||
* @param {WindowProxy} context - Context in which to run the action sequence
|
||||
*/
|
||||
setContext: function(context) {
|
||||
this.context = context;
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the action source with a particular source type and name.
|
||||
* If no name is passed, a new source with the given type is
|
||||
* created.
|
||||
*
|
||||
* @param {String} type - Source type ('none', 'key', 'pointer', or 'wheel')
|
||||
* @param {String?} name - Name of the source
|
||||
* @returns {Source} Source object for that source.
|
||||
*/
|
||||
getSource: function(type, name) {
|
||||
if (!this.sources.has(type)) {
|
||||
throw new Error(`${type} is not a valid action type`);
|
||||
}
|
||||
if (name === null || name === undefined) {
|
||||
name = this.currentSources.get(type);
|
||||
}
|
||||
if (name === null || name === undefined) {
|
||||
return this.createSource(type, null);
|
||||
}
|
||||
return this.sources.get(type).get(name);
|
||||
},
|
||||
|
||||
setSource: function(type, name) {
|
||||
if (!this.sources.has(type)) {
|
||||
throw new Error(`${type} is not a valid action type`);
|
||||
}
|
||||
if (!this.sources.get(type).has(name)) {
|
||||
throw new Error(`${name} is not a valid source for ${type}`);
|
||||
}
|
||||
this.currentSources.set(type, name);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new key input source with the given name
|
||||
*
|
||||
* @param {String} name - Name of the key source
|
||||
* @param {Bool} set - Set source as the default key source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
addKeyboard: function(name, set=true) {
|
||||
this.createSource("key", name);
|
||||
if (set) {
|
||||
this.setKeyboard(name);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the current default key source
|
||||
*
|
||||
* @param {String} name - Name of the key source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
setKeyboard: function(name) {
|
||||
this.setSource("key", name);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new pointer input source with the given name
|
||||
*
|
||||
* @param {String} type - Name of the pointer source
|
||||
* @param {String} pointerType - Type of pointing device
|
||||
* @param {Bool} set - Set source as the default pointer source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
addPointer: function(name, pointerType="mouse", set=true) {
|
||||
this.createSource("pointer", name, {pointerType: pointerType});
|
||||
if (set) {
|
||||
this.setPointer(name);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the current default pointer source
|
||||
*
|
||||
* @param {String} name - Name of the pointer source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
setPointer: function(name) {
|
||||
this.setSource("pointer", name);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new wheel input source with the given name
|
||||
*
|
||||
* @param {String} type - Name of the wheel source
|
||||
* @param {Bool} set - Set source as the default wheel source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
addWheel: function(name, set=true) {
|
||||
this.createSource("wheel", name);
|
||||
if (set) {
|
||||
this.setWheel(name);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the current default wheel source
|
||||
*
|
||||
* @param {String} name - Name of the wheel source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
setWheel: function(name) {
|
||||
this.setSource("wheel", name);
|
||||
return this;
|
||||
},
|
||||
|
||||
createSource: function(type, name, parameters={}) {
|
||||
if (!this.sources.has(type)) {
|
||||
throw new Error(`${type} is not a valid action type`);
|
||||
}
|
||||
let sourceNames = new Set();
|
||||
for (let [_, name] of this.sourceOrder) {
|
||||
sourceNames.add(name);
|
||||
}
|
||||
if (!name) {
|
||||
do {
|
||||
name = "" + sourceNameIdx++;
|
||||
} while (sourceNames.has(name))
|
||||
} else {
|
||||
if (sourceNames.has(name)) {
|
||||
throw new Error(`Alreay have a source of type ${type} named ${name}.`);
|
||||
}
|
||||
}
|
||||
this.sources.get(type).set(name, new (this.sourceTypes.get(type))(parameters));
|
||||
this.currentSources.set(type, name);
|
||||
this.sourceOrder.push([type, name]);
|
||||
return this.sources.get(type).get(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert a new actions tick
|
||||
*
|
||||
* @param {Number?} duration - Minimum length of the tick in ms.
|
||||
* @returns {Actions}
|
||||
*/
|
||||
addTick: function(duration) {
|
||||
this.tickIdx += 1;
|
||||
if (duration) {
|
||||
this.pause(duration);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a pause to the current tick
|
||||
*
|
||||
* @param {Number?} duration - Minimum length of the tick in ms.
|
||||
* @param {String} sourceType - source type
|
||||
* @param {String?} sourceName - Named key, pointer or wheel source to use
|
||||
* or null for the default key, pointer or
|
||||
* wheel source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
pause: function(duration=0, sourceType="none", {sourceName=null}={}) {
|
||||
if (sourceType=="none")
|
||||
this.getSource("none").addPause(this, duration);
|
||||
else
|
||||
this.getSource(sourceType, sourceName).addPause(this, duration);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a keyDown event for the current default key source
|
||||
*
|
||||
* @param {String} key - Key to press
|
||||
* @param {String?} sourceName - Named key source to use or null for the default key source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
keyDown: function(key, {sourceName=null}={}) {
|
||||
let source = this.getSource("key", sourceName);
|
||||
source.keyDown(this, key);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a keyDown event for the current default key source
|
||||
*
|
||||
* @param {String} key - Key to release
|
||||
* @param {String?} sourceName - Named key source to use or null for the default key source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
keyUp: function(key, {sourceName=null}={}) {
|
||||
let source = this.getSource("key", sourceName);
|
||||
source.keyUp(this, key);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a pointerDown event for the current default pointer source
|
||||
*
|
||||
* @param {String} button - Button to press
|
||||
* @param {String?} sourceName - Named pointer source to use or null for the default
|
||||
* pointer source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
pointerDown: function({button=this.ButtonType.LEFT, sourceName=null,
|
||||
width, height, pressure, tangentialPressure,
|
||||
tiltX, tiltY, twist, altitudeAngle, azimuthAngle}={}) {
|
||||
let source = this.getSource("pointer", sourceName);
|
||||
source.pointerDown(this, button, width, height, pressure, tangentialPressure,
|
||||
tiltX, tiltY, twist, altitudeAngle, azimuthAngle);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a pointerUp event for the current default pointer source
|
||||
*
|
||||
* @param {String} button - Button to release
|
||||
* @param {String?} sourceName - Named pointer source to use or null for the default pointer
|
||||
* source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
pointerUp: function({button=this.ButtonType.LEFT, sourceName=null}={}) {
|
||||
let source = this.getSource("pointer", sourceName);
|
||||
source.pointerUp(this, button);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a move event for the current default pointer source
|
||||
*
|
||||
* @param {Number} x - Destination x coordinate
|
||||
* @param {Number} y - Destination y coordinate
|
||||
* @param {String|Element} origin - Origin of the coordinate system.
|
||||
* Either "pointer", "viewport" or an Element
|
||||
* @param {Number?} duration - Time in ms for the move
|
||||
* @param {String?} sourceName - Named pointer source to use or null for the default pointer
|
||||
* source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
pointerMove: function(x, y,
|
||||
{origin="viewport", duration, sourceName=null,
|
||||
width, height, pressure, tangentialPressure,
|
||||
tiltX, tiltY, twist, altitudeAngle, azimuthAngle}={}) {
|
||||
let source = this.getSource("pointer", sourceName);
|
||||
source.pointerMove(this, x, y, duration, origin, width, height, pressure,
|
||||
tangentialPressure, tiltX, tiltY, twist, altitudeAngle,
|
||||
azimuthAngle);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a scroll event for the current default wheel source
|
||||
*
|
||||
* @param {Number} x - mouse cursor x coordinate
|
||||
* @param {Number} y - mouse cursor y coordinate
|
||||
* @param {Number} deltaX - scroll delta value along the x-axis in pixels
|
||||
* @param {Number} deltaY - scroll delta value along the y-axis in pixels
|
||||
* @param {String|Element} origin - Origin of the coordinate system.
|
||||
* Either "viewport" or an Element
|
||||
* @param {Number?} duration - Time in ms for the scroll
|
||||
* @param {String?} sourceName - Named wheel source to use or null for the
|
||||
* default wheel source
|
||||
* @returns {Actions}
|
||||
*/
|
||||
scroll: function(x, y, deltaX, deltaY,
|
||||
{origin="viewport", duration, sourceName=null}={}) {
|
||||
let source = this.getSource("wheel", sourceName);
|
||||
source.scroll(this, x, y, deltaX, deltaY, duration, origin);
|
||||
return this;
|
||||
},
|
||||
};
|
||||
|
||||
function GeneralSource() {
|
||||
this.actions = new Map();
|
||||
}
|
||||
|
||||
GeneralSource.prototype = {
|
||||
serialize: function(tickCount, defaultTickDuration) {
|
||||
let actions = [];
|
||||
let data = {"type": "none", "actions": actions};
|
||||
for (let i=0; i<tickCount; i++) {
|
||||
if (this.actions.has(i)) {
|
||||
actions.push(this.actions.get(i));
|
||||
} else {
|
||||
actions.push({"type": "pause", duration: defaultTickDuration});
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
addPause: function(actions, duration) {
|
||||
let tick = actions.tickIdx;
|
||||
if (this.actions.has(tick)) {
|
||||
throw new Error(`Already have a pause action for the current tick`);
|
||||
}
|
||||
this.actions.set(tick, {type: "pause", duration: duration});
|
||||
},
|
||||
};
|
||||
|
||||
function KeySource() {
|
||||
this.actions = new Map();
|
||||
}
|
||||
|
||||
KeySource.prototype = {
|
||||
serialize: function(tickCount) {
|
||||
if (!this.actions.size) {
|
||||
return undefined;
|
||||
}
|
||||
let actions = [];
|
||||
let data = {"type": "key", "actions": actions};
|
||||
for (let i=0; i<tickCount; i++) {
|
||||
if (this.actions.has(i)) {
|
||||
actions.push(this.actions.get(i));
|
||||
} else {
|
||||
actions.push({"type": "pause"});
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
keyDown: function(actions, key) {
|
||||
let tick = actions.tickIdx;
|
||||
if (this.actions.has(tick)) {
|
||||
tick = actions.addTick().tickIdx;
|
||||
}
|
||||
this.actions.set(tick, {type: "keyDown", value: key});
|
||||
},
|
||||
|
||||
keyUp: function(actions, key) {
|
||||
let tick = actions.tickIdx;
|
||||
if (this.actions.has(tick)) {
|
||||
tick = actions.addTick().tickIdx;
|
||||
}
|
||||
this.actions.set(tick, {type: "keyUp", value: key});
|
||||
},
|
||||
|
||||
addPause: function(actions, duration) {
|
||||
let tick = actions.tickIdx;
|
||||
if (this.actions.has(tick)) {
|
||||
tick = actions.addTick().tickIdx;
|
||||
}
|
||||
this.actions.set(tick, {type: "pause", duration: duration});
|
||||
},
|
||||
};
|
||||
|
||||
function PointerSource(parameters={pointerType: "mouse"}) {
|
||||
let pointerType = parameters.pointerType || "mouse";
|
||||
if (!["mouse", "pen", "touch"].includes(pointerType)) {
|
||||
throw new Error(`Invalid pointerType ${pointerType}`);
|
||||
}
|
||||
this.type = pointerType;
|
||||
this.actions = new Map();
|
||||
}
|
||||
|
||||
function setPointerProperties(action, width, height, pressure, tangentialPressure,
|
||||
tiltX, tiltY, twist, altitudeAngle, azimuthAngle) {
|
||||
if (width) {
|
||||
action.width = width;
|
||||
}
|
||||
if (height) {
|
||||
action.height = height;
|
||||
}
|
||||
if (pressure) {
|
||||
action.pressure = pressure;
|
||||
}
|
||||
if (tangentialPressure) {
|
||||
action.tangentialPressure = tangentialPressure;
|
||||
}
|
||||
if (tiltX) {
|
||||
action.tiltX = tiltX;
|
||||
}
|
||||
if (tiltY) {
|
||||
action.tiltY = tiltY;
|
||||
}
|
||||
if (twist) {
|
||||
action.twist = twist;
|
||||
}
|
||||
if (altitudeAngle) {
|
||||
action.altitudeAngle = altitudeAngle;
|
||||
}
|
||||
if (azimuthAngle) {
|
||||
action.azimuthAngle = azimuthAngle;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
PointerSource.prototype = {
|
||||
serialize: function(tickCount) {
|
||||
if (!this.actions.size) {
|
||||
return undefined;
|
||||
}
|
||||
let actions = [];
|
||||
let data = {"type": "pointer", "actions": actions, "parameters": {"pointerType": this.type}};
|
||||
for (let i=0; i<tickCount; i++) {
|
||||
if (this.actions.has(i)) {
|
||||
actions.push(this.actions.get(i));
|
||||
} else {
|
||||
actions.push({"type": "pause"});
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
pointerDown: function(actions, button, width, height, pressure, tangentialPressure,
|
||||
tiltX, tiltY, twist, altitudeAngle, azimuthAngle) {
|
||||
let tick = actions.tickIdx;
|
||||
if (this.actions.has(tick)) {
|
||||
tick = actions.addTick().tickIdx;
|
||||
}
|
||||
let actionProperties = setPointerProperties({type: "pointerDown", button}, width, height,
|
||||
pressure, tangentialPressure, tiltX, tiltY,
|
||||
twist, altitudeAngle, azimuthAngle);
|
||||
this.actions.set(tick, actionProperties);
|
||||
},
|
||||
|
||||
pointerUp: function(actions, button) {
|
||||
let tick = actions.tickIdx;
|
||||
if (this.actions.has(tick)) {
|
||||
tick = actions.addTick().tickIdx;
|
||||
}
|
||||
this.actions.set(tick, {type: "pointerUp", button});
|
||||
},
|
||||
|
||||
pointerMove: function(actions, x, y, duration, origin, width, height, pressure,
|
||||
tangentialPressure, tiltX, tiltY, twist, altitudeAngle, azimuthAngle) {
|
||||
let tick = actions.tickIdx;
|
||||
if (this.actions.has(tick)) {
|
||||
tick = actions.addTick().tickIdx;
|
||||
}
|
||||
let moveAction = {type: "pointerMove", x, y, origin};
|
||||
if (duration) {
|
||||
moveAction.duration = duration;
|
||||
}
|
||||
let actionProperties = setPointerProperties(moveAction, width, height, pressure,
|
||||
tangentialPressure, tiltX, tiltY, twist,
|
||||
altitudeAngle, azimuthAngle);
|
||||
this.actions.set(tick, actionProperties);
|
||||
},
|
||||
|
||||
addPause: function(actions, duration) {
|
||||
let tick = actions.tickIdx;
|
||||
if (this.actions.has(tick)) {
|
||||
tick = actions.addTick().tickIdx;
|
||||
}
|
||||
this.actions.set(tick, {type: "pause", duration: duration});
|
||||
},
|
||||
};
|
||||
|
||||
function WheelSource() {
|
||||
this.actions = new Map();
|
||||
}
|
||||
|
||||
WheelSource.prototype = {
|
||||
serialize: function(tickCount) {
|
||||
if (!this.actions.size) {
|
||||
return undefined;
|
||||
}
|
||||
let actions = [];
|
||||
let data = {"type": "wheel", "actions": actions};
|
||||
for (let i=0; i<tickCount; i++) {
|
||||
if (this.actions.has(i)) {
|
||||
actions.push(this.actions.get(i));
|
||||
} else {
|
||||
actions.push({"type": "pause"});
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
scroll: function(actions, x, y, deltaX, deltaY, duration, origin) {
|
||||
let tick = actions.tickIdx;
|
||||
if (this.actions.has(tick)) {
|
||||
tick = actions.addTick().tickIdx;
|
||||
}
|
||||
this.actions.set(tick, {type: "scroll", x, y, deltaX, deltaY, origin});
|
||||
if (duration) {
|
||||
this.actions.get(tick).duration = duration;
|
||||
}
|
||||
},
|
||||
|
||||
addPause: function(actions, duration) {
|
||||
let tick = actions.tickIdx;
|
||||
if (this.actions.has(tick)) {
|
||||
tick = actions.addTick().tickIdx;
|
||||
}
|
||||
this.actions.set(tick, {type: "pause", duration: duration});
|
||||
},
|
||||
};
|
||||
|
||||
test_driver.Actions = Actions;
|
||||
})();
|
|
@ -0,0 +1 @@
|
|||
// This file intentionally left blank
|
1259
Tests/LibWeb/Text/input/wpt-import/resources/testdriver.js
Normal file
1259
Tests/LibWeb/Text/input/wpt-import/resources/testdriver.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,198 @@
|
|||
/* Utilities related to WAI-ARIA */
|
||||
|
||||
const AriaUtils = {
|
||||
|
||||
/*
|
||||
Tests simple role assignment: <div role="alert">x</div>
|
||||
Not intended for nested, context-dependent, or other complex role tests.
|
||||
|
||||
Ex: AriaUtils.assignAndVerifyRolesByRoleNames(["group", "main", "button"])
|
||||
|
||||
*/
|
||||
assignAndVerifyRolesByRoleNames: function(roleNames) {
|
||||
if (!Array.isArray(roleNames) || !roleNames.length) {
|
||||
throw `Param roleNames of assignAndVerifyRolesByRoleNames("${roleNames}") should be an array containing at least one role string.`;
|
||||
}
|
||||
for (const role of roleNames) {
|
||||
promise_test(async t => {
|
||||
let el = document.createElement("div");
|
||||
el.appendChild(document.createTextNode("x"));
|
||||
el.setAttribute("role", role); // el.role not yet supported by Gecko.
|
||||
document.body.appendChild(el);
|
||||
const computedRole = await test_driver.get_computed_role(el);
|
||||
assert_equals(computedRole, role, el.outerHTML);
|
||||
}, `role: ${role}`);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
Tests computed ROLE of all elements matching selector
|
||||
against the string value of their data-expectedrole attribute.
|
||||
|
||||
Ex: <div role="list"
|
||||
data-testname="optional unique test name"
|
||||
data-expectedrole="list"
|
||||
class="ex">
|
||||
|
||||
AriaUtils.verifyRolesBySelector(".ex")
|
||||
|
||||
*/
|
||||
verifyRolesBySelector: function(selector, roleTestNamePrefix) {
|
||||
const els = document.querySelectorAll(selector);
|
||||
if (!els.length) {
|
||||
throw `Selector passed in verifyRolesBySelector("${selector}") should match at least one element.`;
|
||||
}
|
||||
for (const el of els) {
|
||||
let role = el.getAttribute("data-expectedrole");
|
||||
let testName = el.getAttribute("data-testname") || role; // data-testname optional if role is unique per test file
|
||||
if (typeof roleTestNamePrefix !== "undefined") {
|
||||
testName = roleTestNamePrefix + testName;
|
||||
}
|
||||
promise_test(async t => {
|
||||
const expectedRole = el.getAttribute("data-expectedrole");
|
||||
const computedRole = await test_driver.get_computed_role(el);
|
||||
assert_equals(computedRole, expectedRole, el.outerHTML);
|
||||
}, `${testName}`);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
Tests computed ROLE of selected elements matching selector
|
||||
against the string value of provided roles array.
|
||||
|
||||
Ex: <foo
|
||||
data-testname="verify fooRole or barRole role on span"
|
||||
class="ex-foo-or-bar">
|
||||
|
||||
AriaUtils.verifyRoleOrVariantRolesBySelector(".ex-foo-or-bar", ["fooRole", "barRole"]);
|
||||
|
||||
See also helper function verifyGenericRolesBySelector shorthand of the above using ["generic", "", "none"].
|
||||
|
||||
Note: This function should not be used to circumvent unexpected interop differences in implementations.
|
||||
It should only be used in specific cases (like "generic") determined by ARIA WG or other spec maintainers to be acceptable for the purposes of testing.
|
||||
|
||||
*/
|
||||
verifyRoleOrVariantRolesBySelector: function(selector, roles) {
|
||||
const els = document.querySelectorAll(selector);
|
||||
if (!els.length) {
|
||||
throw `Selector "${selector}" should match at least one element.`;
|
||||
}
|
||||
if (!roles.length || roles.length < 2) {
|
||||
throw `Roles array ["${roles.join('", "')}"] should include at least two strings, a primary role and at least one acceptable implementation-specific variant. E.g. ["generic", "", "none"]…`;
|
||||
}
|
||||
for (const el of els) {
|
||||
let testName = el.getAttribute("data-testname");
|
||||
promise_test(async t => {
|
||||
const expectedRoles = roles;
|
||||
const computedRole = await test_driver.get_computed_role(el);
|
||||
for (role of roles){
|
||||
if (computedRole === role) {
|
||||
return assert_equals(computedRole, role, `Computed Role: "${computedRole}" matches one of the acceptable role strings in ["${roles.join('", "')}"]: ${el.outerHTML}`);
|
||||
}
|
||||
}
|
||||
return assert_false(true, `Computed Role: "${computedRole}" does not match any of the acceptable role strings in ["${roles.join('", "')}"]: ${el.outerHTML}`);
|
||||
}, `${testName}`);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
Helper function for "generic" ROLE tests.
|
||||
|
||||
Ex: <span
|
||||
data-testname="verify generic, none, or empty computed role on span"
|
||||
class="ex-generic">
|
||||
|
||||
AriaUtils.verifyGenericRolesBySelector(".ex-generic");
|
||||
|
||||
This helper function is equivalant to AriaUtils.verifyRoleOrVariantRolesBySelector(".ex-generic", ["generic", "", "none"]);
|
||||
See various issues and discussions linked from https://github.com/web-platform-tests/interop-accessibility/issues/48
|
||||
|
||||
*/
|
||||
verifyGenericRolesBySelector: function(selector) {
|
||||
// ARIA WG determined implementation variants "none" (Chromium), and the empty string "" (WebKit), are sufficiently equivalent to "generic" for WPT test verification of HTML-AAM.
|
||||
// See various discussions linked from https://github.com/web-platform-tests/interop-accessibility/issues/48
|
||||
this.verifyRoleOrVariantRolesBySelector(selector, ["generic", "", "none"]);
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
Tests computed LABEL of all elements matching selector
|
||||
against the string value of their data-expectedlabel attribute.
|
||||
|
||||
Ex: <div aria-label="foo"
|
||||
data-testname="optional unique test name"
|
||||
data-expectedlabel="foo"
|
||||
class="ex">
|
||||
|
||||
AriaUtils.verifyLabelsBySelector(".ex")
|
||||
|
||||
*/
|
||||
verifyLabelsBySelector: function(selector, labelTestNamePrefix) {
|
||||
const els = document.querySelectorAll(selector);
|
||||
if (!els.length) {
|
||||
throw `Selector passed in verifyLabelsBySelector("${selector}") should match at least one element.`;
|
||||
}
|
||||
for (const el of els) {
|
||||
let label = el.getAttribute("data-expectedlabel");
|
||||
let testName = el.getAttribute("data-testname") || label; // data-testname optional if label is unique per test file
|
||||
if (typeof labelTestNamePrefix !== "undefined") {
|
||||
testName = labelTestNamePrefix + testName;
|
||||
}
|
||||
promise_test(async t => {
|
||||
const expectedLabel = el.getAttribute("data-expectedlabel");
|
||||
// XXX: Ladybird-specific change: upstream WPT has test_driver.get_computed_label(el) here,
|
||||
// but we’ve changed that to the Ladybird-specific window.internals.getComputedLabel(el).
|
||||
let computedLabel = await window.internals.getComputedLabel(el);
|
||||
assert_not_equals(computedLabel, null, `get_computed_label(el) shouldn't return null for ${el.outerHTML}`);
|
||||
|
||||
// See:
|
||||
// - https://github.com/w3c/accname/pull/165
|
||||
// - https://github.com/w3c/accname/issues/192
|
||||
// - https://github.com/w3c/accname/issues/208
|
||||
//
|
||||
// AccName references HTML's definition of ASCII Whitespace
|
||||
// https://infra.spec.whatwg.org/#ascii-whitespace
|
||||
// which matches tab (\t), newline (\n), formfeed (\f), return (\r), and regular space (\u0020).
|
||||
// but it does NOT match non-breaking space (\xA0,\u00A0) and others matched by \s
|
||||
const asciiWhitespace = /[\t\n\f\r\u0020]+/g;
|
||||
computedLabel = computedLabel.replace(asciiWhitespace, '\u0020').replace(/^\u0020|\u0020$/g, '');
|
||||
|
||||
assert_equals(computedLabel, expectedLabel, el.outerHTML);
|
||||
}, `${testName}`);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
Tests computed LABEL and ROLE of all elements matching selector using existing
|
||||
verifyLabelsBySelector(), verifyRolesBySelector() functions and passes a test name prefix
|
||||
to ensure uniqueness.
|
||||
|
||||
Ex: <div aria-label="foo" role="button"
|
||||
data-testname="div with role=button is labelled via aria-label"
|
||||
data-expectedlabel="foo"
|
||||
data-expectedrole="button"
|
||||
class="ex-role-and-label">
|
||||
|
||||
AriaUtils.verifyRolesAndLabelsBySelector(".ex-role-and-label")
|
||||
|
||||
*/
|
||||
verifyRolesAndLabelsBySelector: function(selector) {
|
||||
let labelTestNamePrefix = "Label: ";
|
||||
let roleTestNamePrefix = "Role: ";
|
||||
const els = document.querySelectorAll(selector);
|
||||
if (!els.length) {
|
||||
throw `Selector passed in verifyRolesAndLabelsBySelector("${selector}") should match at least one element.`;
|
||||
}
|
||||
for (const el of els) {
|
||||
el.classList.add("ex-label-only");
|
||||
el.classList.add("ex-role-only");
|
||||
}
|
||||
this.verifyLabelsBySelector(".ex-label-only", labelTestNamePrefix);
|
||||
this.verifyRolesBySelector(".ex-role-only", roleTestNamePrefix);
|
||||
},
|
||||
};
|
||||
|
Loading…
Add table
Reference in a new issue