-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initial version #1
+9,292
−0
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
215c0ff
init package
2fd8f17
add microbundle
73ffab2
add linting & formatting tools
9882f26
add axe-core
2866e2e
add code + readme
09b181e
move axe options to the constructor to use axe.configure
8bc9dd6
simplify Usage example
888d84f
seperate the AxeObserver and log function
jul-sh 9056e0c
improve debouncing with lodash
jul-sh e110cbd
make axeConfiguration function to for better API support
jul-sh 8e8fbd4
Use options objs to avoid breaking the API later
93be81f
bump version to 1.0.0
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# distribution | ||
dist | ||
|
||
# Logs | ||
logs | ||
*.log | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import axeCore from 'axe-core' | ||
import debounce from 'lodash.debounce' | ||
|
||
// If requestIdleCallback is not supported, we fallback to setTimeout | ||
// Ref: https://developers.google.com/web/updates/2015/08/using-requestidlecallback | ||
const requestIdleCallback = | ||
window.requestIdleCallback || | ||
function(callback) { | ||
setTimeout(callback, 1) | ||
} | ||
|
||
// The AxeObserver class takes a violationsCallback, which is invoked with an | ||
// array of observed violations. | ||
export default class AxeObserver { | ||
constructor( | ||
violationsCallback, | ||
{ | ||
axeCoreConfiguration = { | ||
reporter: 'v2', | ||
checks: [ | ||
{ | ||
id: 'color-contrast', | ||
options: { | ||
// Prevent axe from automatically scrolling | ||
noScroll: true | ||
} | ||
} | ||
] | ||
}, | ||
axeCoreInstanceCallback | ||
} | ||
) { | ||
if (typeof violationsCallback !== 'function') { | ||
throw new Error( | ||
'The AxeObserver constructor requires a violationsCallback' | ||
) | ||
} | ||
|
||
this._violationsCallback = violationsCallback | ||
|
||
this.observe = this.observe.bind(this) | ||
this.disconnect = this.disconnect.bind(this) | ||
this._auditTargetNode = this._auditTargetNode.bind(this) | ||
|
||
this._mutationObservers = [] | ||
this._alreadyReportedIncidents = new Set() | ||
|
||
// Allow for registering plugins etc | ||
if (typeof axeInstanceCallback === 'function') { | ||
axeCoreInstanceCallback(axeCore) | ||
} | ||
|
||
// Configure axe | ||
axeCore.configure(axeCoreConfiguration) | ||
} | ||
observe(targetNode, { debounceMs = 1000, maxWaitMs = debounceMs * 5 }) { | ||
if (!targetNode) { | ||
throw new Error('AxeObserver.observe requires a targetNode') | ||
} | ||
|
||
const scheduleAudit = debounce( | ||
() => requestIdleCallback(() => this._auditTargetNode(targetNode)), | ||
debounceMs, | ||
{ leading: true, maxWait: maxWaitMs } | ||
) | ||
const mutationObserver = new window.MutationObserver(scheduleAudit) | ||
|
||
// observe changes | ||
mutationObserver.observe(targetNode, { | ||
attributes: true, | ||
subtree: true | ||
}) | ||
|
||
this._mutationObservers.push(mutationObserver) | ||
|
||
// run initial audit | ||
scheduleAudit(targetNode) | ||
} | ||
disconnect() { | ||
this._mutationObservers.forEach(mutationObserver => { | ||
mutationObserver.disconnect() | ||
}) | ||
} | ||
async _auditTargetNode(targetNode) { | ||
const response = await axeCore.run(targetNode) | ||
|
||
const violationsToReport = response.violations.filter(violation => { | ||
const filteredNodes = violation.nodes.filter(node => { | ||
const key = node.target.toString() + violation.id | ||
|
||
const wasAlreadyReported = this._alreadyReportedIncidents.has(key) | ||
|
||
if (wasAlreadyReported) { | ||
// filter out this violation for this node | ||
return false | ||
} else { | ||
// add to alreadyReportedIncidents as we'll report it now | ||
this._alreadyReportedIncidents.add(key) | ||
return true | ||
} | ||
}) | ||
|
||
return filteredNodes.length > 0 | ||
}) | ||
|
||
const hasViolationsToReport = violationsToReport.length > 0 | ||
|
||
if (hasViolationsToReport) { | ||
this._violationsCallback(violationsToReport) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Agnostic Axe | ||
|
||
Test your web application with the [axe-core](https://github.com/dequelabs/axe-core) accessibility testing library. Results will show in the browser console. | ||
|
||
## Installation | ||
|
||
You can install the module from NPM. | ||
|
||
```sh | ||
npm install --save-dev agnostic-axe | ||
``` | ||
|
||
## Usage | ||
|
||
```js | ||
import('agnostic-axe').then(({ AxeObserver, logViolations }) => { | ||
const MyAxeObserver = new AxeObserver(logViolations) | ||
MyAxeObserver.observe(document) | ||
}) | ||
``` | ||
|
||
> Be sure to only run the module in your development environment. Else your application will use more resources than necessary when in production. | ||
|
||
In the example above, once the `observe` method has been invoked, `MyAxeObserver` starts reporting accessibility defects to the browser console. It continously observes the passed node for changes. If a change has been detected, it will reanalyze the node and report any new accessibility defects. | ||
|
||
To stop observing changes, one can call the `disconnect` method. | ||
jul-sh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```js | ||
MyAxeObserver.disconnect() | ||
``` | ||
|
||
### Configuration | ||
|
||
The `AxeObserver` constructor takes two parameters: | ||
|
||
- `violationsCallback` (required). A function that is invoked with an array of violations, as reported by [axe-core](https://github.com/dequelabs/axe-core). To log violations to the console, simply pass the `logViolations` function exported by this module. | ||
- `options` (optional). An object with that supports the following configuration keys: | ||
- `axeCoreConfiguration` (optional). A configuration object for axe-core. Read about the object here in the [axe-core docs](https://github.com/dequelabs/axe-core/blob/master/doc/API.md#api-name-axeconfigure) and see the `agnostic-axe` source code for the default options used. | ||
- `axeCoreInstanceCallback` (optional). A function that is invoked with the instance of [axe-core](https://github.com/dequelabs/axe-core) used by module. It can be used for complex interactions with the [axe-core API](https://github.com/dequelabs/axe-core/blob/develop/doc/API.md) such as registering plugins. | ||
|
||
The `AxeObserver.observe` method takes two parameters: | ||
|
||
- `targetNode` (required). The node that should be observed & analyzed. | ||
- `options` (optional). An object with that supports the following configuration keys: | ||
- `debounceMs` (optional, defaults to `1000`). The number of milliseconds that updates should cease before an analysis is performed. | ||
- `maxWaitMs` (optional, defaults to `debounceMs * 5`). Number of milliseconds after which an analysis will be performed, even if the targetNode is still updating. | ||
|
||
## Credits | ||
|
||
Agnostic axe itself is merely a wrapper around [axe-core](https://github.com/dequelabs/axe-core) that employs a `MutationObserver` to detect DOM changes automatically. Most of its logic for formatting violations return by [axe-core](https://github.com/dequelabs/axe-core) is taken from [react-axe](https://github.com/dequelabs/react-axe). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import AxeObserver from './AxeObserver.js' | ||
import logViolations from './logViolations.js' | ||
|
||
export default { AxeObserver, logViolations } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import axeCore from 'axe-core' | ||
|
||
const boldCourier = 'font-weight:bold;font-family:Courier;' | ||
const critical = 'color:red;font-weight:bold;' | ||
const serious = 'color:red;font-weight:normal;' | ||
const moderate = 'color:orange;font-weight:bold;' | ||
const minor = 'color:orange;font-weight:normal;' | ||
const defaultReset = 'font-color:black;font-weight:normal;' | ||
|
||
// The logViolations function takes an array of violations and logs them to the | ||
// console in a nice format. Its code is copied from the `react-axe` module. | ||
// Ref: https://github.com/dequelabs/react-axe | ||
export default function logViolations(violations) { | ||
if (violations.length) { | ||
console.group('%cNew aXe issues', serious) | ||
violations.forEach(function(result) { | ||
var fmt | ||
switch (result.impact) { | ||
case 'critical': | ||
fmt = critical | ||
break | ||
case 'serious': | ||
fmt = serious | ||
break | ||
case 'moderate': | ||
fmt = moderate | ||
break | ||
case 'minor': | ||
fmt = minor | ||
break | ||
default: | ||
fmt = minor | ||
break | ||
} | ||
console.groupCollapsed( | ||
'%c%s: %c%s %s', | ||
fmt, | ||
result.impact, | ||
defaultReset, | ||
result.help, | ||
result.helpUrl | ||
) | ||
result.nodes.forEach(function(node) { | ||
failureSummary(node, 'any') | ||
failureSummary(node, 'none') | ||
}) | ||
console.groupEnd() | ||
}) | ||
console.groupEnd() | ||
} | ||
} | ||
|
||
function logElement(node, logFn) { | ||
var el = document.querySelector(node.target.toString()) | ||
if (!el) { | ||
logFn('Selector: %c%s', boldCourier, node.target.toString()) | ||
} else { | ||
logFn('Element: %o', el) | ||
} | ||
} | ||
|
||
function logHtml(node) { | ||
console.log('HTML: %c%s', boldCourier, node.html) | ||
} | ||
|
||
function logFailureMessage(node, key) { | ||
var message = axeCore._audit.data.failureSummaries[key].failureMessage( | ||
node[key].map(function(check) { | ||
return check.message || '' | ||
}) | ||
) | ||
|
||
console.error(message) | ||
} | ||
|
||
function failureSummary(node, key) { | ||
if (node[key].length > 0) { | ||
logElement(node, console.groupCollapsed) | ||
logHtml(node) | ||
logFailureMessage(node, key) | ||
|
||
var relatedNodes = [] | ||
node[key].forEach(function(check) { | ||
relatedNodes = relatedNodes.concat(check.relatedNodes) | ||
}) | ||
|
||
if (relatedNodes.length > 0) { | ||
console.groupCollapsed('Related nodes') | ||
relatedNodes.forEach(function(relatedNode) { | ||
logElement(relatedNode, console.log) | ||
logHtml(relatedNode) | ||
}) | ||
console.groupEnd() | ||
} | ||
|
||
console.groupEnd() | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend accepting an object of options. That will save you from breaking the API later on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, agreed. Updated both the constructor & observe method to support that.