diff options
Diffstat (limited to '')
-rw-r--r-- | accessible/tests/browser/mac/browser_table.js | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/accessible/tests/browser/mac/browser_table.js b/accessible/tests/browser/mac/browser_table.js new file mode 100644 index 0000000000..50ae697deb --- /dev/null +++ b/accessible/tests/browser/mac/browser_table.js @@ -0,0 +1,629 @@ +/* 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"; + +/* import-globals-from ../../mochitest/role.js */ +loadScripts({ name: "role.js", dir: MOCHITESTS_DIR }); + +/* import-globals-from ../../mochitest/attributes.js */ +loadScripts({ name: "attributes.js", dir: MOCHITESTS_DIR }); + +/** + * Helper function to test table consistency. + */ +function testTableConsistency(table, expectedRowCount, expectedColumnCount) { + is(table.getAttributeValue("AXRole"), "AXTable", "Correct role for table"); + + let tableChildren = table.getAttributeValue("AXChildren"); + // XXX: Should be expectedRowCount+ExpectedColumnCount+1 children, rows (incl headers) + cols + headers + // if we're trying to match Safari. + is( + tableChildren.length, + expectedRowCount + expectedColumnCount, + "Table has children = rows (4) + cols (3)" + ); + for (let i = 0; i < tableChildren.length; i++) { + let currChild = tableChildren[i]; + if (i < expectedRowCount) { + is( + currChild.getAttributeValue("AXRole"), + "AXRow", + "Correct role for row" + ); + } else { + is( + currChild.getAttributeValue("AXRole"), + "AXColumn", + "Correct role for col" + ); + is( + currChild.getAttributeValue("AXRoleDescription"), + "column", + "Correct role desc for col" + ); + } + } + + is( + table.getAttributeValue("AXColumnCount"), + expectedColumnCount, + "Table has correct column count." + ); + is( + table.getAttributeValue("AXRowCount"), + expectedRowCount, + "Table has correct row count." + ); + + let cols = table.getAttributeValue("AXColumns"); + is(cols.length, expectedColumnCount, "Table has col list of correct length"); + for (let i = 0; i < cols.length; i++) { + let currCol = cols[i]; + let currChildren = currCol.getAttributeValue("AXChildren"); + is( + currChildren.length, + expectedRowCount, + "Column has correct number of cells" + ); + for (let j = 0; j < currChildren.length; j++) { + let currChild = currChildren[j]; + is( + currChild.getAttributeValue("AXRole"), + "AXCell", + "Column child is cell" + ); + } + } + + let rows = table.getAttributeValue("AXRows"); + is(rows.length, expectedRowCount, "Table has row list of correct length"); + for (let i = 0; i < rows.length; i++) { + let currRow = rows[i]; + let currChildren = currRow.getAttributeValue("AXChildren"); + is( + currChildren.length, + expectedColumnCount, + "Row has correct number of cells" + ); + for (let j = 0; j < currChildren.length; j++) { + let currChild = currChildren[j]; + is(currChild.getAttributeValue("AXRole"), "AXCell", "Row child is cell"); + } + } +} + +/** + * Test table, columns, rows + */ +addAccessibleTask( + `<table id="customers"> + <tbody> + <tr id="firstrow"><th>Company</th><th>Contact</th><th>Country</th></tr> + <tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr> + <tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr> + <tr><td>Ernst Handel</td><td>Roland Mendel</td><td>Austria</td></tr> + </tbody> + </table>`, + async (browser, accDoc) => { + let table = getNativeInterface(accDoc, "customers"); + testTableConsistency(table, 4, 3); + + const rowText = [ + "Madrigal Electromotive GmbH", + "Lydia Rodarte-Quayle", + "Germany", + ]; + let reorder = waitForEvent(EVENT_REORDER, "customers"); + await SpecialPowers.spawn(browser, [rowText], _rowText => { + let tr = content.document.createElement("tr"); + for (let t of _rowText) { + let td = content.document.createElement("td"); + td.textContent = t; + tr.appendChild(td); + } + content.document.getElementById("customers").appendChild(tr); + }); + await reorder; + + let cols = table.getAttributeValue("AXColumns"); + is(cols.length, 3, "Table has col list of correct length"); + for (let i = 0; i < cols.length; i++) { + let currCol = cols[i]; + let currChildren = currCol.getAttributeValue("AXChildren"); + is(currChildren.length, 5, "Column has correct number of cells"); + let lastCell = currChildren[currChildren.length - 1]; + let cellChildren = lastCell.getAttributeValue("AXChildren"); + is(cellChildren.length, 1, "Cell has a single text child"); + is( + cellChildren[0].getAttributeValue("AXRole"), + "AXStaticText", + "Correct role for cell child" + ); + is( + cellChildren[0].getAttributeValue("AXValue"), + rowText[i], + "Correct text for cell" + ); + } + + reorder = waitForEvent(EVENT_REORDER, "firstrow"); + await SpecialPowers.spawn(browser, [], () => { + let td = content.document.createElement("td"); + td.textContent = "Ticker"; + content.document.getElementById("firstrow").appendChild(td); + }); + await reorder; + + cols = table.getAttributeValue("AXColumns"); + is(cols.length, 4, "Table has col list of correct length"); + is( + cols[cols.length - 1].getAttributeValue("AXChildren").length, + 1, + "Last column has single child" + ); + + reorder = waitForEvent( + EVENT_REORDER, + e => e.accessible.role == ROLE_DOCUMENT + ); + await SpecialPowers.spawn(browser, [], () => { + content.document.getElementById("customers").remove(); + }); + await reorder; + + try { + cols[0].getAttributeValue("AXChildren"); + ok(false, "Getting children from column of expired table should fail"); + } catch (e) { + ok(true, "Getting children from column of expired table should fail"); + } + } +); + +addAccessibleTask( + `<table id="table"> + <tr> + <th colspan="2" id="header1">Header 1</th> + <th id="header2">Header 2</th> + </tr> + <tr> + <td id="cell1">one</td> + <td id="cell2" rowspan="2">two</td> + <td id="cell3">three</td> + </tr> + <tr> + <td id="cell4">four</td> + <td id="cell5">five</td> + </tr> + </table>`, + (browser, accDoc) => { + let table = getNativeInterface(accDoc, "table"); + + let getCellAt = (col, row) => + table.getParameterizedAttributeValue("AXCellForColumnAndRow", [col, row]); + + function testCell(cell, expectedId, expectedColRange, expectedRowRange) { + is( + cell.getAttributeValue("AXDOMIdentifier"), + expectedId, + "Correct DOM Identifier" + ); + Assert.deepEqual( + cell.getAttributeValue("AXColumnIndexRange"), + expectedColRange, + "Correct column range" + ); + Assert.deepEqual( + cell.getAttributeValue("AXRowIndexRange"), + expectedRowRange, + "Correct row range" + ); + } + + testCell(getCellAt(0, 0), "header1", [0, 2], [0, 1]); + testCell(getCellAt(1, 0), "header1", [0, 2], [0, 1]); + testCell(getCellAt(2, 0), "header2", [2, 1], [0, 1]); + + testCell(getCellAt(0, 1), "cell1", [0, 1], [1, 1]); + testCell(getCellAt(1, 1), "cell2", [1, 1], [1, 2]); + testCell(getCellAt(2, 1), "cell3", [2, 1], [1, 1]); + + testCell(getCellAt(0, 2), "cell4", [0, 1], [2, 1]); + testCell(getCellAt(1, 2), "cell2", [1, 1], [1, 2]); + testCell(getCellAt(2, 2), "cell5", [2, 1], [2, 1]); + + let colHeaders = table.getAttributeValue("AXColumnHeaderUIElements"); + Assert.deepEqual( + colHeaders.map(c => c.getAttributeValue("AXDOMIdentifier")), + ["header1", "header1", "header2"], + "Correct column headers" + ); + } +); + +addAccessibleTask( + `<table id="table"> + <tr> + <td>Foo</td> + </tr> + </table>`, + (browser, accDoc) => { + // Make sure we guess this table to be a layout table. + testAttrs( + findAccessibleChildByID(accDoc, "table"), + { "layout-guess": "true" }, + true + ); + + let table = getNativeInterface(accDoc, "table"); + is( + table.getAttributeValue("AXRole"), + "AXGroup", + "Correct role (AXGroup) for layout table" + ); + + let children = table.getAttributeValue("AXChildren"); + is( + children.length, + 1, + "Layout table has single child (no additional columns)" + ); + } +); + +addAccessibleTask( + `<div id="table" role="table"> + <span style="display: block;"> + <div role="row"> + <div role="cell">Cell 1</div> + <div role="cell">Cell 2</div> + </div> + </span> + <span style="display: block;"> + <div role="row"> + <span style="display: block;"> + <div role="cell">Cell 3</div> + <div role="cell">Cell 4</div> + </span> + </div> + </span> + </div>`, + async (browser, accDoc) => { + let table = getNativeInterface(accDoc, "table"); + testTableConsistency(table, 2, 2); + } +); + +/* + * After executing function 'change' which operates on 'elem', verify the specified + * 'event' (if not null) is fired on elem. After the event, check if the given + * native accessible 'table' is a layout or data table by role using 'isLayout'. + */ +async function testIsLayout(table, elem, event, change, isLayout) { + info( + "Changing " + + elem + + ", expecting table change to " + + (isLayout ? "AXGroup" : "AXTable") + ); + const toWait = event ? waitForEvent(event, elem) : null; + await change(); + if (toWait) { + await toWait; + } + let intendedRole = isLayout ? "AXGroup" : "AXTable"; + await untilCacheIs( + () => table.getAttributeValue("AXRole"), + intendedRole, + "Table role correct after change" + ); +} + +/* + * The following attributes should fire an attribute changed + * event, which in turn invalidates the layout-table cache + * associated with the given table. After adding and removing + * each attr, verify the table is a data or layout table, + * appropriately. Attrs: summary, abbr, scope, headers + */ +addAccessibleTask( + `<table id="table" summary="example summary"> + <tr role="presentation"> + <td id="cellOne">cell1</td> + <td>cell2</td> + </tr> + <tr> + <td id="cellThree">cell3</td> + <td>cell4</td> + </tr> + </table>`, + async (browser, accDoc) => { + let table = getNativeInterface(accDoc, "table"); + // summary attr should take precedence over role="presentation" to make this + // a data table + is(table.getAttributeValue("AXRole"), "AXTable", "Table is data table"); + + info("Removing summary attr"); + // after summary is removed, we should have a layout table + await testIsLayout( + table, + "table", + EVENT_OBJECT_ATTRIBUTE_CHANGED, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document.getElementById("table").removeAttribute("summary"); + }); + }, + true + ); + + info("Setting abbr attr"); + // after abbr is set we should have a data table again + await testIsLayout( + table, + "cellThree", + EVENT_OBJECT_ATTRIBUTE_CHANGED, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document + .getElementById("cellThree") + .setAttribute("abbr", "hello world"); + }); + }, + false + ); + + info("Removing abbr attr"); + // after abbr is removed we should have a layout table again + await testIsLayout( + table, + "cellThree", + EVENT_OBJECT_ATTRIBUTE_CHANGED, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document.getElementById("cellThree").removeAttribute("abbr"); + }); + }, + true + ); + + info("Setting scope attr"); + // after scope is set we should have a data table again + await testIsLayout( + table, + "cellThree", + EVENT_OBJECT_ATTRIBUTE_CHANGED, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document + .getElementById("cellThree") + .setAttribute("scope", "col"); + }); + }, + false + ); + + info("Removing scope attr"); + // remove scope should give layout + await testIsLayout( + table, + "cellThree", + EVENT_OBJECT_ATTRIBUTE_CHANGED, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document.getElementById("cellThree").removeAttribute("scope"); + }); + }, + true + ); + + info("Setting headers attr"); + // add headers attr should give data + await testIsLayout( + table, + "cellThree", + EVENT_OBJECT_ATTRIBUTE_CHANGED, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document + .getElementById("cellThree") + .setAttribute("headers", "cellOne"); + }); + }, + false + ); + + info("Removing headers attr"); + // remove headers attr should give layout + await testIsLayout( + table, + "cellThree", + EVENT_OBJECT_ATTRIBUTE_CHANGED, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document + .getElementById("cellThree") + .removeAttribute("headers"); + }); + }, + true + ); + } +); + +/* + * The following style changes should fire a table style changed + * event, which in turn invalidates the layout-table cache + * associated with the given table. + */ +addAccessibleTask( + `<table id="table"> + <tr id="rowOne"> + <td id="cellOne">cell1</td> + <td>cell2</td> + </tr> + <tr> + <td>cell3</td> + <td>cell4</td> + </tr> + </table>`, + async (browser, accDoc) => { + let table = getNativeInterface(accDoc, "table"); + // we should start as a layout table + is(table.getAttributeValue("AXRole"), "AXGroup", "Table is layout table"); + + info("Adding cell border"); + // after cell border added, we should have a data table + await testIsLayout( + table, + "cellOne", + null, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document + .getElementById("cellOne") + .style.setProperty("border", "5px solid green"); + }); + }, + false + ); + + info("Removing cell border"); + // after cell border removed, we should have a layout table + await testIsLayout( + table, + "cellOne", + null, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document + .getElementById("cellOne") + .style.removeProperty("border"); + }); + }, + true + ); + + info("Adding row background"); + // after row background added, we should have a data table + await testIsLayout( + table, + "rowOne", + null, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document + .getElementById("rowOne") + .style.setProperty("background-color", "green"); + }); + }, + false + ); + + info("Removing row background"); + // after row background removed, we should have a layout table + await testIsLayout( + table, + "rowOne", + null, + async () => { + await SpecialPowers.spawn(browser, [], () => { + content.document + .getElementById("rowOne") + .style.removeProperty("background-color"); + }); + }, + true + ); + } +); + +/* + * thead/tbody elements with click handlers should: + * (a) render as AXGroup elements + * (b) expose their rows as part of their parent table's AXRows array + */ +addAccessibleTask( + `<table id="table"> + <thead id="thead"> + <tr><td>head row</td></tr> + </thead> + <tbody id="tbody"> + <tr><td>body row</td></tr> + <tr><td>another body row</td></tr> + </tbody> + </table>`, + async (browser, accDoc) => { + let table = getNativeInterface(accDoc, "table"); + + // No click handlers present on thead/tbody + let tableChildren = table.getAttributeValue("AXChildren"); + let tableRows = table.getAttributeValue("AXRows"); + + is(tableChildren.length, 4, "Table has four children (3 row + 1 col)"); + is(tableRows.length, 3, "Table has three rows"); + + for (let i = 0; i < tableChildren.length; i++) { + const child = tableChildren[i]; + if (i < 3) { + is( + child.getAttributeValue("AXRole"), + "AXRow", + "Table's first 3 children are rows" + ); + } else { + is( + child.getAttributeValue("AXRole"), + "AXColumn", + "Table's last child is a column" + ); + } + } + const reorder = waitForEvent(EVENT_REORDER); + await invokeContentTask(browser, [], () => { + const head = content.document.getElementById("thead"); + const body = content.document.getElementById("tbody"); + + head.addEventListener("click", function () {}); + body.addEventListener("click", function () {}); + }); + await reorder; + + // Click handlers present + tableChildren = table.getAttributeValue("AXChildren"); + + is(tableChildren.length, 3, "Table has three children (2 groups + 1 col)"); + is( + tableChildren[0].getAttributeValue("AXRole"), + "AXGroup", + "Child one is a group" + ); + is( + tableChildren[0].getAttributeValue("AXChildren").length, + 1, + "Child one has one child" + ); + + is( + tableChildren[1].getAttributeValue("AXRole"), + "AXGroup", + "Child two is a group" + ); + is( + tableChildren[1].getAttributeValue("AXChildren").length, + 2, + "Child two has two children" + ); + + is( + tableChildren[2].getAttributeValue("AXRole"), + "AXColumn", + "Child three is a col" + ); + + tableRows = table.getAttributeValue("AXRows"); + is(tableRows.length, 3, "Table has three rows"); + } +); |