diff --git a/.eslintrc.js b/.eslintrc.js index a6617baecd..45e80130bd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,5 @@ module.exports = { + root: true, extends: ['prettier'], parserOptions: { ecmaVersion: 2021 @@ -70,6 +71,10 @@ module.exports = { overrides: [ { files: ['lib/**/*.js'], + excludedFiles: [ + 'lib/core/reporters/**/*.js', + 'lib/**/*-after.js' + ], parserOptions: { sourceType: 'module' }, @@ -87,6 +92,23 @@ module.exports = { 'no-use-before-define': 'off' } }, + { + // after functions and reporters will not be run inside the same context as axe.run so should not access browser globals that require context specific information (window.location, window.getComputedStyles, etc.) + files: [ + 'lib/**/*-after.js', + 'lib/core/reporters/**/*.js' + ], + parserOptions: { + sourceType: 'module' + }, + env: {}, + globals: {}, + rules: { + 'func-names': [2, 'as-needed'], + 'prefer-const': 2, + 'no-use-before-define': 'off' + } + }, { files: ['test/**/*.js'], parserOptions: { diff --git a/CHANGELOG.md b/CHANGELOG.md index a17e292c80..2623d61617 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ 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. +### [4.3.3](https://github.com/dequelabs/axe-core/compare/v4.3.2...v4.3.3) (2021-08-24) + +### Bug Fixes + +- **aria-allowed-role:** Update allowed roles based on ARIA spec updates ([#3124](https://github.com/dequelabs/axe-core/issues/3124)) ([a1f637f](https://github.com/dequelabs/axe-core/commit/a1f637f3f5ebf0e483fd21865bd2191c24ccb87a)) +- **d.ts:** Add PartialResults type ([#3126](https://github.com/dequelabs/axe-core/issues/3126)) ([5cdaf01](https://github.com/dequelabs/axe-core/commit/5cdaf012a2f09834d8b7e5f3a645a40e61d47ea9)) +- **reporter:** Run inside isolated contexts ([#3129](https://github.com/dequelabs/axe-core/issues/3129)) ([98066f8](https://github.com/dequelabs/axe-core/commit/98066f8864d4ef09b4b3de12456992d3ca3207b4)) + ### [4.3.2](https://github.com/dequelabs/axe-core/compare/v4.3.1...v4.3.2) (2021-07-27) ### Bug Fixes diff --git a/axe.d.ts b/axe.d.ts index 9d50626755..f0ba24b635 100644 --- a/axe.d.ts +++ b/axe.d.ts @@ -96,13 +96,8 @@ declare namespace axe { preload?: boolean; performanceTimer?: boolean; } - interface AxeResults { + interface AxeResults extends EnvironmentData { toolOptions: RunOptions; - testEngine: TestEngine; - testRunner: TestRunner; - testEnvironment: TestEnvironment; - url: string; - timestamp: string; passes: Result[]; violations: Result[]; incomplete: Result[]; @@ -262,7 +257,9 @@ declare namespace axe { interface PartialResult { frames: SerialDqElement[]; results: PartialRuleResult[]; + environmentData?: EnvironmentData; } + type PartialResults = Array interface FrameContext { frameSelector: CrossTreeSelector; frameContext: ContextObject; @@ -271,6 +268,13 @@ declare namespace axe { getFrameContexts: (context?: ElementContext) => FrameContext[]; shadowSelect: (selector: CrossTreeSelector) => Element | null; } + interface EnvironmentData { + testEngine: TestEngine; + testRunner: TestRunner; + testEnvironment: TestEnvironment; + url: string; + timestamp: string; + } let version: string; let plugins: any; @@ -333,7 +337,7 @@ declare namespace axe { * @param {RunOptions} options Optional Options passed into rules or checks, temporarily modifying them. */ function finishRun( - partialResults: Array, + partialResults: PartialResults, options: RunOptions ): Promise; diff --git a/bower.json b/bower.json index f3cc9089d7..9db9259b71 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.3.2", + "version": "4.3.3", "contributors": [ { "name": "David Sturley", diff --git a/doc/run-partial.md b/doc/run-partial.md index 625aa790d9..011edf4a85 100644 --- a/doc/run-partial.md +++ b/doc/run-partial.md @@ -81,6 +81,13 @@ The `axe.utils.getFrameContexts` method takes any valid context, and returns an - `frameSelector`: This is a CSS selector, or array of CSS selectors in case of nodes in a shadow DOM tree to locate the frame element to be tested. - `frameContext`: This is an object is a context object that should be tested in the particular frame. + +## Custom Rulesets and Reporters + +Because `axe.finishRun` does not run inside the page, the `reporter` and `after` methods do not have access to the top-level `window` and `document` objects, and might not have access to common browser APIs. Axe-core reporter use the `environmentData` property that is set on the partialResult object of the initiator. + +Because of this constraint, custom reporters, and custom rulesets that add `after` methods must not rely on browser APIs or globals. Any data needed for either should either be taken from the `environmentData` property, or collected in an `evaluate` method of a check, and stored using its `.data()` method. + ## Recommendations When building integrations with browser drivers using axe-core, it is safer and more stable to use `axe.runPartial` and `axe.finishRun` then to use `axe.run`. These two methods ensure that no information from one frame is ever handed off to another. That way if any script in a frame interferes with the `axe` object, or with `window.postMessage`, other frames will not be affected. diff --git a/lib/core/core.js b/lib/core/core.js index cf02f7c766..7664e8b676 100644 --- a/lib/core/core.js +++ b/lib/core/core.js @@ -22,7 +22,12 @@ import frameMessenger from './public/frame-messenger'; import getRules from './public/get-rules'; import load from './public/load'; import registerPlugin from './public/plugins'; -import { hasReporter, getReporter, addReporter } from './public/reporter'; +import { + reporters, + hasReporter, + getReporter, + addReporter +} from './public/reporter'; import reset from './public/reset'; import runRules from './public/run-rules'; import runVirtualRule from './public/run-virtual-rule'; @@ -62,6 +67,9 @@ axe._thisWillBeDeletedDoNotUse.base = { Rule, metadataFunctionMap }; +axe._thisWillBeDeletedDoNotUse.public = { + reporters +}; axe.imports = imports; diff --git a/lib/core/public/finish-run.js b/lib/core/public/finish-run.js index c9c7d0d1dd..63228d0bb6 100644 --- a/lib/core/public/finish-run.js +++ b/lib/core/public/finish-run.js @@ -9,6 +9,7 @@ import { export default function finishRun(partialResults, options = {}) { options = clone(options); + const { environmentData } = partialResults.find(r => r.environmentData) || {} // normalize the runOnly option for the output of reporters toolOptions axe._audit.normalizeOptions(options); @@ -20,7 +21,7 @@ export default function finishRun(partialResults, options = {}) { results.forEach(publishMetaData); results = results.map(finalizeRuleResult); - return createReport(results, options); + return createReport(results, { environmentData, ...options }); } function setFrameSpec(partialResults) { diff --git a/lib/core/public/reporter.js b/lib/core/public/reporter.js index d3a4384c6d..a95f42f306 100644 --- a/lib/core/public/reporter.js +++ b/lib/core/public/reporter.js @@ -1,4 +1,4 @@ -const reporters = {}; +export const reporters = {}; let defaultReporter; export function hasReporter(reporterName) { diff --git a/lib/core/public/run-partial.js b/lib/core/public/run-partial.js index 50dc14722c..e543397933 100644 --- a/lib/core/public/run-partial.js +++ b/lib/core/public/run-partial.js @@ -1,6 +1,6 @@ import Context from '../base/context'; import teardown from './teardown'; -import { DqElement, getSelectorData, assert } from '../utils'; +import { DqElement, getSelectorData, assert, getEnvironmentData } from '../utils'; import normalizeRunParams from './run/normalize-run-params'; export default function runPartial(...args) { @@ -28,7 +28,12 @@ export default function runPartial(...args) { const frames = contextObj.frames.map(({ node }) => { return new DqElement(node, options).toJSON(); }); - return { results, frames }; + let environmentData; + if (contextObj.initiator) { + environmentData = getEnvironmentData(); + } + + return { results, frames, environmentData }; }) .finally(() => { axe._running = false; diff --git a/lib/core/public/run-virtual-rule.js b/lib/core/public/run-virtual-rule.js index 1477d3796c..d474dcb112 100644 --- a/lib/core/public/run-virtual-rule.js +++ b/lib/core/public/run-virtual-rule.js @@ -5,6 +5,7 @@ import { publishMetaData, finalizeRuleResult, aggregateResult, + getEnvironmentData, getRule } from '../utils'; @@ -54,7 +55,7 @@ function runVirtualRule(ruleId, vNode, options = {}) { ); return { - ...helpers.getEnvironmentData(), + ...getEnvironmentData(), ...results, toolOptions: options }; diff --git a/lib/core/reporters/helpers/get-environment-data.js b/lib/core/reporters/helpers/get-environment-data.js deleted file mode 100644 index 986aa55d27..0000000000 --- a/lib/core/reporters/helpers/get-environment-data.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Add information about the environment axe was run in. - * @return {Object} - */ -function getEnvironmentData(win = window) { - // TODO: remove parameter once we are testing axe-core in jsdom and other - // supported environments - const { - screen = {}, - navigator = {}, - location = {}, - innerHeight, - innerWidth - } = win; - - const orientation = - screen.msOrientation || screen.orientation || screen.mozOrientation || {}; - - return { - testEngine: { - name: 'axe-core', - version: axe.version - }, - testRunner: { - name: axe._audit.brand - }, - testEnvironment: { - userAgent: navigator.userAgent, - windowWidth: innerWidth, - windowHeight: innerHeight, - orientationAngle: orientation.angle, - orientationType: orientation.type - }, - timestamp: new Date().toISOString(), - url: location.href - }; -} - -export default getEnvironmentData; diff --git a/lib/core/reporters/helpers/index.js b/lib/core/reporters/helpers/index.js index a0d0736c1e..dbde573bcb 100644 --- a/lib/core/reporters/helpers/index.js +++ b/lib/core/reporters/helpers/index.js @@ -1,5 +1,4 @@ import failureSummary from './failure-summary'; -import getEnvironmentData from './get-environment-data'; import incompleteFallbackMessage from './incomplete-fallback-msg'; import processAggregate from './process-aggregate'; @@ -8,14 +7,12 @@ import processAggregate from './process-aggregate'; axe._thisWillBeDeletedDoNotUse = axe._thisWillBeDeletedDoNotUse || {}; axe._thisWillBeDeletedDoNotUse.helpers = { failureSummary, - getEnvironmentData, incompleteFallbackMessage, processAggregate }; export { failureSummary, - getEnvironmentData, incompleteFallbackMessage, processAggregate }; diff --git a/lib/core/reporters/na.js b/lib/core/reporters/na.js index 4723364b99..edf76d0fc6 100644 --- a/lib/core/reporters/na.js +++ b/lib/core/reporters/na.js @@ -1,23 +1,20 @@ -import { processAggregate, getEnvironmentData } from './helpers'; +import { processAggregate } from './helpers'; +import { getEnvironmentData } from '../utils'; const naReporter = (results, options, callback) => { console.warn( '"na" reporter will be deprecated in axe v4.0. Use the "v2" reporter instead.' ); - if (typeof options === 'function') { callback = options; options = {}; } - var out = processAggregate(results, options); + const { environmentData, ...toolOptions } = options; callback({ - ...getEnvironmentData(), - toolOptions: options, - violations: out.violations, - passes: out.passes, - incomplete: out.incomplete, - inapplicable: out.inapplicable + ...getEnvironmentData(environmentData), + toolOptions, + ...processAggregate(results, options) }); }; diff --git a/lib/core/reporters/no-passes.js b/lib/core/reporters/no-passes.js index 91b9c49b34..3cc3cbe451 100644 --- a/lib/core/reporters/no-passes.js +++ b/lib/core/reporters/no-passes.js @@ -1,19 +1,21 @@ -import { processAggregate, getEnvironmentData } from './helpers'; +import { processAggregate } from './helpers'; +import { getEnvironmentData } from '../utils'; const noPassesReporter = (results, options, callback) => { if (typeof options === 'function') { callback = options; options = {}; } + const { environmentData, ...toolOptions } = options; // limit result processing to types we want to include in the output options.resultTypes = ['violations']; - var out = processAggregate(results, options); + var { violations } = processAggregate(results, options); callback({ - ...getEnvironmentData(), - toolOptions: options, - violations: out.violations + ...getEnvironmentData(environmentData), + toolOptions, + violations }); }; diff --git a/lib/core/reporters/raw-env.js b/lib/core/reporters/raw-env.js index becd34ef3a..cda4904b37 100644 --- a/lib/core/reporters/raw-env.js +++ b/lib/core/reporters/raw-env.js @@ -1,4 +1,4 @@ -import { getEnvironmentData } from './helpers'; +import { getEnvironmentData } from '../utils'; import rawReporter from './raw'; const rawEnvReporter = (results, options, callback) => { @@ -6,12 +6,11 @@ const rawEnvReporter = (results, options, callback) => { callback = options; options = {}; } - function rawCallback(raw) { - const env = getEnvironmentData(); + const { environmentData, ...toolOptions } = options; + rawReporter(results, toolOptions, (raw) => { + const env = getEnvironmentData(environmentData); callback({ raw, env }); - } - - rawReporter(results, options, rawCallback); + }); }; export default rawEnvReporter; diff --git a/lib/core/reporters/v1.js b/lib/core/reporters/v1.js index 3375ce5978..d83f6d3b72 100644 --- a/lib/core/reporters/v1.js +++ b/lib/core/reporters/v1.js @@ -1,15 +1,13 @@ -import { - processAggregate, - failureSummary, - getEnvironmentData -} from './helpers'; +import { processAggregate, failureSummary } from './helpers'; +import { getEnvironmentData } from '../utils' const v1Reporter = (results, options, callback) => { if (typeof options === 'function') { callback = options; options = {}; - } - var out = processAggregate(results, options); + }; + const { environmentData, ...toolOptions } = options; + const out = processAggregate(results, options); const addFailureSummaries = result => { result.nodes.forEach(nodeResult => { @@ -21,12 +19,9 @@ const v1Reporter = (results, options, callback) => { out.violations.forEach(addFailureSummaries); callback({ - ...getEnvironmentData(), - toolOptions: options, - violations: out.violations, - passes: out.passes, - incomplete: out.incomplete, - inapplicable: out.inapplicable + ...getEnvironmentData(environmentData), + toolOptions, + ...out }); }; diff --git a/lib/core/reporters/v2.js b/lib/core/reporters/v2.js index 0c6102a81e..f4a63333ee 100644 --- a/lib/core/reporters/v2.js +++ b/lib/core/reporters/v2.js @@ -1,18 +1,17 @@ -import { processAggregate, getEnvironmentData } from './helpers'; +import { processAggregate } from './helpers'; +import { getEnvironmentData } from '../utils'; const v2Reporter = (results, options, callback) => { if (typeof options === 'function') { callback = options; options = {}; } + const { environmentData, ...toolOptions } = options; var out = processAggregate(results, options); callback({ - ...getEnvironmentData(), - toolOptions: options, - violations: out.violations, - passes: out.passes, - incomplete: out.incomplete, - inapplicable: out.inapplicable + ...getEnvironmentData(environmentData), + toolOptions, + ...out }); }; diff --git a/lib/core/utils/get-environment-data.js b/lib/core/utils/get-environment-data.js new file mode 100644 index 0000000000..fc99f56796 --- /dev/null +++ b/lib/core/utils/get-environment-data.js @@ -0,0 +1,47 @@ +/** + * Add information about the environment axe was run in. + * @return {EnvironmentData} + */ +export default function getEnvironmentData(metadata = null, win = window) { + if (metadata && typeof metadata === 'object') { + return metadata; + } else if (typeof win !== 'object') { + return {} + } + + return { + testEngine: { + name: 'axe-core', + version: axe.version + }, + testRunner: { + name: axe._audit.brand + }, + testEnvironment: getTestEnvironment(win), + timestamp: new Date().toISOString(), + url: win.location?.href + }; +} + +function getTestEnvironment(win) { + if (!win.navigator || typeof win.navigator !== 'object') { + return {} + } + const { navigator, innerHeight, innerWidth } = win; + const { angle, type } = getOrientation(win) || {} + return { + userAgent: navigator.userAgent, + windowWidth: innerWidth, + windowHeight: innerHeight, + orientationAngle: angle, + orientationType: type + } +} + +function getOrientation({ screen }) { + return ( + screen.orientation || + screen.msOrientation || + screen.mozOrientation + ); +} diff --git a/lib/core/utils/index.js b/lib/core/utils/index.js index 5fc50f7eb9..d1004457b0 100644 --- a/lib/core/utils/index.js +++ b/lib/core/utils/index.js @@ -29,6 +29,7 @@ export { default as getAllChecks } from './get-all-checks'; export { default as getBaseLang } from './get-base-lang'; export { default as getCheckMessage } from './get-check-message'; export { default as getCheckOption } from './get-check-option'; +export { default as getEnvironmentData } from './get-environment-data'; export { default as getFrameContexts } from './get-frame-contexts'; export { default as getFriendlyUriEnd } from './get-friendly-uri-end'; export { default as getNodeAttributes } from './get-node-attributes'; diff --git a/lib/standards/html-elms.js b/lib/standards/html-elms.js index 7d889a26f8..3b854e1b71 100644 --- a/lib/standards/html-elms.js +++ b/lib/standards/html-elms.js @@ -98,7 +98,7 @@ const htmlElms = { }, b: { contentTypes: ['phrasing', 'flow'], - allowedRoles: false + allowedRoles: true }, base: { allowedRoles: false, @@ -612,7 +612,14 @@ const htmlElms = { }, nav: { contentTypes: ['sectioning', 'flow'], - allowedRoles: ['doc-index', 'doc-pagelist', 'doc-toc'], + allowedRoles: [ + 'doc-index', + 'doc-pagelist', + 'doc-toc', + 'menu', + 'menubar', + 'tablist' + ], shadowRoot: true }, noscript: { @@ -685,7 +692,7 @@ const htmlElms = { }, progress: { contentTypes: ['phrasing', 'flow'], - allowedRoles: true, + allowedRoles: false, implicitAttrs: { 'aria-valuemax': '100', 'aria-valuemin': '0', @@ -822,7 +829,7 @@ const htmlElms = { }, svg: { contentTypes: ['embedded', 'phrasing', 'flow'], - allowedRoles: ['application', 'document', 'img'], + allowedRoles: true, chromiumRole: 'SVGRoot', namingMethods: ['svgTitleText'] }, diff --git a/package-lock.json b/package-lock.json index 0cd34a7eee..f17f13ef75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "axe-core", - "version": "4.3.2", + "version": "4.3.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5b4eeeaf68..adfdc009af 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "axe-core", "description": "Accessibility engine for automated Web UI testing", - "version": "4.3.2", + "version": "4.3.3", "license": "MPL-2.0", "engines": { "node": ">=4" diff --git a/sri-history.json b/sri-history.json index bfb92ad5ec..606807a933 100644 --- a/sri-history.json +++ b/sri-history.json @@ -278,5 +278,9 @@ "4.3.2": { "axe.js": "sha256-q0wpLH5m3+m2qIpuhiAfNx2BMxC6E8O5BLBQ52P6AvM=", "axe.min.js": "sha256-eGWJWadXBOoWLwGBXmtbrpqclaIxMoIRVIN9mJ5/idw=" + }, + "4.3.3": { + "axe.js": "sha256-Zvxy3UIxivqa1hGYQez9LNFxm6JBX826VrkGlH4x6hk=", + "axe.min.js": "sha256-KqKNp9dAHi8QV4BZv/lB28ZJt3ROsN8Y7vVRbxewSJ8=" } } diff --git a/test/act-mapping/aria-props-permitted.json b/test/act-mapping/aria-props-permitted.json deleted file mode 100644 index 1b5721eb51..0000000000 --- a/test/act-mapping/aria-props-permitted.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "5c01ea", - "title": "ARIA state or property is permitted", - "axeRules": ["aria-allowed-attr"] -} diff --git a/test/commons/aria/is-aria-role-allowed-on-element.js b/test/commons/aria/is-aria-role-allowed-on-element.js index c9f031f3ed..964bc45220 100644 --- a/test/commons/aria/is-aria-role-allowed-on-element.js +++ b/test/commons/aria/is-aria-role-allowed-on-element.js @@ -22,12 +22,12 @@ describe('aria.isAriaRoleAllowedOnElement', function() { assert.equal(actual, expected); }); - it('returns false for SVG with role alertdialog', function() { + it('returns true for SVG with role alertdialog', function() { var node = document.createElement('svg'); var role = 'alertdialog'; node.setAttribute('role', role); flatTreeSetup(node); - assert.isFalse(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); + assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); it('returns true for OBJECT with role application', function() { @@ -162,6 +162,38 @@ describe('aria.isAriaRoleAllowedOnElement', function() { assert.isFalse(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); }); + it('returns true when B has role navigation', function() { + var node = document.createElement('b'); + var role = 'navigation'; + node.setAttribute('role', role); + flatTreeSetup(node); + assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); + }); + + it('returns true when NAV has role menubar', function() { + var node = document.createElement('nav'); + var role = 'menubar'; + node.setAttribute('role', role); + flatTreeSetup(node); + assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); + }); + + it('returns true when NAV has role tablist', function() { + var node = document.createElement('nav'); + var role = 'tablist'; + node.setAttribute('role', role); + flatTreeSetup(node); + assert.isTrue(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); + }); + + it('returns false when PROGRESS has role button', function() { + var node = document.createElement('progress'); + var role = 'button'; + node.setAttribute('role', role); + flatTreeSetup(node); + assert.isFalse(axe.commons.aria.isAriaRoleAllowedOnElement(node, role)); + }); + it('returns true if given element can have any role', function() { var node = document.createElement('div'); flatTreeSetup(node); diff --git a/test/core/public/finish-run.js b/test/core/public/finish-run.js index 728d687307..b87ccd0986 100644 --- a/test/core/public/finish-run.js +++ b/test/core/public/finish-run.js @@ -83,6 +83,24 @@ describe('axe.finishRun', function() { .catch(done); }); + it('takes partialResult.environmentData to the reporter', function (done) { + var testEngine = { + name: 'dummy-engine', + version: '1.2.3.4.5' + }; + axe + .runPartial() + .then(function(partialResult) { + partialResult.environmentData = { testEngine: testEngine } + return axe.finishRun([partialResult], { runOnly: 'region' }); + }) + .then(function(results) { + assert.deepEqual(results.testEngine, testEngine); + done(); + }) + .catch(done); + }); + it('can report violations results', function(done) { fixture.innerHTML = '
'; axe diff --git a/test/core/public/run-partial.js b/test/core/public/run-partial.js index 491a4ecfbe..53cb2893d6 100644 --- a/test/core/public/run-partial.js +++ b/test/core/public/run-partial.js @@ -129,6 +129,30 @@ describe('axe.runPartial', function() { }); }); + describe('environmentData', function () { + it('includes environment data for the initiator', function (done) { + var context = { + include: ['#fixture'] + } + axe.runPartial(context, { runOnly: 'image-alt' }).then(function(out) { + var keys = Object.keys(axe.utils.getEnvironmentData()); + assert.hasAllKeys(out.environmentData, keys); + done(); + }).catch(done); + }); + + it('is undefined for frames', function (done) { + var context = { + include: ['#fixture'], + initiator: false + } + axe.runPartial(context, { runOnly: 'image-alt' }).then(function(out) { + assert.isUndefined(out.environmentData); + done(); + }).catch(done); + }); + }); + describe('guards', function() { var audit = axe._audit; afterEach(function() { diff --git a/test/core/reporters/na.js b/test/core/reporters/na.js index 75e9ba0e52..3c1ee57358 100644 --- a/test/core/reporters/na.js +++ b/test/core/reporters/na.js @@ -204,15 +204,27 @@ describe('reporters - na', function() { }); it('should add environment data', function() { axe.getReporter('na')(runResults, {}, function(results) { - assert.isNotNull(results.url); - assert.isNotNull(results.timestamp); - assert.isNotNull(results.testEnvironement); - assert.isNotNull(results.testRunner); + assert.isDefined(results.url); + assert.isDefined(results.timestamp); + assert.isDefined(results.testEnvironment); + assert.isDefined(results.testRunner); }); }); it('should add toolOptions property', function() { axe.getReporter('na')(runResults, {}, function(results) { - assert.isNotNull(results.toolOptions); + assert.isDefined(results.toolOptions); + }); + }); + it('uses the environmentData option instead of environment data if specified', function () { + var environmentData = { + myReporter: 'hello world' + } + axe.getReporter('na')(runResults, { environmentData: environmentData }, function(results) { + assert.equal(results.myReporter, 'hello world'); + assert.isUndefined(results.url); + assert.isUndefined(results.timestamp); + assert.isUndefined(results.testEnvironment); + assert.isUndefined(results.testRunner); }); }); }); diff --git a/test/core/reporters/no-passes.js b/test/core/reporters/no-passes.js index 9ee36ee936..d24a84df2f 100644 --- a/test/core/reporters/no-passes.js +++ b/test/core/reporters/no-passes.js @@ -107,22 +107,22 @@ describe('reporters - no-passes', function() { }); }); it('should add the rule id to the rule result', function() { - axe.getReporter('na')(runResults, {}, function(results) { + axe.getReporter('no-passes')(runResults, {}, function(results) { assert.equal(results.violations[0].id, 'idkStuff'); }); }); it('should add tags to the rule result', function() { - axe.getReporter('na')(runResults, {}, function(results) { + axe.getReporter('no-passes')(runResults, {}, function(results) { assert.deepEqual(results.violations[0].tags, ['tag2']); }); }); it('should add the rule help to the rule result', function() { - axe.getReporter('na')(runResults, {}, function(results) { + axe.getReporter('no-passes')(runResults, {}, function(results) { assert.isNotOk(results.violations[0].helpUrl); }); }); it('should add the html to the node data', function() { - axe.getReporter('na')(runResults, {}, function(results) { + axe.getReporter('no-passes')(runResults, {}, function(results) { assert.ok(results.violations[0].nodes); assert.equal(results.violations[0].nodes.length, 1); assert.equal( @@ -132,7 +132,7 @@ describe('reporters - no-passes', function() { }); }); it('should add the target selector array to the node data', function() { - axe.getReporter('na')(runResults, {}, function(results) { + axe.getReporter('no-passes')(runResults, {}, function(results) { assert.ok(results.violations[0].nodes); assert.equal(results.violations[0].nodes.length, 1); assert.deepEqual(results.violations[0].nodes[0].target, [ @@ -143,18 +143,18 @@ describe('reporters - no-passes', function() { }); }); it('should add the description to the rule result', function() { - axe.getReporter('na')(runResults, {}, function(results) { + axe.getReporter('no-passes')(runResults, {}, function(results) { assert.equal(results.violations[0].description, 'something more nifty'); }); }); it('should add the impact to the rule result', function() { - axe.getReporter('na')(runResults, {}, function(results) { + axe.getReporter('no-passes')(runResults, {}, function(results) { assert.equal(results.violations[0].impact, 'cats'); assert.equal(results.violations[0].nodes[0].impact, 'cats'); }); }); it('should map relatedNodes', function() { - axe.getReporter('na')(runResults, {}, function(results) { + axe.getReporter('no-passes')(runResults, {}, function(results) { assert.lengthOf(results.violations[0].nodes[0].all[0].relatedNodes, 1); assert.equal( results.violations[0].nodes[0].all[0].relatedNodes[0].target, @@ -167,16 +167,28 @@ describe('reporters - no-passes', function() { }); }); it('should add environment data', function() { - axe.getReporter('na')(runResults, {}, function(results) { - assert.isNotNull(results.url); - assert.isNotNull(results.timestamp); - assert.isNotNull(results.testEnvironement); - assert.isNotNull(results.testRunner); + axe.getReporter('no-passes')(runResults, {}, function(results) { + assert.isDefined(results.url); + assert.isDefined(results.timestamp); + assert.isDefined(results.testEnvironment); + assert.isDefined(results.testRunner); }); }); it('should add toolOptions property', function() { - axe.getReporter('na')(runResults, {}, function(results) { - assert.isNotNull(results.toolOptions); + axe.getReporter('no-passes')(runResults, {}, function(results) { + assert.isDefined(results.toolOptions); + }); + }); + it('uses the environmentData option instead of environment data if specified', function () { + var environmentData = { + myReporter: 'hello world' + } + axe.getReporter('no-passes')(runResults, { environmentData: environmentData }, function(results) { + assert.equal(results.myReporter, 'hello world'); + assert.isUndefined(results.url); + assert.isUndefined(results.timestamp); + assert.isUndefined(results.testEnvironment); + assert.isUndefined(results.testRunner); }); }); }); diff --git a/test/core/reporters/raw-env.js b/test/core/reporters/raw-env.js index 2c8dcce0e4..448cfddae8 100644 --- a/test/core/reporters/raw-env.js +++ b/test/core/reporters/raw-env.js @@ -129,11 +129,20 @@ describe('reporters - raw-env', function() { it('should pass env object', function() { axe.getReporter('rawEnv')(runResults, {}, function(results) { - assert.isNotNull(results.env); - assert.isNotNull(results.env.url); - assert.isNotNull(results.env.timestamp); - assert.isNotNull(results.env.testEnvironement); - assert.isNotNull(results.env.testRunner); + assert.isDefined(results.env); + assert.isDefined(results.env.url); + assert.isDefined(results.env.timestamp); + assert.isDefined(results.env.testEnvironment); + assert.isDefined(results.env.testRunner); + }); + }); + + it('uses the environmentData option instead of environment data if specified', function () { + var environmentData = { + myReporter: 'hello world' + } + axe.getReporter('rawEnv')(runResults, { environmentData: environmentData }, function(results) { + assert.deepEqual(results.env, environmentData); }); }); }); diff --git a/test/core/reporters/v1.js b/test/core/reporters/v1.js index 37ff9bc656..8e4e532883 100644 --- a/test/core/reporters/v1.js +++ b/test/core/reporters/v1.js @@ -285,15 +285,27 @@ describe('reporters - v1', function() { }); it('should add environment data', function() { axe.getReporter('v1')(runResults, {}, function(results) { - assert.isNotNull(results.url); - assert.isNotNull(results.timestamp); - assert.isNotNull(results.testEnvironement); - assert.isNotNull(results.testRunner); + assert.isDefined(results.url); + assert.isDefined(results.timestamp); + assert.isDefined(results.testEnvironment); + assert.isDefined(results.testRunner); }); }); it('should add toolOptions property', function() { axe.getReporter('v1')(runResults, {}, function(results) { - assert.isNotNull(results.toolOptions); + assert.isDefined(results.toolOptions); + }); + }); + it('uses the environmentData option instead of environment data if specified', function () { + var environmentData = { + myReporter: 'hello world' + } + axe.getReporter('v1')(runResults, { environmentData: environmentData }, function(results) { + assert.equal(results.myReporter, 'hello world'); + assert.isUndefined(results.url); + assert.isUndefined(results.timestamp); + assert.isUndefined(results.testEnvironment); + assert.isUndefined(results.testRunner); }); }); }); diff --git a/test/core/reporters/v2.js b/test/core/reporters/v2.js index a9af4f103a..b7380a5953 100644 --- a/test/core/reporters/v2.js +++ b/test/core/reporters/v2.js @@ -186,15 +186,27 @@ describe('reporters - v2', function() { }); it('should add environment data', function() { axe.getReporter('v2')(runResults, {}, function(results) { - assert.isNotNull(results.url); - assert.isNotNull(results.timestamp); - assert.isNotNull(results.testEnvironement); - assert.isNotNull(results.testRunner); + assert.isDefined(results.url); + assert.isDefined(results.timestamp); + assert.isDefined(results.testEnvironment); + assert.isDefined(results.testRunner); }); }); it('should add toolOptions property', function() { axe.getReporter('v2')(runResults, {}, function(results) { - assert.isNotNull(results.toolOptions); + assert.isDefined(results.toolOptions); + }); + }); + it('uses the environmentData option instead of environment data if specified', function () { + var environmentData = { + myReporter: 'hello world' + } + axe.getReporter('v2')(runResults, { environmentData: environmentData }, function(results) { + assert.equal(results.myReporter, 'hello world'); + assert.isUndefined(results.url); + assert.isUndefined(results.timestamp); + assert.isUndefined(results.testEnvironment); + assert.isUndefined(results.testRunner); }); }); }); diff --git a/test/core/reporters/helpers/get-environment-data.js b/test/core/utils/get-environment-data.js similarity index 77% rename from test/core/reporters/helpers/get-environment-data.js rename to test/core/utils/get-environment-data.js index a653345543..bb4a9a877a 100644 --- a/test/core/reporters/helpers/get-environment-data.js +++ b/test/core/utils/get-environment-data.js @@ -1,6 +1,7 @@ -describe('helpers.getEnvironmentData', function() { +describe('utils.getEnvironmentData', function() { 'use strict'; var __audit; + var getEnvironmentData = axe.utils.getEnvironmentData; before(function() { __audit = axe._audit; axe._audit = { brand: 'Deque' }; @@ -10,21 +11,32 @@ describe('helpers.getEnvironmentData', function() { axe._audit = __audit; }); + it('returns the first argument, if it is truthy', function () { + var input = { + testEngine: { + name: 'axe-core', + version: axe.version + } + } + var output = getEnvironmentData(input) + assert.equal(input, output); + }) + it('should return a `testEngine` property', function() { - var data = helpers.getEnvironmentData(); + var data = getEnvironmentData(); assert.isObject(data.testEngine); assert.equal(data.testEngine.name, 'axe-core'); assert.equal(data.testEngine.version, axe.version); }); it('should return a `testRunner` property', function() { - var data = helpers.getEnvironmentData(); + var data = getEnvironmentData(); assert.isObject(data.testRunner); assert.equal(data.testRunner.name, axe._audit.brand); }); it('should return a `testEnvironment` property', function() { - var data = helpers.getEnvironmentData(); + var data = getEnvironmentData(); assert.isObject(data.testEnvironment); assert.ok(data.testEnvironment.userAgent); assert.ok(data.testEnvironment.windowWidth); @@ -34,12 +46,12 @@ describe('helpers.getEnvironmentData', function() { }); it('should return a `timestamp` property`', function() { - var data = helpers.getEnvironmentData(); + var data = getEnvironmentData(); assert.isDefined(data.timestamp); }); it('should return a `url` property', function() { - var data = helpers.getEnvironmentData(); + var data = getEnvironmentData(); assert.isDefined(data.url); }); @@ -47,7 +59,7 @@ describe('helpers.getEnvironmentData', function() { // other supported environments as what this is testing should be done in // those environment tests it('gets data from the `win` parameter when passed', function() { - var data = helpers.getEnvironmentData({ + var data = getEnvironmentData(null, { screen: { orientation: { type: 'fictional', diff --git a/test/integration/full/isolated-env/frames/focusable.html b/test/integration/full/isolated-env/frames/focusable.html new file mode 100644 index 0000000000..de5374ba35 --- /dev/null +++ b/test/integration/full/isolated-env/frames/focusable.html @@ -0,0 +1,11 @@ + + + + Hello + + + + + + + diff --git a/test/integration/full/isolated-env/frames/isolated.html b/test/integration/full/isolated-env/frames/isolated.html new file mode 100644 index 0000000000..b93be6ab7c --- /dev/null +++ b/test/integration/full/isolated-env/frames/isolated.html @@ -0,0 +1,34 @@ + + + + + + diff --git a/test/integration/full/isolated-env/isolated-env.html b/test/integration/full/isolated-env/isolated-env.html new file mode 100644 index 0000000000..defb21a83c --- /dev/null +++ b/test/integration/full/isolated-env/isolated-env.html @@ -0,0 +1,124 @@ + + + + all rules test + + + + + + + + + + +
+ bad link 1 + +
+
+ + monkeys + +
Foo
+
+
Home
+
+ +
+
+
+
+
Item 1
+
+ +
Some text and some more text
+
Newspaper
+
Copy this content
+
+
Item
+
+ + +

Banana error

+

text

+ +
+
foo
+
bar
+
+
+

Ok

+

Ok

+ + + + +
Ok
+ + + + + I am a circle + + +
+ +
  • Hello
  • +
    +
    + +
    + + +

    Paragraph.

    + + + This content is inside a marquee. +
    +
    +
    +
    + + + + +
    +
    +
    + + + +
    +
    + + + + + diff --git a/test/integration/full/isolated-env/isolated-env.js b/test/integration/full/isolated-env/isolated-env.js new file mode 100644 index 0000000000..0359b9d46c --- /dev/null +++ b/test/integration/full/isolated-env/isolated-env.js @@ -0,0 +1,133 @@ +/* global chai */ + +describe('isolated-env test', function() { + 'use strict'; + var fixture = document.querySelector('#fixture'); + var isIE11 = axe.testUtils.isIE11; + var origPartialResults; + var partialResults; + var win; + + // just a nicer assertion error rather than just doing + // done(err) + function doesNotThrow(err, done) { + if (err instanceof chai.AssertionError) { + return done(err); + } + + var error = new chai.AssertionError( + "expected [Function] to not throw an error but '" + + err.toString() + + "' was thrown" + ); + done(error); + } + + function setEmptyReporter() { + win.axeConfigure({ + reporter: function(results, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + callback(results); + } + }); + } + + before(function(done) { + if (isIE11) { + return this.skip(); + } + + axe.testUtils.awaitNestedLoad(function() { + win = fixture.querySelector('#isolated-frame').contentWindow; + var focusableFrame = fixture.querySelector('#focusable-iframe'); + + // trigger frame-focusable-content rule + var iframePromise = focusableFrame.contentWindow.axe.runPartial({ + include: [], + exclude: [], + initiator: false, + focusable: false, + size: { width: 10, height: 10 } + }); + + var promises = [axe.runPartial(), iframePromise]; + Promise.all(promises) + .then(function(r) { + origPartialResults = r; + done(); + }) + .catch(done); + }); + }); + + beforeEach(function() { + // calling axe.finishRun mutates the partial results + // object and prevents calling finishRun again with + // the same object + partialResults = axe.utils.clone(origPartialResults); + + if (win.axeConfigure) { + win.axeConfigure({ reporter: 'v1' }); + } + }); + + it('successfully isolates axe object in iframe', function() { + assert.isUndefined(win.axe); + assert.isDefined(win.axeFinishRun); + assert.isDefined(win.axeConfigure); + }); + + it('after methods do not error by calling window or DOM methods', function(done) { + setEmptyReporter(); + + win + .axeFinishRun(partialResults) + .then(function(results) { + assert.isDefined(results); + done(); + }) + .catch(function(err) { + doesNotThrow(err, done); + }); + }); + + it('runs all rules and after methods', function(done) { + win + .axeFinishRun(partialResults) + .then(function(results) { + assert.lengthOf(results.inapplicable, 0); + done(); + }) + .catch(function(err) { + doesNotThrow(err, done); + }); + }); + + describe('reporters', function() { + var reporters = axe._thisWillBeDeletedDoNotUse.public.reporters; + Object.keys(reporters).forEach(function(reporterName) { + it( + reporterName + + ' reporter does not error by calling window or DOM methods', + function(done) { + win.axeConfigure({ + reporter: reporterName + }); + + win + .axeFinishRun(partialResults) + .then(function(results) { + assert.isDefined(results); + done(); + }) + .catch(function(err) { + doesNotThrow(err, done); + }); + } + ); + }); + }); +});