Skip to content

Commit

Permalink
refactor(empty-table-header): use virtual node (#3621)
Browse files Browse the repository at this point in the history
use virtual node

references: #3473
  • Loading branch information
dbowling authored Aug 29, 2022
1 parent 49ccb00 commit b16f553
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 54 deletions.
6 changes: 3 additions & 3 deletions lib/commons/standards/implicit-html-roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ const implicitHtmlRoles = {
case '':
return !suggestionsSourceElement ? 'textbox' : 'combobox';

default:
default:
return 'textbox';
}
},
Expand Down Expand Up @@ -171,10 +171,10 @@ const implicitHtmlRoles = {
textarea: 'textbox',
tfoot: 'rowgroup',
th: vNode => {
if (isColumnHeader(vNode.actualNode)) {
if (isColumnHeader(vNode)) {
return 'columnheader';
}
if (isRowHeader(vNode.actualNode)) {
if (isRowHeader(vNode)) {
return 'rowheader';
}
},
Expand Down
22 changes: 14 additions & 8 deletions lib/commons/table/get-scope.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import toGrid from './to-grid';
import getCellPosition from './get-cell-position';
import findUp from '../dom/find-up';
import { getNodeFromTree } from '../../core/utils';
import AbstractVirtualNode from '../../core/base/virtual-node/abstract-virtual-node';

/**
* Determine if a `HTMLTableCellElement` is a column header, if so get the scope of the header
* @method getScope
* @memberof axe.commons.table
* @instance
* @param {HTMLTableCellElement} cell The table cell to test
* @param {HTMLTableCellElement|AbstractVirtualNode} cell The table cell to test
* @return {Boolean|String} Returns `false` if not a column header, or the scope of the column header element
*/
function getScope(cell) {
var scope = cell.getAttribute('scope');
var role = cell.getAttribute('role');
const vNode =
cell instanceof AbstractVirtualNode ? cell : getNodeFromTree(cell);

if (
cell instanceof window.Element === false ||
['TD', 'TH'].indexOf(cell.nodeName.toUpperCase()) === -1
) {
cell = vNode.actualNode;

const scope = vNode.attr('scope');
const role = vNode.attr('role');

if (!['td', 'th'].includes(vNode.props.nodeName)) {
throw new TypeError('Expected TD or TH element');
}

Expand All @@ -27,8 +31,10 @@ function getScope(cell) {
return 'row';
} else if (scope === 'col' || scope === 'row') {
return scope;
} else if (cell.nodeName.toUpperCase() !== 'TH') {
} else if (vNode.props.nodeName !== 'th') {
return false;
} else if (!vNode.actualNode) {
return 'auto';
}
var tableGrid = toGrid(findUp(cell, 'table'));
var pos = getCellPosition(cell, tableGrid);
Expand Down
88 changes: 53 additions & 35 deletions test/commons/table/get-scope.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,48 @@
describe('table.getScope', function() {
describe('table.getScope', function () {
'use strict';

function $id(id) {
return document.getElementById(id);
}

var fixture = $id('fixture');
var fixtureSetup = axe.testUtils.fixtureSetup;

afterEach(function() {
fixture.innerHTML = '';
axe._tree = undefined;
});

it('returns false for TD without scope attribute', function() {
it('returns false for TD without scope attribute', function () {
fixture.innerHTML =
'<table>' +
'<tr><td></td><td id="target">1</td></tr>' +
'<tr><th>2</th><td>ok</td></tr>' +
'</table>';

var target = $id('target');
fixtureSetup();
assert.equal(axe.commons.table.getScope(target), false);
});

it('throws if it is not passed a data cell', function() {
assert.throws(function() {
it('throws if it is not passed a data cell', function () {
assert.throws(function () {
axe.commons.table.getScope();
});
}, TypeError);

assert.throws(function() {
axe.commons.table.getScope(document.createElement('tr'));
});
var node = document.createElement('tr');
axe.utils.getFlattenedTree(node);

assert.doesNotThrow(function() {
axe.commons.table.getScope(document.createElement('td'));
assert.throws(function () {
axe.commons.table.getScope(node);
}, TypeError);
});

it('does not throw if it is passed a data cell', function () {
var node = document.createElement('td');
axe.utils.getFlattenedTree(node);
assert.doesNotThrow(function () {
axe.commons.table.getScope(node);
});
});

describe('auto scope', function() {
it('return `auto` with implicit row and col scope', function() {
describe('auto scope', function () {
it('return `auto` with implicit row and col scope', function () {
fixture.innerHTML =
'<table>' +
'<tr><th id="target">1</th><td>ok</td></tr>' +
Expand All @@ -50,7 +54,7 @@ describe('table.getScope', function() {
assert.equal(axe.commons.table.getScope(target), 'auto');
});

it('return `auto` with implicit row and col scope, not in the first column', function() {
it('return `auto` with implicit row and col scope, not in the first column', function () {
fixture.innerHTML =
'<table>' +
'<tr><td></td><th id="target">1</th></tr>' +
Expand All @@ -62,7 +66,7 @@ describe('table.getScope', function() {
assert.equal(axe.commons.table.getScope(target), 'auto');
});

it('return `auto` with implicit row and col scope, not in the first row', function() {
it('return `auto` with implicit row and col scope, not in the first row', function () {
fixture.innerHTML =
'<table>' +
'<tr><td></td><th>1</th></tr>' +
Expand All @@ -73,43 +77,54 @@ describe('table.getScope', function() {
axe.testUtils.flatTreeSetup(fixture.firstChild);
assert.equal(axe.commons.table.getScope(target), 'auto');
});

it('return `auto` without an actualNode or in the tree', function () {
var serialNode = new axe.SerialVirtualNode({
nodeName: 'th'
});

assert.equal(axe.commons.table.getScope(serialNode), 'auto');
});
});

describe('col scope', function() {
it('returns `col` with explicit col scope on TH', function() {
describe('col scope', function () {
it('returns `col` with explicit col scope on TH', function () {
fixture.innerHTML =
'<table>' +
'<tr><td></td><th id="target" scope="col">1</th></tr>' +
'<tr><th>2</th><td>ok</td><td></td></tr>' +
'</table>';

var target = $id('target');
fixtureSetup();
assert.equal(axe.commons.table.getScope(target), 'col');
});

it('returns `col` with explicit col scope on TD', function() {
it('returns `col` with explicit col scope on TD', function () {
fixture.innerHTML =
'<table>' +
'<tr><td></td><td id="target" scope="col">1</td></tr>' +
'<tr><th>2</th><td>ok</td></tr>' +
'</table>';

var target = $id('target');
fixtureSetup();
assert.equal(axe.commons.table.getScope(target), 'col');
});

it('returns `col` with role = columnheader on TD', function() {
it('returns `col` with role = columnheader on TD', function () {
fixture.innerHTML =
'<table>' +
'<tr><td></td><td id="target" scope="row" role="columnheader">1</td></tr>' +
'<tr><th>2</th><td>ok</td></tr>' +
'</table>';

var target = $id('target');
fixtureSetup();
assert.equal(axe.commons.table.getScope(target), 'col');
});

it('returns `col` when part of a row of all TH elements', function() {
it('returns `col` when part of a row of all TH elements', function () {
fixture.innerHTML =
'<table>' +
'<tr><th></th><th id="target">1</th></tr>' +
Expand All @@ -121,7 +136,7 @@ describe('table.getScope', function() {
assert.equal(axe.commons.table.getScope(target), 'col');
});

it('returns `col` when part of both a row and a column of all TH elements', function() {
it('returns `col` when part of both a row and a column of all TH elements', function () {
fixture.innerHTML =
'<table>' +
'<tr><th id="target">1</th><th></th></tr>' +
Expand All @@ -133,7 +148,7 @@ describe('table.getScope', function() {
assert.equal(axe.commons.table.getScope(target), 'col');
});

it('understands colspan on the table', function() {
it('understands colspan on the table', function () {
fixture.innerHTML =
'<table>' +
'<tr> <th colspan="2"></th> </tr>' +
Expand All @@ -145,7 +160,7 @@ describe('table.getScope', function() {
assert.equal(axe.commons.table.getScope(target), 'col');
});

it('understands colspan on the cell', function() {
it('understands colspan on the cell', function () {
fixture.innerHTML =
'<table>' +
'<tr> <th id="target" colspan="2"></th> </tr>' +
Expand All @@ -157,41 +172,44 @@ describe('table.getScope', function() {
});
});

describe('row scope', function() {
it('returns `row` with explicit row scope on TH', function() {
describe('row scope', function () {
it('returns `row` with explicit row scope on TH', function () {
fixture.innerHTML =
'<table>' +
'<tr><td></td><th>1</th></tr>' +
'<tr><th id="target" scope="row">2</th><td>ok</td><td></td></tr>' +
'</table>';

var target = $id('target');
fixtureSetup();
assert.equal(axe.commons.table.getScope(target), 'row');
});

it('returns `row` with explicit row scope on TD', function() {
it('returns `row` with explicit row scope on TD', function () {
fixture.innerHTML =
'<table>' +
'<tr><td></td><td>1</td></tr>' +
'<tr><td id="target" scope="row">2</td><td>ok</td></tr>' +
'</table>';

var target = $id('target');
fixtureSetup();
assert.equal(axe.commons.table.getScope(target), 'row');
});

it('returns `row` with role = rowheader on TD', function() {
it('returns `row` with role = rowheader on TD', function () {
fixture.innerHTML =
'<table>' +
'<tr><td></td><td>1</td></tr>' +
'<tr><td id="target" scope="col" role="rowheader">2</td><td>ok</td></tr>' +
'</table>';

var target = $id('target');
fixtureSetup();
assert.equal(axe.commons.table.getScope(target), 'row');
});

it('returns `row` when part of a column of all TH elements', function() {
it('returns `row` when part of a column of all TH elements', function () {
fixture.innerHTML =
'<table>' +
'<tr><th></th><td>1</td></tr>' +
Expand All @@ -203,7 +221,7 @@ describe('table.getScope', function() {
assert.equal(axe.commons.table.getScope(target), 'row');
});

it('understands rowspan in the table', function() {
it('understands rowspan in the table', function () {
fixture.innerHTML =
'<table>' +
'<tr> <th rowspan="2"></th> <th></th> <th></th> </tr>' +
Expand All @@ -214,7 +232,7 @@ describe('table.getScope', function() {
assert.equal(axe.commons.table.getScope(target), 'row');
});

it('understands rowspan on the cell', function() {
it('understands rowspan on the cell', function () {
fixture.innerHTML =
'<table>' +
'<tr> <th></th> <th></th> </tr>' +
Expand All @@ -227,7 +245,7 @@ describe('table.getScope', function() {
});
});

it('does not throw on empty rows', function() {
it('does not throw on empty rows', function () {
fixture.innerHTML =
'<table>' +
'<tr> </tr>' +
Expand Down
16 changes: 8 additions & 8 deletions test/commons/table/is-header.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
describe('table.isHeader', function() {
describe('table.isHeader', function () {
'use strict';

var table = axe.commons.table;
Expand All @@ -21,43 +21,43 @@ describe('table.isHeader', function() {
'</tr>' +
'</table>';

it('should return true for column headers', function() {
it('should return true for column headers', function () {
fixtureSetup(tableFixture);
var cell = document.querySelector('#ch1');
assert.isTrue(table.isHeader(cell));
});

it('should return true if cell is a row header', function() {
it('should return true if cell is a row header', function () {
fixtureSetup(tableFixture);
var cell = document.querySelector('#rh1');
assert.isTrue(table.isHeader(cell));
});

it('should return false if cell is not a column or row header', function() {
it('should return false if cell is not a column or row header', function () {
fixtureSetup(tableFixture);
var cell = document.querySelector('#cell1');
assert.isFalse(table.isHeader(cell));
});

it('should return true if referenced by another cells headers attr', function() {
it('should return true if referenced by another cells headers attr', function () {
fixture.innerHTML =
'<table>' +
'<tr><td id="target">1</td><td headers="bar target foo"></tr>' +
'</table>';

var target = document.querySelector('#target');

fixtureSetup();
assert.isTrue(table.isHeader(target));
});

it('should return false otherwise', function() {
it('should return false otherwise', function () {
fixture.innerHTML =
'<table>' +
'<tr><td id="target">1</td><td headers="bar monkeys foo"></tr>' +
'</table>';

var target = document.querySelector('#target');

fixtureSetup();
assert.isFalse(table.isHeader(target));
});
});
Loading

0 comments on commit b16f553

Please sign in to comment.