Skip to content

Commit

Permalink
Merge branch 'master' into dev-805
Browse files Browse the repository at this point in the history
  • Loading branch information
ErickRenteria authored Sep 18, 2023
2 parents 7c584f5 + a55b157 commit e16230b
Show file tree
Hide file tree
Showing 38 changed files with 2,202 additions and 73 deletions.
11 changes: 5 additions & 6 deletions .github/ISSUE_TEMPLATE/accessibility_issue.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ body:
multiple: true
options:
- 'able site'
- 'a11y checker engine'
- 'a11y checker extension'
- 'a11y checker'
- 'cypress accessibility checker'
- 'karma accessibility checker'
- 'rule-server'
- 'accessibility-checker-engine'
- 'extension-checker'
- 'accessibility-checker for Node'
- 'cypress-accessibility-checker'
- 'karma-accessibility-checker'
- 'other'
validations:
required: true
Expand Down
11 changes: 5 additions & 6 deletions .github/ISSUE_TEMPLATE/bug_report.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ body:
multiple: true
options:
- 'able site'
- 'a11y checker engine'
- 'a11y checker extension'
- 'a11y checker'
- 'cypress accessibility checker'
- 'karma accessibility checker'
- 'rule-server'
- 'accessibility-checker-engine'
- 'extension-checker'
- 'accessibility-checker for Node'
- 'cypress-accessibility-checker'
- 'karma-accessibility-checker'
- 'other'
validations:
required: true
Expand Down
11 changes: 5 additions & 6 deletions .github/ISSUE_TEMPLATE/feature_request_or_enhancement.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ body:
multiple: true
options:
- 'able site'
- 'a11y checker engine'
- 'a11y checker extension'
- 'a11y checker'
- 'cypress accessibility checker'
- 'karma accessibility checker'
- 'rule-server'
- 'accessibility-checker-engine'
- 'extension-checker'
- 'accessibility-checker for Node'
- 'cypress-accessibility-checker'
- 'karma-accessibility-checker'
- 'other'
validations:
required: true
Expand Down
11 changes: 5 additions & 6 deletions .github/ISSUE_TEMPLATE/question.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@ body:
multiple: true
options:
- 'able site'
- 'a11y checker engine'
- 'a11y checker extension'
- 'a11y checker'
- 'cypress accessibility checker'
- 'karma accessibility checker'
- 'rule-server'
- 'accessibility-checker-engine'
- 'extension-checker'
- 'accessibility-checker for Node'
- 'cypress-accessibility-checker'
- 'karma-accessibility-checker'
- 'other'
validations:
required: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1097,50 +1097,116 @@ export class RPTUtil {
}
if (!isComplexTable && trNodeCount !== 0) {
// a table with headers not located in the first row or first column
isComplexTable = thNodeCount > 0 && !RPTUtil.isTableHeaderInFirstRowOrColumn(table);
isComplexTable = thNodeCount > 0 && !RPTUtil.tableHeaderExists(table);
}
}
table.RPTUtil_isComplexDataTable = isComplexTable;

return isComplexTable;
}

// Return true if a table cell is hidden or contain no data: <td></td>
public static isTableCellEmpty(cell) {
if (!cell || !VisUtil.isNodeVisible(cell) || cell.innerHTML.replace(/&nbsp;/g,' ').trim().length === 0)
return true;

return false;
}

// Return true if a table row is hidden or contain no data: <tr /> or <tr><td></td><td></td></tr>
public static isTableRowEmpty(row) {
if (!row || !row.cells || row.cells.length === 0 || !VisUtil.isNodeVisible(row))
return true;

let passed = true; //empty
for (let c=0; passed && c < row.cells.length; c++) {
let cell = row.cells[c];
passed = RPTUtil.isTableCellEmpty(cell);
}

return passed;
}

