UNPKG

3.75 kBJavaScriptView Raw
1import { registerAsyncHelper } from '@ember/test';
2import { assert } from '@ember/debug';
3import RSVP from 'rsvp';
4import config from 'ember-get-config';
5import formatViolation from 'ember-a11y-testing/utils/format-violation';
6import violationsHelper from 'ember-a11y-testing/utils/violations-helper';
7import { mark, markEndAndMeasure } from './utils';
8
9/**
10 * Processes the results of calling axe.a11yCheck. If there are any
11 * violations, it throws an error and then logs them individually.
12 * @param {Object} results
13 * @return {Void}
14 */
15function a11yAuditCallback(results) {
16 let violations = results.violations;
17
18 if (violations.length) {
19 let allViolations = [];
20
21 for (let i = 0, l = violations.length; i < l; i++) {
22 let violation = violations[i];
23 let violationNodes = violation.nodes.map(node => node.html);
24
25 let violationMessage = formatViolation(violation, violationNodes);
26 allViolations.push(violationMessage);
27
28 console.error(violationMessage, violation); // eslint-disable-line no-console
29 violationsHelper.push(violation);
30 }
31
32 let allViolationMessages = allViolations.join('\n');
33 assert(`The page should have no accessibility violations. Violations:\n${allViolationMessages}`);
34 }
35}
36
37/**
38 * Determines if an object is a plain object (as opposed to a jQuery or other
39 * type of object).
40 * @param {Object} obj
41 * @return {Boolean}
42 */
43function isPlainObj(obj) {
44 return typeof obj == 'object' && obj.constructor == Object;
45}
46
47/**
48 * Determines whether supplied object contains `include` and `exclude` axe
49 * context selector properties. This is necessary to distinguish an axe
50 * config object from a context selector object, after a single argument
51 * is supplied to `runA11yAudit`.
52 * @param {Object} obj
53 * @return {Boolean}
54 */
55function isNotSelectorObj(obj) {
56 return !obj.hasOwnProperty('include') && !obj.hasOwnProperty('exclude');
57}
58
59/**
60 * Runs the axe a11y audit with the given context selector and options.
61 * The context defaults to '#ember-testing-container' if not specified.
62 * The options default axe-core defaults.
63 *
64 * @method runA11yAudit
65 * @private
66 */
67function runA11yAudit(contextSelector = '#ember-testing-container', axeOptions) {
68 mark('a11y_audit_start');
69
70 // Support passing axeOptions as a single argument
71 if (arguments.length === 1 && isPlainObj(contextSelector) && isNotSelectorObj(contextSelector)) {
72 axeOptions = contextSelector;
73 contextSelector = '#ember-testing-container';
74 }
75
76 if (!axeOptions) {
77 // Try load default config
78 let a11yConfig = config['ember-a11y-testing'] || {};
79 let componentOptions = a11yConfig['componentOptions'] || {};
80 axeOptions = componentOptions['axeOptions'] || {};
81 }
82
83 document.body.classList.add('axe-running');
84
85 let auditPromise = new RSVP.Promise((resolve, reject) => {
86 axe.run(contextSelector, axeOptions, (error, result) => {
87 if (!error) {
88 return resolve(result);
89 } else {
90 return reject(error);
91 }
92 })
93 });
94
95 return auditPromise
96 .then(a11yAuditCallback)
97 .finally(() => {
98 document.body.classList.remove('axe-running');
99 markEndAndMeasure('a11y_audit', 'a11y_audit_start', 'a11y_audit_end');
100 });
101}
102
103// Register an async helper to use in acceptance tests
104registerAsyncHelper('a11yAudit', function(app, ...args) {
105 return runA11yAudit(...args);
106});
107
108/**
109 * A wrapper method to run the async a11yAudit test helper if in an acceptance
110 * testing situation, but also supports being used in integration/unit test
111 * scenarios.
112 *
113 * @method a11yAudit
114 * @public
115 */
116export default function a11yAudit(...args) {
117 if (window.a11yAudit) {
118 return window.a11yAudit(...args);
119 }
120
121 return runA11yAudit(...args);
122}