Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
Marcy Sutton committed Sep 8, 2017
2 parents 4177417 + 17279bd commit c4b231d
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 22 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

<a name="3.0.0-alpha.4"></a>
## [3.0.0-alpha.4](https://github.com/dequelabs/axe-core/compare/v3.0.0-alpha.3...v3.0.0-alpha.4) (2017-09-08)

### Bug fixes:

* fix(color-contrast): Include `THEAD` and `TBODY` in contrast checks (#514) ([f98f8bd](https://github.com/dequelabs/axe-core/commit/f98f8bdacc551579c259aefd88bef41ed8157b68))
* fix(responsible): Restrict error construction to known errors (#513) ([0128a7e](https://github.com/dequelabs/axe-core/commit/0128a7ea47847b9fa04dbf98327f4bc1760c5e11))

### Features:

* docs: Document how to propose axe-core rules (#507) ([cabd329](https://github.com/dequelabs/axe-core/commit/cabd3297afbbfe9dbcc41a168b5529ba52f408ba))

<a name="3.0.0-alpha.3"></a>
## [3.0.0-alpha.3](https://github.com/dequelabs/axe-core/compare/v3.0.0-alpha.2...v3.0.0-alpha.3) (2017-09-06)

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ To create a new translation for aXe, start by running `grunt translate --lang=<l

## Contributing

Read the [Proposing Axe-core Rules guide ](./doc/rule-proposal.md)

Read the [documentation on the architecture](./doc/developer-guide.md)

Read the [documentation on contributing](CONTRIBUTING.md)
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "axe-core",
"version": "3.0.0-alpha.3",
"version": "3.0.0-alpha.4",
"contributors": [
{
"name": "David Sturley",
Expand Down
35 changes: 35 additions & 0 deletions doc/act-rules-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# W3C Standardized Rules

Deque Systems is one of leading organizations in the development of standardized accessibility conformance testing rules. The [axe-core rules proposal format](./rule-proposal.md) is an adaptation of the [Accessibility Conformance Testing Rules Format](https://www.w3.org/TR/act-rules-format/).

There are two ways a rule written in the axe-core rule format can be transformed into the ACT Rules format:

## Method 1: Create a single rule

This method is useful for rules with a small number of checks.

1. Add the test input type to it: `rendered page`
2. Add an `assumptions` section, add possible assumptions to it
3. Add an `outcomes` section, describing the different possible outcomes of the rule
4. Add a `Validation Tests` section, that links to the integration tests
5. Update the check to return pass/fail/cantTell instead of true/false/undefined
6. Add control flow to the checks:
- `any` checks should only return `fail` in the last step. All steps leading up to it either return `pass` or say `continue to the next step`.
- `all` and `none` checks should only return `pass` in the last step. All steps leading up to it either return `fail` or say `continue to the next step`.
7. Rename `checks` to `steps` and add a `step X` (where X is the step number) to the heading with the check name.
8. Replace the `tags` section with a `Accessibility Requirements`. The requirements can be determined based on the `wcag###` tags.

## Method 2: Create a rule group

This method is useful for larger rules with `any` checks. This effectively turns every check into its own rule, and turns the rule into a rule group.

1. Copy each check into a new document
2. Add a `steps` heading
3. Add the test input type to it: `rendered page`
4. Add an `assumptions` section, add possible assumptions to it
5. Add an `outcomes` section, describing the different possible outcomes of the rule
6. Copy the `selector` section from the original rule into the new rule documents
7. Update the check to return pass/fail/cantTell instead of true/false/undefined
8. Add a `Validation Tests` section, that links to only those integration tests relevant for this check (now a new rule).
9. Indicate that the new rule is part of a group, using the original axe-core rule ID as the group name.
10. Replace the `tags` section with a `Accessibility Requirements`. The requirements can be determined based on the `wcag###` tags.
4 changes: 2 additions & 2 deletions doc/developer-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ Occasionally, you may want to add additional information about why a Check passe
}
```

See [rules.md](./rules.md) for more information on writing rules and checks,
including incomplete results.
See [Developing Axe-core Rules](./rule-development.md) for more information
on writing rules and checks, including incomplete results.

#### CheckResult

Expand Down
4 changes: 3 additions & 1 deletion doc/rules.md → doc/rule-development.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Writing Axe-core Rules
# Developing Axe-core Rules

Before you start writing axe-core rules, be sure to create a proposal for them in a github issue. Read [Proposing Axe-core rules](./rule-proposal.md) for details.

A rule is a JSON Object that defines a test for aXe-core to run. At a high level, think of a rule as doing two things. First it finds all elements that it should test, and after that it runs a number of checks to see if those selected elements pass or fail the rule.

Expand Down
102 changes: 102 additions & 0 deletions doc/rule-proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Proposing Axe-core Rules

This document outlines the process of proposing a rule. For a technical description on how to build a rule, read [Developing Axe-core Rules](./rule-development.md)

Before you start coding a new rule for axe-core, you *must* create a Github issue to document the rule you want to create. There are many considerations to writing a good rule. It must have no false positives, which is difficult, because there are always many many edge cases to consider. Additionally, a known problem of accessibility testing, is that not all testers hold the same interpretation of the accessibility guidelines. All rules *must* be consistent with the interpretation of Deque Systems, the developer behind Axe-core.

In addition to giving the axe-core development team an opportunity to provide feedback on the proposed rule, the Github Issue will serve as documentation of that rule for the future.

## Rules Format

All Github issues that propose a rule must be tagged as *rule*, and must use the following format:

### Intro

In one sentence, describe what the rule does.

Example: "Ensures ARIA attributes are allowed for an element's role"\

#### Rule help

In one sentence, describe how to resolve the issue.

Example: "Elements must only use allowed ARIA attributes"

#### Tags

Indicate which tags the rule should use.

Example: wcag2a, wcag211, cat.keyboard

### Selector

If possible using a CSS selector, otherwise describe in one sentence using plain language what elements the rule selects.

Example 1: `input[type=checkbox][name]`

Example 2: Select each node that has an attribute starting with `aria-`.

### Checks:

Make each check a subheading of `checks`. Give the check name in the heading, and indicate if the check type is `any`, `all` or `none`.

In short sentences, using plain language, describe what conditions will lead to the check returning false, true or undefined. Keep the steps simple and short. You don't have to write out all the logic. Preferably not, just give the high level view of what the check does.

**Example 1:**

`###` aria/allow-attr (any)

1. Look up the element role
2. Look up a list of aria attributes allowed for that role
3. Return false if the element has aria attributes not in the list
4. Otherwise return true

**Example 2:**

`###` keyboard/focusable-no-name (none)

1. If the element is not focusable, return false
2. If the element has an accessible name, return false
3. Otherwise return true

## Best practices

For rule design, consider the following as best practices:

1. Rules should only have one `none` check so that the error message is specific
2. Rules should not combine `any` and `none`, these should be broken out into separate rules
3. Checks should each only test a single specific case (either a passing technique or a single failing test)

## Template

Use this template when creating the issue:

```markdown
# {{ Rule name }}

{{ Rule description }}

{{ Rule help }}

**Tags:** {{ tag, tag, tag }}

## Selector

{{ selector }}

## Checks

### {{ Check name 1 }} ( any / all / none )

1.

### {{ Check name 2, optional }} ( any / all / none )

1.
```

## W3C Standardized Rules

Deque Systems is one of leading organizations in the development of standardized accessibility conformance testing rules. The above format is an adaptation of the [Accessibility Conformance Testing Rules Format](https://www.w3.org/TR/act-rules-format/).

For details on how the above format maps to the ACT Rules format, see [act-rules-format.md](./act-rules-format.md).
35 changes: 20 additions & 15 deletions lib/commons/color/get-background-color.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,29 +121,34 @@ function elmPartiallyObscured(elm, bgElm, bgColor) {
* @param {Element} elm
*/
function includeMissingElements(elmStack, elm) {
const elementMap = {'TD': 'TR', 'TH': 'TR', 'INPUT': 'LABEL'};
const elementMap = {'TD': ['TR', 'TBODY'], 'TH': ['TR', 'THEAD'], 'INPUT': ['LABEL']};
const tagArray = elmStack.map((elm) => {
return elm.tagName;
});
let bgNodes = elmStack;
//jshint maxdepth:7
for (let candidate in elementMap) {
// check that TR or LABEL has paired nodeName from elementMap, but don't expect elm to be that candidate
// check that TR or LABEL has paired nodeName from elementMap, but don't expect elm to be that candidate
if (tagArray.includes(candidate)) {
// look up the tree for a matching candidate
let ancestorMatch = axe.commons.dom.findUp(elm, elementMap[candidate]);
if (ancestorMatch && elmStack.indexOf(ancestorMatch) === -1) {
// found an ancestor not in elmStack, and it overlaps
let overlaps = axe.commons.dom.visuallyOverlaps(elm.getBoundingClientRect(), ancestorMatch);
if (overlaps) {
// if target is in the elementMap, use its position.
bgNodes.splice(tagArray.indexOf(candidate) + 1, 0, ancestorMatch);
for (let candidateIndex in elementMap[candidate]) {
if (candidate.hasOwnProperty(candidateIndex)) {
// look up the tree for a matching candidate
let ancestorMatch = axe.commons.dom.findUp(elm, elementMap[candidate][candidateIndex]);
if (ancestorMatch && elmStack.indexOf(ancestorMatch) === -1) {
// found an ancestor not in elmStack, and it overlaps
let overlaps = axe.commons.dom.visuallyOverlaps(elm.getBoundingClientRect(), ancestorMatch);
if (overlaps) {
// if target is in the elementMap, use its position.
bgNodes.splice(tagArray.indexOf(candidate) + 1, 0, ancestorMatch);
}
}
// tagName matches value
// (such as LABEL, when matching itself. It should be in the list, but Phantom skips it)
if (elm.tagName === elementMap[candidate][candidateIndex] && tagArray.indexOf(elm.tagName) === -1) {
bgNodes.splice(tagArray.indexOf(candidate) + 1, 0, elm);
}
}
}
// tagName matches value
// (such as LABEL, when matching itself. It should be in the list, but Phantom skips it)
if (elm.tagName === elementMap[candidate] && tagArray.indexOf(elm.tagName) === -1) {
bgNodes.splice(tagArray.indexOf(candidate) + 1, 0, elm);
}
}
}
return bgNodes;
Expand Down
7 changes: 5 additions & 2 deletions lib/core/utils/respondable.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
(function (exports) {
'use strict';
var messages = {},
subscribers = {};
subscribers = {},
errorTypes = Object.freeze(['EvalError', 'RangeError', 'ReferenceError',
'SyntaxError', 'TypeError', 'URIError']);

/**
* get the unique string to be used to identify our instance of aXe
Expand Down Expand Up @@ -154,7 +156,8 @@
*/
function buildErrorObject(error) {
var msg = error.message || 'Unknown error occurred';
var ErrConstructor = window[error.name] || Error;
var errorName = errorTypes.includes(error.name) ? error.name : 'Error';
var ErrConstructor = window[errorName] || Error;

if (error.stack) {
msg += '\n' + error.stack.replace(error.message, '');
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "axe-core",
"description": "Accessibility engine for automated Web UI testing",
"version": "3.0.0-alpha.3",
"version": "3.0.0-alpha.4",
"license": "MPL-2.0",
"contributors": [
{
Expand Down
14 changes: 14 additions & 0 deletions test/checks/color/color-contrast.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,20 @@ describe('color-contrast', function () {
assert.isTrue(result);
});

it('should return true when there is sufficient contrast based on thead', function () {
fixture.innerHTML = '<table><thead style="background: #d00d2c"><tr><th id="target" style="color: #fff; padding: .5em">Col 1</th></tr></thead></table>';
var target = fixture.querySelector('#target');
assert.isTrue(checks['color-contrast'].evaluate.call(checkContext, target));
assert.deepEqual(checkContext._relatedNodes, []);
});

it('should return true when there is sufficient contrast based on tbody', function () {
fixture.innerHTML = '<table><tbody style="background: #d00d2c"><tr><td id="target" style="color: #fff; padding: .5em">Col 1</td></tr></tbody></table>';
var target = fixture.querySelector('#target');
assert.isTrue(checks['color-contrast'].evaluate.call(checkContext, target));
assert.deepEqual(checkContext._relatedNodes, []);
});

it('should return undefined if element overlaps text content', function () {
fixture.innerHTML = '<div style="background-color: white; height: 60px; width: 80px; border:1px solid;position: relative;">' +
'<div id="target" style="color: white; height: 40px; width: 60px; border:1px solid red;">Hi</div>' +
Expand Down
30 changes: 30 additions & 0 deletions test/core/utils/respondable.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,36 @@ describe('axe.utils.respondable', function () {
assert.isTrue(success);
});

it('should create an Error if an invalid error type is passed', function () {
var success = false;
var event = document.createEvent('Event');
window.evil = function () {};
// Define that the event name is 'build'.
event.initEvent('message', true, true);
event.data = JSON.stringify({
_respondable: true,
_source: 'axe.2.0.0',
topic: 'Death star',
error: {
name: 'evil',
message: 'The exhaust port is open!',
trail: '... boom'
},
uuid: mockUUID
});
event.source = window;

axe.utils.respondable(window, 'Death star', null, true, function (data) {
success = true;
assert.instanceOf(data, Error);
assert.equal(data.message, 'The exhaust port is open!');
});

document.dispatchEvent(event);
assert.isTrue(success);
window.evil = undefined;
});

it('uses respondable.isInFrame() to check if the page is in a frame or not', function() {
assert.equal(axe.utils.respondable.isInFrame(), !!window.frameElement);

Expand Down

0 comments on commit c4b231d

Please sign in to comment.