/* 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/. */
/**
* Test tables for both local and remote Accessibles. There is more extensive
* coverage in ../../mochitest/table. These tests are primarily to ensure that
* the cache works as expected and that there is consistency between local and
* remote.
*/
"use strict";
/* import-globals-from ../../mochitest/table.js */
/* import-globals-from ../../mochitest/attributes.js */
loadScripts(
{ name: "table.js", dir: MOCHITESTS_DIR },
{ name: "attributes.js", dir: MOCHITESTS_DIR }
);
/**
* Test table counts, indexes, extents and implicit headers.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const table = findAccessibleChildByID(docAcc, "table", [
nsIAccessibleTable,
]);
is(table.rowCount, 3, "table rowCount correct");
is(table.columnCount, 4, "table columnCount correct");
testTableIndexes(table, [
[0, 1, 1, 2],
[3, 4, 5, 6],
[3, 4, 7, -1],
]);
const cells = {};
for (const id of ["a", "bc", "d", "ei", "fj", "g", "h", "k"]) {
cells[id] = findAccessibleChildByID(docAcc, id, [nsIAccessibleTableCell]);
}
is(cells.a.rowExtent, 1, "a rowExtent correct");
is(cells.a.columnExtent, 1, "a columnExtent correct");
is(cells.bc.rowExtent, 1, "bc rowExtent correct");
is(cells.bc.columnExtent, 2, "bc columnExtent correct");
is(cells.ei.rowExtent, 2, "ei rowExtent correct");
is(cells.fj.rowExtent, 2, "fj rowExtent correct");
testHeaderCells([
{
cell: cells.ei,
rowHeaderCells: [],
columnHeaderCells: [cells.a],
},
{
cell: cells.g,
rowHeaderCells: [cells.ei],
columnHeaderCells: [cells.bc],
},
{
cell: cells.k,
rowHeaderCells: [cells.ei],
columnHeaderCells: [cells.bc],
},
]);
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test table explicit headers.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const cells = {};
for (const id of ["a", "b", "c", "d", "e", "f"]) {
cells[id] = findAccessibleChildByID(docAcc, id, [nsIAccessibleTableCell]);
}
testHeaderCells([
{
cell: cells.c,
rowHeaderCells: [cells.d],
columnHeaderCells: [cells.b],
},
{
cell: cells.e,
rowHeaderCells: [cells.f],
columnHeaderCells: [cells.c],
},
]);
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test that an inner table doesn't impact an outer table.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const outerTable = findAccessibleChildByID(docAcc, "outerTable", [
nsIAccessibleTable,
]);
is(outerTable.rowCount, 1, "outerTable rowCount correct");
is(outerTable.columnCount, 1, "outerTable columnCount correct");
const outerCell = findAccessibleChildByID(docAcc, "outerCell");
is(
outerTable.getCellAt(0, 0),
outerCell,
"outerTable returns correct cell"
);
const innerTable = findAccessibleChildByID(docAcc, "innerTable", [
nsIAccessibleTable,
]);
is(innerTable.rowCount, 1, "innerTable rowCount correct");
is(innerTable.columnCount, 1, "innerTable columnCount correct");
const innerCell = findAccessibleChildByID(docAcc, "innerCell");
is(
innerTable.getCellAt(0, 0),
innerCell,
"innerTable returns correct cell"
);
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test table caption and summary.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const t1 = findAccessibleChildByID(docAcc, "t1", [nsIAccessibleTable]);
const c1 = findAccessibleChildByID(docAcc, "c1");
is(t1.caption, c1, "t1 caption correct");
ok(!t1.summary, "t1 no summary");
const t2 = findAccessibleChildByID(docAcc, "t2", [nsIAccessibleTable]);
ok(!t2.caption, "t2 caption is null");
is(t2.summary, "s2", "t2 summary correct");
const t3 = findAccessibleChildByID(docAcc, "t3", [nsIAccessibleTable]);
const c3 = findAccessibleChildByID(docAcc, "c3");
is(t3.caption, c3, "t3 caption correct");
is(t3.summary, "s3", "t3 summary correct");
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test table layout guess.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const layout = findAccessibleChildByID(docAcc, "layout");
testAttrs(layout, { "layout-guess": "true" }, true);
const data = findAccessibleChildByID(docAcc, "data");
testAbsentAttrs(data, { "layout-guess": "true" });
const mutate = findAccessibleChildByID(docAcc, "mutate");
testAttrs(mutate, { "layout-guess": "true" }, true);
info("mutate: Adding 5 rows");
let reordered = waitForEvent(EVENT_REORDER, mutate);
await invokeContentTask(browser, [], () => {
const frag = content.document.createDocumentFragment();
for (let r = 0; r < 6; ++r) {
const tr = content.document.createElement("tr");
tr.innerHTML = "a | b | ";
frag.append(tr);
}
content.document.getElementById("mutate").tBodies[0].append(frag);
});
await reordered;
testAbsentAttrs(mutate, { "layout-guess": "true" });
info("mutate: Removing 5 rows");
reordered = waitForEvent(EVENT_REORDER, mutate);
await invokeContentTask(browser, [], () => {
// Pause refresh driver so all the children removals below will
// be collated into the same tick and only one 'reorder' event will
// be dispatched.
content.windowUtils.advanceTimeAndRefresh(100);
let tBody = content.document.getElementById("mutate").tBodies[0];
for (let r = 0; r < 6; ++r) {
tBody.lastChild.remove();
}
// Resume refresh driver
content.windowUtils.restoreNormalRefresh();
});
await reordered;
testAttrs(mutate, { "layout-guess": "true" }, true);
info("mutate: Adding new table");
let shown = waitForEvent(EVENT_SHOW, "newTable");
await invokeContentTask(browser, [], () => {
content.document.getElementById(
"newTableContainer"
).innerHTML = ``;
});
let newTable = (await shown).accessible;
testAbsentAttrs(newTable, { "layout-guess": "true" });
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test table layout guess with border styling changes.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const layout = findAccessibleChildByID(docAcc, "layout");
testAttrs(layout, { "layout-guess": "true" }, true);
info("changing border style on table cell");
await invokeContentTask(browser, [], () => {
content.document.getElementById("cell").style.border = "1px solid black";
content.document.body.offsetTop; // Flush layout.
});
await untilCacheOk(() => {
// manually verify the attribute doesn't exist, since `testAbsentAttrs`
// has internal calls to ok() which fail if the cache hasn't yet updated
for (let prop of layout.attributes.enumerate()) {
if (prop.key == "layout-guess") {
return false;
}
}
return true;
}, "Table is a data table");
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test ARIA grid.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const grid = findAccessibleChildByID(docAcc, "grid", [nsIAccessibleTable]);
is(grid.rowCount, 2, "grid rowCount correct");
is(grid.columnCount, 2, "grid columnCount correct");
testTableIndexes(grid, [
[0, 1],
[2, 3],
]);
const cells = {};
for (const id of ["a", "b", "c", "d"]) {
cells[id] = findAccessibleChildByID(docAcc, id, [nsIAccessibleTableCell]);
}
is(cells.a.rowExtent, 1, "a rowExtent correct");
is(cells.a.columnExtent, 1, "a columnExtent correct");
testHeaderCells([
{
cell: cells.c,
rowHeaderCells: [],
columnHeaderCells: [cells.a],
},
{
cell: cells.d,
rowHeaderCells: [cells.c],
columnHeaderCells: [cells.b],
},
]);
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
function setNodeHidden(browser, id, hidden) {
return invokeContentTask(browser, [id, hidden], (cId, cHidden) => {
content.document.getElementById(cId).hidden = cHidden;
});
}
/**
* Test that the table is updated correctly when it is mutated.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const table = findAccessibleChildByID(docAcc, "table", [
nsIAccessibleTable,
]);
is(table.rowCount, 1, "table rowCount correct");
is(table.columnCount, 2, "table columnCount correct");
testTableIndexes(table, [[0, 1]]);
info("Showing r2");
let reordered = waitForEvent(EVENT_REORDER, table);
await setNodeHidden(browser, "r2", false);
await reordered;
is(table.rowCount, 2, "table rowCount correct");
testTableIndexes(table, [
[0, 1],
[2, 3],
]);
info("Hiding r2");
reordered = waitForEvent(EVENT_REORDER, table);
await setNodeHidden(browser, "r2", true);
await reordered;
is(table.rowCount, 1, "table rowCount correct");
testTableIndexes(table, [[0, 1]]);
info("Hiding b");
reordered = waitForEvent(EVENT_REORDER, "r1");
await setNodeHidden(browser, "b", true);
await reordered;
is(table.columnCount, 1, "table columnCount correct");
testTableIndexes(table, [[0]]);
info("Showing b");
reordered = waitForEvent(EVENT_REORDER, "r1");
await setNodeHidden(browser, "b", false);
await reordered;
is(table.columnCount, 2, "table columnCount correct");
info("Moving b out of table using aria-owns");
reordered = waitForEvent(EVENT_REORDER, "r1");
await invokeContentTask(browser, [], () => {
content.document.getElementById("owner").setAttribute("aria-owns", "b");
});
await reordered;
is(table.columnCount, 1, "table columnCount correct");
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test the handling of ARIA tables with display: contents.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const table = findAccessibleChildByID(docAcc, "table", [
nsIAccessibleTable,
]);
is(table.rowCount, 1, "table rowCount correct");
is(table.columnCount, 1, "table columnCount correct");
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test a broken ARIA table with an invalid cell.
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const table = findAccessibleChildByID(docAcc, "table", [
nsIAccessibleTable,
]);
is(table.rowCount, 0, "table rowCount correct");
is(table.columnCount, 0, "table columnCount correct");
const cell = findAccessibleChildByID(docAcc, "cell");
let queryOk = false;
try {
cell.QueryInterface(nsIAccessibleTableCell);
queryOk = true;
} catch (e) {}
ok(!queryOk, "Got nsIAccessibleTableCell on an invalid cell");
},
{
chrome: true,
topLevel: true,
iframe: true,
remoteIframe: true,
}
);
/**
* Test that building the cache for a malformed table with an iframe inside a
* row doesn't crash (bug 1800780).
*/
addAccessibleTask(
``,
async function (browser, docAcc) {
let reordered = waitForEvent(EVENT_REORDER, "tr");
await invokeContentTask(browser, [], () => {
const iframe = content.document.createElement("iframe");
content.document.getElementById("tr").append(iframe);
});
await reordered;
},
{ topLevel: true }
);
/**
* Verify that we don't crash for authoring error like .
*/
addAccessibleTask(
``,
async function (browser, docAcc) {
const table = findAccessibleChildByID(docAcc, "table");
ok(table, "Retrieved table Accessible");
},
{ chrome: true, topLevel: true }
);
/**
* Verify that we don't crash for authoring error like .
*/
addAccessibleTask(
`
`,
async function (browser, docAcc) {
const table = findAccessibleChildByID(docAcc, "table", [
nsIAccessibleTable,
]);
is(table.rowCount, 1, "table rowCount correct");
is(table.columnCount, 1, "table columnCount correct");
const b = findAccessibleChildByID(docAcc, "b");
let queryOk = false;
try {
b.QueryInterface(nsIAccessibleTableCell);
queryOk = true;
} catch (e) {}
ok(!queryOk, "No nsIAccessibleTableCell on invalid cell b");
}
);