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:
sideshowbarker 2024-10-30 02:44:04 +09:00 committed by Tim Ledbetter
parent 1b4e609eb2
commit 120bc52f23
Notes: github-actions[bot] 2024-10-31 01:17:42 +00:00
6 changed files with 2227 additions and 0 deletions

View file

@ -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

View file

@ -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>

View file

@ -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;
})();

View file

@ -0,0 +1 @@
// This file intentionally left blank

File diff suppressed because it is too large Load diff

View file

@ -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 weve 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);
},
};