summaryrefslogtreecommitdiffstats
path: root/accessible/tests/browser/windows/uia
diff options
context:
space:
mode:
Diffstat (limited to 'accessible/tests/browser/windows/uia')
-rw-r--r--accessible/tests/browser/windows/uia/browser.toml7
-rw-r--r--accessible/tests/browser/windows/uia/browser_generalProps.js350
-rw-r--r--accessible/tests/browser/windows/uia/browser_gridPatterns.js161
-rw-r--r--accessible/tests/browser/windows/uia/browser_relationProps.js143
-rw-r--r--accessible/tests/browser/windows/uia/browser_selectionPatterns.js226
-rw-r--r--accessible/tests/browser/windows/uia/browser_simplePatterns.js97
-rw-r--r--accessible/tests/browser/windows/uia/head.js14
7 files changed, 997 insertions, 1 deletions
diff --git a/accessible/tests/browser/windows/uia/browser.toml b/accessible/tests/browser/windows/uia/browser.toml
index 75728f56d7..13b1c12cc0 100644
--- a/accessible/tests/browser/windows/uia/browser.toml
+++ b/accessible/tests/browser/windows/uia/browser.toml
@@ -11,8 +11,15 @@ support-files = ["head.js"]
["browser_elementFromPoint.js"]
["browser_focus.js"]
+
["browser_generalProps.js"]
+["browser_gridPatterns.js"]
+
+["browser_relationProps.js"]
+
+["browser_selectionPatterns.js"]
+
["browser_simplePatterns.js"]
["browser_tree.js"]
diff --git a/accessible/tests/browser/windows/uia/browser_generalProps.js b/accessible/tests/browser/windows/uia/browser_generalProps.js
index 5cfda226d0..244c9e4b1b 100644
--- a/accessible/tests/browser/windows/uia/browser_generalProps.js
+++ b/accessible/tests/browser/windows/uia/browser_generalProps.js
@@ -4,6 +4,12 @@
"use strict";
+/* eslint-disable camelcase */
+// From https://learn.microsoft.com/en-us/windows/win32/winauto/landmark-type-identifiers
+const UIA_CustomLandmarkTypeId = 80000;
+const UIA_MainLandmarkTypeId = 80002;
+/* eslint-enable camelcase */
+
/**
* Test the Name property.
*/
@@ -103,3 +109,347 @@ addUiaTask(
ok(await runPython(`p.CurrentIsEnabled`), "p has IsEnabled true");
}
);
+
+async function testGroupPos(id, level, pos, size) {
+ await assignPyVarToUiaWithId(id);
+ is(await runPython(`${id}.CurrentLevel`), level, `${id} Level correct`);
+ is(
+ await runPython(`${id}.CurrentPositionInSet`),
+ pos,
+ `${id} PositionInSet correct`
+ );
+ is(
+ await runPython(`${id}.CurrentSizeOfSet`),
+ size,
+ `${id} SizeOfSet correct`
+ );
+}
+
+/**
+ * Test the Level, PositionInSet and SizeOfSet properties.
+ */
+addUiaTask(
+ `
+<ul>
+ <li id="li1">li1<ul id="ul1">
+ <li id="li2a">li2a</li>
+ <li id="li2b" hidden>li2b</li>
+ <li id="li2c">li2c</li>
+ </ul></li>
+</ul>
+<h2 id="h2">h2</h2>
+<button id="button">button</button>
+ `,
+ async function testGroupPosProps(browser) {
+ await definePyVar("doc", `getDocUia()`);
+ await testGroupPos("li1", 1, 1, 1);
+ await testGroupPos("li2a", 2, 1, 2);
+ await testGroupPos("li2c", 2, 2, 2);
+ info("Showing li2b");
+ // There aren't events in any API for a change to group position properties
+ // because this would be too spammy and isn't particularly useful given
+ // how frequently these can change.
+ let shown = waitForEvent(EVENT_SHOW, "li2b");
+ await invokeContentTask(browser, [], () => {
+ content.document.getElementById("li2b").hidden = false;
+ });
+ await shown;
+ await testGroupPos("li2a", 2, 1, 3);
+ await testGroupPos("li2b", 2, 2, 3);
+ await testGroupPos("li2c", 2, 3, 3);
+
+ // The IA2 -> UIA proxy doesn't map heading level to the Level property.
+ if (gIsUiaEnabled) {
+ await testGroupPos("h2", 2, 0, 0);
+ }
+ await testGroupPos("button", 0, 0, 0);
+ }
+);
+
+/**
+ * Test the FrameworkId property.
+ */
+addUiaTask(
+ `<button id="button">button</button>`,
+ async function testFrameworkId() {
+ await definePyVar("doc", `getDocUia()`);
+ is(
+ await runPython(`doc.CurrentFrameworkId`),
+ "Gecko",
+ "doc FrameworkId is correct"
+ );
+ await assignPyVarToUiaWithId("button");
+ is(
+ await runPython(`button.CurrentFrameworkId`),
+ "Gecko",
+ "button FrameworkId is correct"
+ );
+ }
+);
+
+/**
+ * Test the ClassName property.
+ */
+addUiaTask(
+ `
+<p id="p">p</p>
+<button id="button" class="c1">button</button>
+ `,
+ async function testClassName(browser, docAcc) {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("p");
+ ok(!(await runPython(`p.CurrentClassName`)), "p has no ClassName");
+
+ await assignPyVarToUiaWithId("button");
+ is(
+ await runPython(`button.CurrentClassName`),
+ "c1",
+ "button has correct ClassName"
+ );
+ info("Changing button class");
+ await invokeSetAttribute(browser, "button", "class", "c2 c3");
+ // Gecko doesn't fire an event for class changes, as this isn't useful for
+ // clients.
+ const button = findAccessibleChildByID(docAcc, "button");
+ await untilCacheIs(
+ () => button.attributes.getStringProperty("class"),
+ "c2 c3",
+ "button class updated"
+ );
+ is(
+ await runPython(`button.CurrentClassName`),
+ "c2 c3",
+ "button has correct ClassName"
+ );
+ },
+ // The IA2 -> UIA proxy doesn't support ClassName.
+ { uiaEnabled: true, uiaDisabled: false }
+);
+
+/**
+ * Test the AriaRole property.
+ */
+addUiaTask(
+ `
+<div id="button" role="button">button</div>
+<div id="main" role="main">main</div>
+<div id="unknown" role="unknown">unknown</div>
+<button id="computedButton">computedButton</button>
+<h1 id="computedHeading">computedHeading</h1>
+<main id="computedMain">computedMain</main>
+<div id="generic">generic</div>
+ `,
+ async function testAriaRole() {
+ await definePyVar("doc", `getDocUia()`);
+ is(
+ await runPython(`findUiaByDomId(doc, "button").CurrentAriaRole`),
+ "button",
+ "button has correct AriaRole"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "main").CurrentAriaRole`),
+ "main",
+ "main has correct AriaRole"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "unknown").CurrentAriaRole`),
+ "unknown",
+ "unknown has correct AriaRole"
+ );
+ // The IA2 -> UIA proxy doesn't compute ARIA roles.
+ if (gIsUiaEnabled) {
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "computedButton").CurrentAriaRole`
+ ),
+ "button",
+ "computedButton has correct AriaRole"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "computedMain").CurrentAriaRole`),
+ "main",
+ "computedMain has correct AriaRole"
+ );
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "computedHeading").CurrentAriaRole`
+ ),
+ "heading",
+ "computedHeading has correct AriaRole"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "generic").CurrentAriaRole`),
+ "generic",
+ "generic has correct AriaRole"
+ );
+ }
+ }
+);
+
+/**
+ * Test the LocalizedControlType property. We don't support this ourselves, but
+ * the system provides it based on ControlType and AriaRole.
+ */
+addUiaTask(
+ `
+<button id="button">button</button>
+<h1 id="h1">h1</h1>
+<main id="main">main</main>
+ `,
+ async function testLocalizedControlType() {
+ await definePyVar("doc", `getDocUia()`);
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "button").CurrentLocalizedControlType`
+ ),
+ "button",
+ "button has correct LocalizedControlType"
+ );
+ // The IA2 -> UIA proxy doesn't compute ARIA roles, so it can't compute the
+ // correct LocalizedControlType for these either.
+ if (gIsUiaEnabled) {
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "h1").CurrentLocalizedControlType`
+ ),
+ "heading",
+ "h1 has correct LocalizedControlType"
+ );
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "main").CurrentLocalizedControlType`
+ ),
+ "main",
+ "main has correct LocalizedControlType"
+ );
+ }
+ }
+);
+
+/**
+ * Test the LandmarkType property.
+ */
+addUiaTask(
+ `
+<div id="main" role="main">main</div>
+<main id="htmlMain">htmlMain</main>
+<div id="banner" role="banner">banner</div>
+<header id="header">header</header>
+<div id="region" role="region" aria-label="region">region</div>
+<div id="unnamedRegion" role="region">unnamedRegion</div>
+<main id="mainBanner" role="banner">mainBanner</main>
+<div id="none">none</div>
+ `,
+ async function testLandmarkType() {
+ await definePyVar("doc", `getDocUia()`);
+ is(
+ await runPython(`findUiaByDomId(doc, "main").CurrentLandmarkType`),
+ UIA_MainLandmarkTypeId,
+ "main has correct LandmarkType"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "htmlMain").CurrentLandmarkType`),
+ UIA_MainLandmarkTypeId,
+ "htmlMain has correct LandmarkType"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "banner").CurrentLandmarkType`),
+ UIA_CustomLandmarkTypeId,
+ "banner has correct LandmarkType"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "header").CurrentLandmarkType`),
+ UIA_CustomLandmarkTypeId,
+ "header has correct LandmarkType"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "region").CurrentLandmarkType`),
+ UIA_CustomLandmarkTypeId,
+ "region has correct LandmarkType"
+ );
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "unnamedRegion").CurrentLandmarkType`
+ ),
+ 0,
+ "unnamedRegion has correct LandmarkType"
+ );
+ // ARIA role takes precedence.
+ is(
+ await runPython(`findUiaByDomId(doc, "mainBanner").CurrentLandmarkType`),
+ UIA_CustomLandmarkTypeId,
+ "mainBanner has correct LandmarkType"
+ );
+ is(
+ await runPython(`findUiaByDomId(doc, "none").CurrentLandmarkType`),
+ 0,
+ "none has correct LandmarkType"
+ );
+ }
+);
+
+/**
+ * Test the LocalizedLandmarkType property.
+ */
+addUiaTask(
+ `
+<div id="main" role="main">main</div>
+<div id="contentinfo" role="contentinfo">contentinfo</div>
+<div id="region" role="region" aria-label="region">region</div>
+<div id="unnamedRegion" role="region">unnamedRegion</div>
+<main id="mainBanner" role="banner">mainBanner</main>
+<div id="none">none</div>
+ `,
+ async function testLocalizedLandmarkType() {
+ await definePyVar("doc", `getDocUia()`);
+ // Provided by the system.
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "main").CurrentLocalizedLandmarkType`
+ ),
+ "main",
+ "main has correct LocalizedLandmarkType"
+ );
+ // The IA2 -> UIA proxy doesn't follow the Core AAM spec for this role.
+ if (gIsUiaEnabled) {
+ // Provided by us.
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "contentinfo").CurrentLocalizedLandmarkType`
+ ),
+ "content information",
+ "contentinfo has correct LocalizedLandmarkType"
+ );
+ }
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "region").CurrentLocalizedLandmarkType`
+ ),
+ "region",
+ "region has correct LocalizedLandmarkType"
+ );
+ // Invalid landmark.
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "unnamedRegion").CurrentLocalizedLandmarkType`
+ ),
+ "",
+ "unnamedRegion has correct LocalizedLandmarkType"
+ );
+ // ARIA role takes precedence.
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "mainBanner").CurrentLocalizedLandmarkType`
+ ),
+ "banner",
+ "mainBanner has correct LocalizedLandmarkType"
+ );
+ is(
+ await runPython(
+ `findUiaByDomId(doc, "none").CurrentLocalizedLandmarkType`
+ ),
+ "",
+ "none has correct LocalizedLandmarkType"
+ );
+ }
+);
diff --git a/accessible/tests/browser/windows/uia/browser_gridPatterns.js b/accessible/tests/browser/windows/uia/browser_gridPatterns.js
new file mode 100644
index 0000000000..24c80a6340
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_gridPatterns.js
@@ -0,0 +1,161 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* eslint-disable camelcase */
+const RowOrColumnMajor_RowMajor = 0;
+/* eslint-enable camelcase */
+
+const SNIPPET = `
+<table id="table">
+ <tr><th id="a">a</th><th id="b">b</th><th id="c">c</th></tr>
+ <tr><th id="dg" rowspan="2">dg</th><td id="ef" colspan="2" headers="b c">ef</td></tr>
+ <tr><th id="h">h</th><td id="i" headers="dg h">i</td></tr>
+ <tr><td id="jkl" colspan="3" headers="a b c">jkl</td></tr>
+</table>
+<button id="button">button</button>
+`;
+
+async function testGridGetItem(row, col, cellId) {
+ is(
+ await runPython(`pattern.GetItem(${row}, ${col}).CurrentAutomationId`),
+ cellId,
+ `GetItem with row ${row} and col ${col} returned ${cellId}`
+ );
+}
+
+async function testGridItemProps(id, row, col, rowSpan, colSpan, gridId) {
+ await assignPyVarToUiaWithId(id);
+ await definePyVar("pattern", `getUiaPattern(${id}, "GridItem")`);
+ ok(await runPython(`bool(pattern)`), `${id} has GridItem pattern`);
+ is(await runPython(`pattern.CurrentRow`), row, `${id} has correct Row`);
+ is(await runPython(`pattern.CurrentColumn`), col, `${id} has correct Column`);
+ is(
+ await runPython(`pattern.CurrentRowSpan`),
+ rowSpan,
+ `${id} has correct RowSpan`
+ );
+ is(
+ await runPython(`pattern.CurrentColumnSpan`),
+ colSpan,
+ `${id} has correct ColumnSpan`
+ );
+ is(
+ await runPython(`pattern.CurrentContainingGrid.CurrentAutomationId`),
+ gridId,
+ `${id} ContainingGridItem is ${gridId}`
+ );
+}
+
+async function testTableItemProps(id, rowHeaders, colHeaders) {
+ await assignPyVarToUiaWithId(id);
+ await definePyVar("pattern", `getUiaPattern(${id}, "TableItem")`);
+ ok(await runPython(`bool(pattern)`), `${id} has TableItem pattern`);
+ await isUiaElementArray(
+ `pattern.GetCurrentRowHeaderItems()`,
+ rowHeaders,
+ `${id} has correct RowHeaderItems`
+ );
+ await isUiaElementArray(
+ `pattern.GetCurrentColumnHeaderItems()`,
+ colHeaders,
+ `${id} has correct ColumnHeaderItems`
+ );
+}
+
+/**
+ * Test the Grid pattern.
+ */
+addUiaTask(SNIPPET, async function testGrid() {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("table");
+ await definePyVar("pattern", `getUiaPattern(table, "Grid")`);
+ ok(await runPython(`bool(pattern)`), "table has Grid pattern");
+ is(
+ await runPython(`pattern.CurrentRowCount`),
+ 4,
+ "table has correct RowCount"
+ );
+ is(
+ await runPython(`pattern.CurrentColumnCount`),
+ 3,
+ "table has correct ColumnCount"
+ );
+ await testGridGetItem(0, 0, "a");
+ await testGridGetItem(0, 1, "b");
+ await testGridGetItem(0, 2, "c");
+ await testGridGetItem(1, 0, "dg");
+ await testGridGetItem(1, 1, "ef");
+ await testGridGetItem(1, 2, "ef");
+ await testGridGetItem(2, 0, "dg");
+ await testGridGetItem(2, 1, "h");
+ await testGridGetItem(2, 2, "i");
+
+ await testPatternAbsent("button", "Grid");
+});
+
+/**
+ * Test the GridItem pattern.
+ */
+addUiaTask(SNIPPET, async function testGridItem() {
+ await definePyVar("doc", `getDocUia()`);
+ await testGridItemProps("a", 0, 0, 1, 1, "table");
+ await testGridItemProps("b", 0, 1, 1, 1, "table");
+ await testGridItemProps("c", 0, 2, 1, 1, "table");
+ await testGridItemProps("dg", 1, 0, 2, 1, "table");
+ await testGridItemProps("ef", 1, 1, 1, 2, "table");
+ await testGridItemProps("jkl", 3, 0, 1, 3, "table");
+
+ await testPatternAbsent("button", "GridItem");
+});
+
+/**
+ * Test the Table pattern.
+ */
+addUiaTask(
+ SNIPPET,
+ async function testTable() {
+ await definePyVar("doc", `getDocUia()`);
+ await assignPyVarToUiaWithId("table");
+ await definePyVar("pattern", `getUiaPattern(table, "Table")`);
+ ok(await runPython(`bool(pattern)`), "table has Table pattern");
+ await isUiaElementArray(
+ `pattern.GetCurrentRowHeaders()`,
+ ["dg", "h"],
+ "table has correct RowHeaders"
+ );
+ await isUiaElementArray(
+ `pattern.GetCurrentColumnHeaders()`,
+ ["a", "b", "c"],
+ "table has correct ColumnHeaders"
+ );
+ is(
+ await runPython(`pattern.CurrentRowOrColumnMajor`),
+ RowOrColumnMajor_RowMajor,
+ "table has correct RowOrColumnMajor"
+ );
+
+ await testPatternAbsent("button", "Table");
+ },
+ // The IA2 -> UIA proxy doesn't support the Row/ColumnHeaders properties.
+ { uiaEnabled: true, uiaDisabled: false }
+);
+
+/**
+ * Test the TableItem pattern.
+ */
+addUiaTask(SNIPPET, async function testTableItem() {
+ await definePyVar("doc", `getDocUia()`);
+ await testTableItemProps("a", [], []);
+ await testTableItemProps("b", [], []);
+ await testTableItemProps("c", [], []);
+ await testTableItemProps("dg", [], ["a"]);
+ await testTableItemProps("ef", ["dg"], ["b", "c"]);
+ await testTableItemProps("h", ["dg"], ["b"]);
+ await testTableItemProps("i", ["dg", "h"], ["c"]);
+ await testTableItemProps("jkl", [], ["a", "b", "c"]);
+
+ await testPatternAbsent("button", "TableItem");
+});
diff --git a/accessible/tests/browser/windows/uia/browser_relationProps.js b/accessible/tests/browser/windows/uia/browser_relationProps.js
new file mode 100644
index 0000000000..ff4059f99e
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_relationProps.js
@@ -0,0 +1,143 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function testUiaRelationArray(id, prop, targets) {
+ return isUiaElementArray(
+ `findUiaByDomId(doc, "${id}").Current${prop}`,
+ targets,
+ `${id} has correct ${prop} targets`
+ );
+}
+
+/**
+ * Test the ControllerFor property.
+ */
+addUiaTask(
+ `
+<input id="controls" aria-controls="t1 t2">
+<input id="error" aria-errormessage="t3 t4" aria-invalid="true">
+<input id="controlsError" aria-controls="t1 t2" aria-errormessage="t3 t4" aria-invalid="true">
+<div id="t1">t1</div>
+<div id="t2">t2</div>
+<div id="t3">t3</div>
+<div id="t4">t4</div>
+<button id="none">none</button>
+ `,
+ async function testControllerFor() {
+ await definePyVar("doc", `getDocUia()`);
+ await testUiaRelationArray("controls", "ControllerFor", ["t1", "t2"]);
+ // The IA2 -> UIA proxy doesn't support IA2_RELATION_ERROR.
+ if (gIsUiaEnabled) {
+ await testUiaRelationArray("error", "ControllerFor", ["t3", "t4"]);
+ await testUiaRelationArray("controlsError", "ControllerFor", [
+ "t1",
+ "t2",
+ "t3",
+ "t4",
+ ]);
+ }
+ await testUiaRelationArray("none", "ControllerFor", []);
+ }
+);
+
+/**
+ * Test the DescribedBy property.
+ */
+addUiaTask(
+ `
+<input id="describedby" aria-describedby="t1 t2">
+<input id="details" aria-details="t3 t4">
+<input id="describedbyDetails" aria-describedby="t1 t2" aria-details="t3 t4" aria-invalid="true">
+<div id="t1">t1</div>
+<div id="t2">t2</div>
+<div id="t3">t3</div>
+<div id="t4">t4</div>
+<button id="none">none</button>
+ `,
+ async function testDescribedBy() {
+ await definePyVar("doc", `getDocUia()`);
+ await testUiaRelationArray("describedby", "DescribedBy", ["t1", "t2"]);
+ // The IA2 -> UIA proxy doesn't support IA2_RELATION_DETAILS.
+ if (gIsUiaEnabled) {
+ await testUiaRelationArray("details", "DescribedBy", ["t3", "t4"]);
+ await testUiaRelationArray("describedbyDetails", "DescribedBy", [
+ "t1",
+ "t2",
+ "t3",
+ "t4",
+ ]);
+ }
+ await testUiaRelationArray("none", "DescribedBy", []);
+ }
+);
+
+/**
+ * Test the FlowsFrom and FlowsTo properties.
+ */
+addUiaTask(
+ `
+<div id="t1" aria-flowto="t2">t1</div>
+<div id="t2">t2</div>
+<button id="none">none</button>
+ `,
+ async function testFlows() {
+ await definePyVar("doc", `getDocUia()`);
+ await testUiaRelationArray("t1", "FlowsTo", ["t2"]);
+ await testUiaRelationArray("t2", "FlowsFrom", ["t1"]);
+ await testUiaRelationArray("none", "FlowsFrom", []);
+ await testUiaRelationArray("none", "FlowsTo", []);
+ }
+);
+
+/**
+ * Test the LabeledBy property.
+ */
+addUiaTask(
+ `
+<label id="label">label</label>
+<input id="input" aria-labelledby="label">
+<label id="wrappingLabel">
+ <input id="wrappedInput" value="wrappedInput">
+ <p id="wrappingLabelP">wrappingLabel</p>
+</label>
+<button id="button" aria-labelledby="label">content</button>
+<button id="noLabel">noLabel</button>
+ `,
+ async function testLabeledBy() {
+ await definePyVar("doc", `getDocUia()`);
+ // input's LabeledBy should be label's text leaf.
+ let result = await runPython(`
+ input = findUiaByDomId(doc, "input")
+ label = findUiaByDomId(doc, "label")
+ labelLeaf = uiaClient.RawViewWalker.GetFirstChildElement(label)
+ return uiaClient.CompareElements(input.CurrentLabeledBy, labelLeaf)
+ `);
+ ok(result, "input has correct LabeledBy");
+ // wrappedInput's LabeledBy should be wrappingLabelP's text leaf.
+ result = await runPython(`
+ wrappedInput = findUiaByDomId(doc, "wrappedInput")
+ wrappingLabelP = findUiaByDomId(doc, "wrappingLabelP")
+ wrappingLabelLeaf = uiaClient.RawViewWalker.GetFirstChildElement(wrappingLabelP)
+ return uiaClient.CompareElements(wrappedInput.CurrentLabeledBy, wrappingLabelLeaf)
+ `);
+ ok(result, "wrappedInput has correct LabeledBy");
+ // button has aria-labelledby, but UIA prohibits LabeledBy on buttons.
+ ok(
+ !(await runPython(
+ `bool(findUiaByDomId(doc, "button").CurrentLabeledBy)`
+ )),
+ "button has no LabeledBy"
+ );
+ ok(
+ !(await runPython(
+ `bool(findUiaByDomId(doc, "noLabel").CurrentLabeledBy)`
+ )),
+ "noLabel has no LabeledBy"
+ );
+ },
+ // The IA2 -> UIA proxy doesn't expose LabeledBy properly.
+ { uiaEnabled: true, uiaDisabled: false }
+);
diff --git a/accessible/tests/browser/windows/uia/browser_selectionPatterns.js b/accessible/tests/browser/windows/uia/browser_selectionPatterns.js
new file mode 100644
index 0000000000..a1f70b886a
--- /dev/null
+++ b/accessible/tests/browser/windows/uia/browser_selectionPatterns.js
@@ -0,0 +1,226 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const SNIPPET = `
+<select id="selectList" size="2">
+ <option id="sl1" selected>sl1</option>
+ <option id="sl2">sl2</option>
+</select>
+<select id="selectRequired" size="2" required>
+ <option id="sr1">sr1</option>
+</select>
+<select id="selectMulti" size="2" multiple>
+ <option id="sm1" selected>sm1</option>
+ <option id="sm2">sm2</option>
+ <option id="sm3" selected>sm3</option>
+ <option id="sm4">sm4</option>
+ <option id="sm5">sm5</option>
+ <option id="sm6">sm6</option>
+</select>
+<select id="selectCombo" size="1">
+ <option>sc1</option>
+</select>
+<div id="ariaListbox" role="listbox">
+ <div id="al1" role="option" aria-selected="true">al1</div>
+ <div id="al2" role="option">al2</div>
+</div>
+<div id="tablist" role="tablist">
+ <div id="t1" role="tab">t1</div>
+ <div id="t2" role="tab" aria-selected="true">t2</div>
+</div>
+<table id="grid" role="grid" aria-multiselectable="true">
+ <tr>
+ <td id="g1">g1</td>
+ <td id="g2" role="gridcell" aria-selected="true">g2</td>
+ </tr>
+ <tr>
+ <td id="g3">g3</td>
+ <td id="g4" role="gridcell" aria-selected="true">g4</td>
+ </tr>
+</table>
+<div id="radiogroup" role="radiogroup">
+ <label><input id="r1" type="radio" name="r" checked>r1</label>
+ <label><input id="r2" type="radio" name="r">r2</label>
+</div>
+<div id="menu" role="menu">
+ <div id="m1" role="menuitem">m1</div>
+ <div id="m2" role="menuitemradio">m2</div>
+ <div id="m3" role="menuitemradio" aria-checked="true">m3</div>
+</div>
+<button id="button">button</button>
+`;
+
+async function testSelectionProps(id, selection, multiple, required) {
+ await assignPyVarToUiaWithId(id);
+ await definePyVar("pattern", `getUiaPattern(${id}, "Selection")`);
+ ok(await runPython(`bool(pattern)`), `${id} has Selection pattern`);
+ await isUiaElementArray(
+ `pattern.GetCurrentSelection()`,
+ selection,
+ `${id} has correct Selection`
+ );
+ is(
+ !!(await runPython(`pattern.CurrentCanSelectMultiple`)),
+ multiple,
+ `${id} has correct CanSelectMultiple`
+ );
+ // The IA2 -> UIA proxy doesn't reflect the required state correctly.
+ if (gIsUiaEnabled) {
+ is(
+ !!(await runPython(`pattern.CurrentIsSelectionRequired`)),
+ required,
+ `${id} has correct IsSelectionRequired`
+ );
+ }
+}
+
+async function testSelectionItemProps(id, selected, container) {
+ await assignPyVarToUiaWithId(id);
+ await definePyVar("pattern", `getUiaPattern(${id}, "SelectionItem")`);
+ ok(await runPython(`bool(pattern)`), `${id} has SelectionItem pattern`);
+ is(
+ !!(await runPython(`pattern.CurrentIsSelected`)),
+ selected,
+ `${id} has correct IsSelected`
+ );
+ if (container) {
+ is(
+ await runPython(`pattern.CurrentSelectionContainer.CurrentAutomationId`),
+ container,
+ `${id} has correct SelectionContainer`
+ );
+ } else {
+ ok(
+ !(await runPython(`bool(pattern.CurrentSelectionContainer)`)),
+ `${id} has no SelectionContainer`
+ );
+ }
+}
+
+/**
+ * Test the Selection pattern.
+ */
+addUiaTask(SNIPPET, async function testSelection(browser) {
+ await definePyVar("doc", `getDocUia()`);
+ await testSelectionProps("selectList", ["sl1"], false, false);
+ await testSelectionProps("selectRequired", [], false, true);
+
+ await testSelectionProps("selectMulti", ["sm1", "sm3"], true, false);
+ // The Selection pattern only has an event for a complete invalidation of the
+ // container's selection, which only happens when there are too many selection
+ // events. Smaller selection changes fire events in the SelectionItem pattern.
+ info("Changing selectMulti selection");
+ await setUpWaitForUiaEvent("Selection_Invalidated", "selectMulti");
+ await invokeContentTask(browser, [], () => {
+ const multi = content.document.getElementById("selectMulti");
+ multi[0].selected = false;
+ multi[1].selected = true;
+ multi[2].selected = false;
+ multi[3].selected = true;
+ multi[4].selected = true;
+ multi[5].selected = true;
+ });
+ await waitForUiaEvent();
+ ok(true, "select got Invalidated event");
+ await testSelectionProps(
+ "selectMulti",
+ ["sm2", "sm4", "sm5", "sm6"],
+ true,
+ false
+ );
+
+ await testPatternAbsent("selectCombo", "Selection");
+
+ await testSelectionProps("ariaListbox", ["al1"], false, false);
+ await testSelectionProps("tablist", ["t2"], false, false);
+ // The IA2 -> UIA proxy doesn't expose the Selection pattern on grids.
+ if (gIsUiaEnabled) {
+ await testSelectionProps("grid", ["g2", "g4"], true, false);
+ }
+
+ // radio gets the SelectionItem pattern, but radiogroup doesn't get the
+ // Selection pattern for now. Same for menu/menuitemradio.
+ await testPatternAbsent("radiogroup", "Selection");
+ await testPatternAbsent("menu", "Selection");
+
+ await testPatternAbsent("button", "Selection");
+});
+
+/**
+ * Test the SelectionItem pattern.
+ */
+addUiaTask(SNIPPET, async function testSelection() {
+ await definePyVar("doc", `getDocUia()`);
+ await testPatternAbsent("selectList", "SelectionItem");
+ await testSelectionItemProps("sl1", true, "selectList");
+ await testSelectionItemProps("sl2", false, "selectList");
+ info("Calling Select on sl2");
+ await setUpWaitForUiaEvent("SelectionItem_ElementSelected", "sl2");
+ await runPython(`pattern.Select()`);
+ await waitForUiaEvent();
+ ok(true, "sl2 got ElementSelected event");
+ await testSelectionItemProps("sl1", false, "selectList");
+ await testSelectionItemProps("sl2", true, "selectList");
+
+ await testSelectionItemProps("sr1", false, "selectRequired");
+
+ await testSelectionItemProps("sm1", true, "selectMulti");
+ await testSelectionItemProps("sm2", false, "selectMulti");
+ info("Calling AddToSelection on sm2");
+ await setUpWaitForUiaEvent("SelectionItem_ElementAddedToSelection", "sm2");
+ await runPython(`pattern.AddToSelection()`);
+ await waitForUiaEvent();
+ ok(true, "sm2 got ElementAddedToSelection event");
+ await testSelectionItemProps("sm2", true, "selectMulti");
+ await testSelectionItemProps("sm3", true, "selectMulti");
+ info("Calling RemoveFromSelection on sm3");
+ await setUpWaitForUiaEvent(
+ "SelectionItem_ElementRemovedFromSelection",
+ "sm3"
+ );
+ await runPython(`pattern.RemoveFromSelection()`);
+ await waitForUiaEvent();
+ ok(true, "sm3 got ElementRemovedFromSelection event");
+ await testSelectionItemProps("sm3", false, "selectMulti");
+
+ await testSelectionItemProps("t1", false, "tablist");
+ await testSelectionItemProps("t2", true, "tablist");
+
+ // The IA2 -> UIA proxy doesn't expose the SelectionItem pattern on grid
+ // cells.
+ if (gIsUiaEnabled) {
+ await testSelectionItemProps("g1", false, "grid");
+ await testSelectionItemProps("g2", true, "grid");
+ }
+
+ await testSelectionItemProps("r1", true, null);
+ await testSelectionItemProps("r2", false, null);
+ // The IA2 -> UIA proxy doesn't fire correct events for radio buttons.
+ if (gIsUiaEnabled) {
+ info("Calling Select on r2");
+ await setUpWaitForUiaEvent("SelectionItem_ElementSelected", "r2");
+ await runPython(`pattern.Select()`);
+ await waitForUiaEvent();
+ ok(true, "r2 got ElementSelected event");
+ await testSelectionItemProps("r1", false, null);
+ await testSelectionItemProps("r2", true, null);
+ info("Calling RemoveFromSelection on r2");
+ await testPythonRaises(
+ `pattern.RemoveFromSelection()`,
+ "RemoveFromSelection failed on r2"
+ );
+ }
+
+ await testPatternAbsent("m1", "SelectionItem");
+ // The IA2 -> UIA proxy doesn't expose the SelectionItem pattern for radio
+ // menu items.
+ if (gIsUiaEnabled) {
+ await testSelectionItemProps("m2", false, null);
+ await testSelectionItemProps("m3", true, null);
+ }
+
+ await testPatternAbsent("button", "SelectionItem");
+});
diff --git a/accessible/tests/browser/windows/uia/browser_simplePatterns.js b/accessible/tests/browser/windows/uia/browser_simplePatterns.js
index f464db0e13..484d217af2 100644
--- a/accessible/tests/browser/windows/uia/browser_simplePatterns.js
+++ b/accessible/tests/browser/windows/uia/browser_simplePatterns.js
@@ -27,6 +27,7 @@ addUiaTask(
<button id="button">button</button>
<p id="p">p</p>
<input id="checkbox" type="checkbox">
+<input id="radio" type="radio">
`,
async function testInvoke() {
await definePyVar("doc", `getDocUia()`);
@@ -54,6 +55,8 @@ addUiaTask(
// Check boxes expose the Toggle pattern, so they should not expose the
// Invoke pattern.
await testPatternAbsent("checkbox", "Invoke");
+ // Ditto for radio buttons.
+ await testPatternAbsent("radio", "Invoke");
}
}
);
@@ -317,6 +320,7 @@ addUiaTask(
await setUpWaitForUiaPropEvent("ValueValue", "text");
await runPython(`pattern.SetValue("after")`);
await waitForUiaEvent();
+ ok(true, "Got ValueValue prop change event on text");
is(
await runPython(`pattern.CurrentValue`),
"after",
@@ -434,6 +438,7 @@ addUiaTask(
await setUpWaitForUiaPropEvent("ValueValue", "ariaTextbox");
await runPython(`pattern.SetValue("after")`);
await waitForUiaEvent();
+ ok(true, "Got ValueValue prop change event on ariaTextbox");
is(
await runPython(`pattern.CurrentValue`),
"after",
@@ -443,3 +448,95 @@ addUiaTask(
await testPatternAbsent("button", "Value");
}
);
+
+async function testRangeValueProps(id, ro, val, min, max, small, large) {
+ await assignPyVarToUiaWithId(id);
+ await definePyVar("pattern", `getUiaPattern(${id}, "RangeValue")`);
+ ok(await runPython(`bool(pattern)`), `${id} has RangeValue pattern`);
+ is(
+ !!(await runPython(`pattern.CurrentIsReadOnly`)),
+ ro,
+ `${id} has IsReadOnly ${ro}`
+ );
+ is(await runPython(`pattern.CurrentValue`), val, `${id} has correct Value`);
+ is(
+ await runPython(`pattern.CurrentMinimum`),
+ min,
+ `${id} has correct Minimum`
+ );
+ is(
+ await runPython(`pattern.CurrentMaximum`),
+ max,
+ `${id} has correct Maximum`
+ );
+ // IA2 doesn't support small/large change, so the IA2 -> UIA proxy can't
+ // either.
+ if (gIsUiaEnabled) {
+ is(
+ await runPython(`pattern.CurrentSmallChange`),
+ small,
+ `${id} has correct SmallChange`
+ );
+ is(
+ await runPython(`pattern.CurrentLargeChange`),
+ large,
+ `${id} has correct LargeChange`
+ );
+ }
+}
+
+/**
+ * Test the RangeValue pattern.
+ */
+addUiaTask(
+ `
+<input id="range" type="range">
+<input id="rangeBig" type="range" max="1000">
+<progress id="progress" value="0.5"></progress>
+<input id="numberRo" type="number" min="0" max="10" value="5" readonly>
+<div id="ariaSlider" role="slider">slider</div>
+<button id="button">button</button>
+ `,
+ async function testRangeValue(browser) {
+ await definePyVar("doc", `getDocUia()`);
+ await testRangeValueProps("range", false, 50, 0, 100, 1, 10);
+ info("SetValue on range");
+ await setUpWaitForUiaPropEvent("RangeValueValue", "range");
+ await runPython(`pattern.SetValue(20)`);
+ await waitForUiaEvent();
+ ok(true, "Got RangeValueValue prop change event on range");
+ is(await runPython(`pattern.CurrentValue`), 20, "range has correct Value");
+
+ await testRangeValueProps("rangeBig", false, 500, 0, 1000, 1, 100);
+
+ // Gecko a11y doesn't expose progress bars as read only, but it probably
+ // should.
+ await testRangeValueProps("progress", false, 0.5, 0, 1, 0, 0.1);
+ info("Calling SetValue on progress");
+ await testPythonRaises(
+ `pattern.SetValue(0.6)`,
+ "SetValue on progress failed"
+ );
+
+ await testRangeValueProps("numberRo", true, 5, 0, 10, 1, 1);
+ info("Calling SetValue on numberRo");
+ await testPythonRaises(
+ `pattern.SetValue(6)`,
+ "SetValue on numberRo failed"
+ );
+
+ await testRangeValueProps("ariaSlider", false, 50, 0, 100, null, null);
+ info("Setting aria-valuenow on ariaSlider");
+ await setUpWaitForUiaPropEvent("RangeValueValue", "ariaSlider");
+ await invokeSetAttribute(browser, "ariaSlider", "aria-valuenow", "60");
+ await waitForUiaEvent();
+ ok(true, "Got RangeValueValue prop change event on ariaSlider");
+ is(
+ await runPython(`pattern.CurrentValue`),
+ 60,
+ "ariaSlider has correct Value"
+ );
+
+ await testPatternAbsent("button", "RangeValue");
+ }
+);
diff --git a/accessible/tests/browser/windows/uia/head.js b/accessible/tests/browser/windows/uia/head.js
index 5b453ce6fe..217b8cb844 100644
--- a/accessible/tests/browser/windows/uia/head.js
+++ b/accessible/tests/browser/windows/uia/head.js
@@ -4,7 +4,7 @@
"use strict";
-/* exported gIsUiaEnabled, addUiaTask, definePyVar, assignPyVarToUiaWithId, setUpWaitForUiaEvent, setUpWaitForUiaPropEvent, waitForUiaEvent, testPatternAbsent, testPythonRaises */
+/* exported gIsUiaEnabled, addUiaTask, definePyVar, assignPyVarToUiaWithId, setUpWaitForUiaEvent, setUpWaitForUiaPropEvent, waitForUiaEvent, testPatternAbsent, testPythonRaises, isUiaElementArray */
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
@@ -126,3 +126,15 @@ async function testPythonRaises(expression, message) {
}
ok(failed, message);
}
+
+/**
+ * Verify that an array of UIA elements contains (only) elements with the given
+ * DOM ids.
+ */
+async function isUiaElementArray(pyExpr, ids, message) {
+ const result = await runPython(`
+ uias = (${pyExpr})
+ return [uias.GetElement(i).CurrentAutomationId for i in range(uias.Length)]
+ `);
+ SimpleTest.isDeeply(result, ids, message);
+}