// Return true if a table's header is in the first row or column
public static isTableHeaderInFirstRowOrColumn(ruleContext) {
public static tableHeaderExists(ruleContext) {

let passed = false;
let rows = ruleContext.rows;
// Check if the first row is all TH's
if (rows != null && rows.length > 0) {
let firstRow = rows[0];
passed = firstRow.cells.length > 0 && RPTUtil.getChildByTagHidden(firstRow, "td", false, true).length === 0;
// If the first row isn't a header row, try the first column
if (!passed) {
// Assume that the first column has all TH's unless we find a TD in the first column.
passed = true;
for (let i = 0; passed && i < rows.length; ++i) {
// If no cells in this row, that's okay too.
passed = !rows[i].cells ||
rows[i].cells.length === 0 ||
rows[i].cells[0].nodeName.toLowerCase() != "td";
}
}
if (!passed) {
// Special case - both first row and first column are headers, but they did not use
// a th for the upper-left cell
passed = true;
for (let i = 1; passed && i < firstRow.cells.length; ++i) {
passed = firstRow.cells[i].nodeName.toLowerCase() != "td";
}
for (let i = 1; passed && i < rows.length; ++i) {
// If no cells in this row, that's okay too.
passed = !rows[i].cells ||
rows[i].cells.length === 0 ||
rows[i].cells[0].nodeName.toLowerCase() != "td";
}
}
if (!rows || rows.length === 0)
return null;

// note that table.rows return all all the rows in the table,
// including the rows contained within <thead>, <tfoot>, and <tbody> elements.

//case 1: headers are in the very first row with data in tbody or thead, but not in tfoot
//get the first row with data, ignoring the rows with no data
let passed = true;
let firstRow = rows[0];
for (let r=0; passed && r < rows.length; r++) {
firstRow = rows[r];
// ignore the rows from tfoot
if (firstRow.parentNode && firstRow.parentNode.nodeName.toLowerCase() === 'tfoot') continue;

passed = RPTUtil.isTableRowEmpty(firstRow);
}
return passed;

//table contain no data: <table><tr><td></td><td></td></tr></table>
if (passed)
return null;

// Check if the cells with data in the first data row are all TH's
passed = true;
for (let r=0; passed && r < firstRow.cells.length; r++) {
let cell = firstRow.cells[r];
passed = RPTUtil.isTableCellEmpty(cell) || cell.nodeName.toLowerCase() === 'th';
}

if (passed)
return true;

// Case 2: headers are in the first column with data
// Assume that the first column has all TH's or a TD without data in the first column.
passed = true;
for (let i = 0; passed && i < rows.length; ++i) {
// ignore the rows from tfoot
if (rows[i].parentNode && rows[i].parentNode.nodeName.toLowerCase() === 'tfoot') continue;

// If no cells in this row, or no data at all, that's okay too.
passed = !rows[i].cells ||
rows[i].cells.length === 0 ||
rows[i].cells[0].innerHTML.trim().length === 0 ||
rows[i].cells[0].nodeName.toLowerCase() != "td";
}

if (passed)
return true;

//case 3: all td data cells have headers attributes that point to the id of a th element in the same table.
// https://html.spec.whatwg.org/multipage/tables.html#attributes-common-to-td-and-th-elements
passed = true;
let thIds = [];
let tdHeaders = [];
for (let r=0; passed && r < rows.length; r++) {
let row = rows[r];
// Check if the cells with data in the last data row are all TH's
for (let c=0; c < row.cells.length; c++) {
let cell = row.cells[c];
if (RPTUtil.isTableCellEmpty(cell)) continue;
if (cell.nodeName.toLowerCase() === 'td') {
if (!cell.getAttribute('headers') || cell.getAttribute('headers').trim().length === 0)
passed = false;
else
RPTUtil.concatUniqueArrayItemList(cell.getAttribute('headers').trim().split(" "), tdHeaders);
} else if (cell.nodeName.toLowerCase() === 'th' && cell.getAttribute('id') && cell.getAttribute('id').trim().length > 0)
RPTUtil.concatUniqueArrayItem(cell.getAttribute('id').trim(), thIds);
}
}

if (passed) { // all td elements have headers, to exam if the headers point to a th id
if (thIds.length > 0 && tdHeaders.every(header => thIds.includes(header)))
return true;
}

return false;
}

public static isNodeInGrid(node) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ export let table_headers_exists: Rule = {
const ruleContext = context["dom"].node as HTMLTableElement;
// If this is a layout table or there are no rows, the rule does not apply.
let rows = ruleContext.rows;
if (!RPTUtil.isDataTable(ruleContext) || rows == null || rows.length == 0)
if (!RPTUtil.isDataTable(ruleContext) || rows === null || rows.length === 0)
return null;

let passed = RPTUtil.isTableHeaderInFirstRowOrColumn(ruleContext);

let passed = RPTUtil.tableHeaderExists(ruleContext);
if (passed === null)
return;

if (!passed) {
return RuleFail("Fail_1");
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ <h3>Table Tests</h3>
passedXpaths: [
],
failedXpaths: [
"/html[1]/body[1]/table[1]"
]
},
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html>
<!--
/******************************************************************************
Copyright:: 2020- IBM, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*****************************************************************************/
-->

<head>
<META http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Data table is missing one row header</title>
<style type="text/css">
#vertical-2 thead,#vertical-2 tbody, th, td{
border: 1px solid black;
}
</style>
</head>
<body>


<table id="vertical-2">
<caption>Bottom headers</caption>

<tbody>
<tr>
<td headers="header1">row 1</td>
<td headers="header2">row 1</td>
<td headers="header3">row 1</td>
</tr>
<tr>
<td headers="header1">data</td>
<td headers="header2">data</td>
<td headers="header3">data</td>
</tr>
<tr>
<td headers="header1">data</td>
<td headers="header2">data</td>
<td headers="header3">data</td>
</tr>
<tr>
<th scope="col" id='header1'>Header 1</th>
<th scope="col" id='header2'>Header 2</th>
<th scope="col" id='header3'>Header 3</th>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="4">Footer content</td>
</tr>
</tfoot>
</table>
<script>
UnitTest = {
ruleIds: ["table_headers_exists"],
results: [
{
"ruleId": "table_headers_exists",
"value": [
"INFORMATION",
"FAIL"
],
"path": {
"dom": "/html[1]/body[1]/table[1]",
"aria": "/document[1]/table[1]"
},
"reasonId": "Fail_1",
"message": "Table has no headers identified",
"messageArgs": [],
"apiArgs": [],
"category": "Accessibility"
}
]
};
</script>
</body>
</html>
Loading

0 comments on commit e16230b

Please sign in to comment.