From 727323c07980e2291575f545444d389fb942906f Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Wed, 8 May 2019 10:52:41 -0600 Subject: [PATCH 1/7] chore: update standard-version dep (#1555) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aa2dd76a01..28dc9723b4 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "revalidator": "~0.3.1", "selenium-webdriver": "~3.6.0", "sri-toolbox": "^0.2.0", - "standard-version": "^5.0.0", + "standard-version": "^6.0.0", "typescript": "^2.9.2", "uglify-js": "^3.4.4", "weakmap-polyfill": "^2.0.0" From 51c2e19e874be6a7a203cb77829ac9b29dbf1946 Mon Sep 17 00:00:00 2001 From: Jey Date: Thu, 9 May 2019 16:22:29 +0100 Subject: [PATCH 2/7] fix: check if property exists in cache of flattenedTree (#1536) --- lib/core/utils/flattened-tree.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/utils/flattened-tree.js b/lib/core/utils/flattened-tree.js index 5012acb2d3..68dad0f270 100644 --- a/lib/core/utils/flattened-tree.js +++ b/lib/core/utils/flattened-tree.js @@ -34,13 +34,13 @@ function virtualDOMfromNode(node, shadowId) { actualNode: node, _isHidden: null, // will be populated by axe.utils.isHidden get isFocusable() { - if (!vNodeCache._isFocusable) { + if (!vNodeCache.hasOwnProperty('_isFocusable')) { vNodeCache._isFocusable = axe.commons.dom.isFocusable(node); } return vNodeCache._isFocusable; }, get tabbableElements() { - if (!vNodeCache._tabbableElements) { + if (!vNodeCache.hasOwnProperty('_tabbableElements')) { vNodeCache._tabbableElements = axe.commons.dom.getTabbableElements( this ); From 1b507b39dfa4ee9f40a9f80740a3be18ad42d72a Mon Sep 17 00:00:00 2001 From: 0xflotus <0xflotus@gmail.com> Date: Thu, 9 May 2019 18:36:46 +0200 Subject: [PATCH 3/7] docs: fixed small errors (#1545) * Update API.md * Update accessibility-supported.md * Update code-submission-guidelines.md * Update developer-guide.md * Update plugins.md * Update rule-development.md --- doc/API.md | 6 +++--- doc/accessibility-supported.md | 2 +- doc/code-submission-guidelines.md | 2 +- doc/developer-guide.md | 4 ++-- doc/plugins.md | 4 ++-- doc/rule-development.md | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/API.md b/doc/API.md index 9c292f754a..da6024734b 100644 --- a/doc/API.md +++ b/doc/API.md @@ -183,7 +183,7 @@ axe.configure({ - `any` - array(optional, default `[]`). This is a list of checks that, if none "pass", will generate a violation. - `all` - array(optional, default `[]`). This is a list of checks that, if any "fails", will generate a violation. - `none` - array(optional, default `[]`). This is a list of checks that, if any "pass", will generate a violation. - - `tags` - array(optional, default `[]`). A list if the tags that "classify" the rule. In practice, you must supply some valid tags or the default evaluation will not invoke the rule. The convention is to include the standard (WCAG 2 and/or section 508), the WCAG 2 level, Section 508 paragraph, and the WCAG 2 success criteria. Tags are constructed by converting all letters to lower case, removing spaces and periods and concatinating the result. E.g. WCAG 2 A success criteria 1.1.1 would become ["wcag2a", "wcag111"] + - `tags` - array(optional, default `[]`). A list if the tags that "classify" the rule. In practice, you must supply some valid tags or the default evaluation will not invoke the rule. The convention is to include the standard (WCAG 2 and/or section 508), the WCAG 2 level, Section 508 paragraph, and the WCAG 2 success criteria. Tags are constructed by converting all letters to lower case, removing spaces and periods and concatenating the result. E.g. WCAG 2 A success criteria 1.1.1 would become ["wcag2a", "wcag111"] - `matches` - string(optional, default `*`). A filtering [CSS selector](./developer-guide.md#supported-css-selectors) that will exclude elements that do not match the CSS selector. - `disableOtherRules` - Disables all rules not included in the `rules` property. - `locale` - A locale object to apply (at runtime) to all rules and checks, in the same shape as `/locales/*.json`. @@ -493,7 +493,7 @@ The `assets` attribute expects an array of preload(able) constraints to be fetch | Asset Type | Description | | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `cssom` | This asset type preloads all CSS Stylesheets rulesets specified in the page. The stylessheets can be an external cross-domain resource, a relative stylesheet or an inline style with in the head tag of the document. If the stylesheet is an external cross-domain a network request is made. An object representing the CSS Rules from each stylesheet is made available to the checks evaluate function as `preloadedAssets` at run-time | +| `cssom` | This asset type preloads all CSS Stylesheets rulesets specified in the page. The stylesheets can be an external cross-domain resource, a relative stylesheet or an inline style with in the head tag of the document. If the stylesheet is an external cross-domain a network request is made. An object representing the CSS Rules from each stylesheet is made available to the checks evaluate function as `preloadedAssets` at run-time | The `timeout` attribute in the object configuration is `optional` and has a fallback default value (10000ms). The `timeout` is essential for any network dependent assets that are preloaded, where-in if a given request takes longer than the specified/ default value, the operation is aborted. @@ -745,7 +745,7 @@ The top-level document or shadow DOM document fragment #### axe.commons.dom.findUp -Recusively walk up the DOM, checking for a node which matches a selector. Warning: this should be used sparingly for performance reasons. +Recursively walk up the DOM, checking for a node which matches a selector. Warning: this should be used sparingly for performance reasons. ##### Synopsis diff --git a/doc/accessibility-supported.md b/doc/accessibility-supported.md index 7d904c35cc..8eca3c8350 100644 --- a/doc/accessibility-supported.md +++ b/doc/accessibility-supported.md @@ -4,7 +4,7 @@ In order to adhere to the manifesto and at the same time be useful to developers ## Accessibility supported -Boiled-down, accessibility supported means that in order for a technique to be valid, it must work on all viable platforms for all assitive technology that are widely used and freely available (paraphrased). +Boiled-down, accessibility supported means that in order for a technique to be valid, it must work on all viable platforms for all assistive technology that are widely used and freely available (paraphrased). We currently test the following AT combinations for support diff --git a/doc/code-submission-guidelines.md b/doc/code-submission-guidelines.md index 8219620cc6..518cc74bad 100644 --- a/doc/code-submission-guidelines.md +++ b/doc/code-submission-guidelines.md @@ -139,7 +139,7 @@ changes in the pull request, so the git log stays lean. We particularly want to You can use git's interactive rebase to manipulate, merge, and rename commits in your local history. If these steps are followed, a force push shouldn't be necessary. -**Do not force push to develop or master under any circulstances.** +**Do not force push to develop or master under any circumstances.** To interactively rebase all of your commits on top of the latest in develop, run: diff --git a/doc/developer-guide.md b/doc/developer-guide.md index 1a2f672cab..c86e3d9442 100644 --- a/doc/developer-guide.md +++ b/doc/developer-guide.md @@ -299,7 +299,7 @@ will make it easier to work with the virtual DOM. #### Description -Recursvely return an array containing the virtual DOM tree for the node specified, excluding comment nodes +Recursively return an array containing the virtual DOM tree for the node specified, excluding comment nodes and shadow DOM nodes `` and ``. This method will return a flattened tree containing both light and shadow DOM, if applicable. @@ -392,7 +392,7 @@ None #### Returns -An object containg the data, relatedNodes, and a way to reset them. +An object containing the data, relatedNodes, and a way to reset them. ```js { diff --git a/doc/plugins.md b/doc/plugins.md index 8d074b7367..436182db22 100644 --- a/doc/plugins.md +++ b/doc/plugins.md @@ -86,11 +86,11 @@ The plugin takes this information and sends the same instructions to its impleme The plugin waits for the commands in the iframes to complete and then executes its instances' action function within the current document. -In the above implementation, the axe promise utility `axe.utils.queue()` is used to coordinate the asynchronous handling of communication accross iframes. +In the above implementation, the axe promise utility `axe.utils.queue()` is used to coordinate the asynchronous handling of communication across iframes. The command handler callback runs the plugin's run function within each iframe. This essentially operates like a recursive call to the run function for the plugin within each iframe. -Once all the iframes' run functions have been executed, the callback is called. This essentially operates as a recursive "return" up the iframe heirarchy until at the top document, the actual callback function is executed. This can be leveraged to pass data back up the iframe hierarchy back to the caller (but this is a more advanced topic). +Once all the iframes' run functions have been executed, the callback is called. This essentially operates as a recursive "return" up the iframe hierarchy until at the top document, the actual callback function is executed. This can be leveraged to pass data back up the iframe hierarchy back to the caller (but this is a more advanced topic). #### Basic plugin instance diff --git a/doc/rule-development.md b/doc/rule-development.md index 2d2dd6424c..7fbe271bc3 100644 --- a/doc/rule-development.md +++ b/doc/rule-development.md @@ -100,11 +100,11 @@ Axe-core handles shadow DOM and cross-domain iframe rules very well - as long as The rule callbacks all receive both a `node` and a `virtualNode` argument (in addition to the `options` argument). `node` points to the DOM Node that is to be evaluated, whereas `virtualNode` points to the node in the flattened tree (the hierarchy that shadow DOM creates that is used for parent child relationships across shadow DOM boundaries). -If your rule looks at any hierarchical context (parents or children) then you need to operate on the `virtualNode` for those operations. Calls to `isHidden` and the accessible name calculation calls will all evaluate the hierarchy. The commons and utils functions will all fetch the `virtualNode` from the flattened tree if you use the `node` implementation (and then simply call the virtualNode one) and are there for backwards compatibility. This backwards compatibility comes at a perfomance cost that can be avoided by simply using the `virtualNode` to start with. We will ask you to change this during PR review, so you might as well just start out by using the `virtualNode`. +If your rule looks at any hierarchical context (parents or children) then you need to operate on the `virtualNode` for those operations. Calls to `isHidden` and the accessible name calculation calls will all evaluate the hierarchy. The commons and utils functions will all fetch the `virtualNode` from the flattened tree if you use the `node` implementation (and then simply call the virtualNode one) and are there for backwards compatibility. This backwards compatibility comes at a performance cost that can be avoided by simply using the `virtualNode` to start with. We will ask you to change this during PR review, so you might as well just start out by using the `virtualNode`. ## iframes -Rules that evaluate the structure and/or the number of elements on the entire page (for example the heading nesting rule, or the landmark rules) will need to do this evaluation across iframe boundaries. What this means is that the check function instead of determining pass/fail/incomplete, perfomas a data gathering function. This data is then passed up the iframe hierarchy to the top window where it is passed into the `after` function, which does the evaluation of the gathered data and determines pass/fail. +Rules that evaluate the structure and/or the number of elements on the entire page (for example the heading nesting rule, or the landmark rules) will need to do this evaluation across iframe boundaries. What this means is that the check function instead of determining pass/fail/incomplete, performs a data gathering function. This data is then passed up the iframe hierarchy to the top window where it is passed into the `after` function, which does the evaluation of the gathered data and determines pass/fail. ## Rules of Thumb for Rule Code Reviewers From 80ae444893511a7807ac252265868ffb6ce3a5c3 Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Thu, 9 May 2019 10:37:34 -0600 Subject: [PATCH 4/7] fix(aria-required-attr): don't require aria-valuemin/max (#1529) * fix(aria-required-attr): don't require aria-valuemin/max * fix failing test --- lib/commons/aria/index.js | 23 +++++++++++-------- test/checks/aria/required-attr.js | 6 +---- .../configure-options/configure-options.js | 2 -- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/commons/aria/index.js b/lib/commons/aria/index.js index ab3fa350b2..c70b365217 100644 --- a/lib/commons/aria/index.js +++ b/lib/commons/aria/index.js @@ -1682,13 +1682,14 @@ lookupTable.role = { scrollbar: { type: 'widget', attributes: { - required: [ - 'aria-controls', - 'aria-valuenow', + required: ['aria-controls', 'aria-valuenow'], + allowed: [ + 'aria-valuetext', + 'aria-orientation', + 'aria-errormessage', 'aria-valuemax', 'aria-valuemin' - ], - allowed: ['aria-valuetext', 'aria-orientation', 'aria-errormessage'] + ] }, owned: null, nameFrom: ['author'], @@ -1775,9 +1776,11 @@ lookupTable.role = { 'aria-valuetext', 'aria-orientation', 'aria-readonly', - 'aria-errormessage' + 'aria-errormessage', + 'aria-valuemax', + 'aria-valuemin' ], - required: ['aria-valuenow', 'aria-valuemax', 'aria-valuemin'] + required: ['aria-valuenow'] }, owned: null, nameFrom: ['author'], @@ -1792,9 +1795,11 @@ lookupTable.role = { 'aria-valuetext', 'aria-required', 'aria-readonly', - 'aria-errormessage' + 'aria-errormessage', + 'aria-valuemax', + 'aria-valuemin' ], - required: ['aria-valuenow', 'aria-valuemax', 'aria-valuemin'] + required: ['aria-valuenow'] }, owned: null, nameFrom: ['author'], diff --git a/test/checks/aria/required-attr.js b/test/checks/aria/required-attr.js index ceea0d876b..176f55621a 100644 --- a/test/checks/aria/required-attr.js +++ b/test/checks/aria/required-attr.js @@ -19,11 +19,7 @@ describe('aria-required-attr', function() { assert.isFalse( checks['aria-required-attr'].evaluate.call(checkContext, node) ); - assert.deepEqual(checkContext._data, [ - 'aria-valuenow', - 'aria-valuemax', - 'aria-valuemin' - ]); + assert.deepEqual(checkContext._data, ['aria-valuenow']); }); it('should return true if there is no role', function() { diff --git a/test/integration/full/configure-options/configure-options.js b/test/integration/full/configure-options/configure-options.js index 5930bb7658..f468a0ae33 100644 --- a/test/integration/full/configure-options/configure-options.js +++ b/test/integration/full/configure-options/configure-options.js @@ -58,8 +58,6 @@ describe('Configure Options', function() { function(error, results) { assert.lengthOf(results.violations, 1, 'violations'); assert.sameMembers(results.violations[0].nodes[0].any[0].data, [ - 'aria-valuemax', - 'aria-valuemin', 'aria-snuggles' ]); done(); From 91a04c5288342a27c89422ca78d1649e5a47317e Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Thu, 9 May 2019 10:38:30 -0600 Subject: [PATCH 5/7] fix(utils): make cache global instead of only setup in axe.run (#1535) * fix(utils): make cache global instead of only setup in axe.run * make cache not public * add integration test for plugins * use backgroundcolor instead of background --- lib/commons/aria/is-accessible-ref.js | 10 +-- lib/commons/dom/is-skip-link.js | 6 +- lib/core/base/cache.js | 33 +++++++ lib/core/public/run-rules.js | 6 +- lib/core/utils/flattened-tree.js | 21 ++++- test/core/base/cache.js | 32 +++++++ test/integration/full/plugin/plugin.html | 27 ++++++ test/integration/full/plugin/plugin.js | 107 +++++++++++++++++++++++ test/testutils.js | 6 +- 9 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 lib/core/base/cache.js create mode 100644 test/core/base/cache.js create mode 100644 test/integration/full/plugin/plugin.html create mode 100644 test/integration/full/plugin/plugin.js diff --git a/lib/commons/aria/is-accessible-ref.js b/lib/commons/aria/is-accessible-ref.js index 0278a1571e..1db12d27dd 100644 --- a/lib/commons/aria/is-accessible-ref.js +++ b/lib/commons/aria/is-accessible-ref.js @@ -4,7 +4,7 @@ const idRefsRegex = /^idrefs?$/; function cacheIdRefs(node, refAttrs) { if (node.hasAttribute) { if (node.nodeName.toUpperCase() === 'LABEL' && node.hasAttribute('for')) { - axe._cache.idRefs[node.getAttribute('for')] = true; + axe._cache.get('idRefs')[node.getAttribute('for')] = true; } refAttrs @@ -12,7 +12,7 @@ function cacheIdRefs(node, refAttrs) { .forEach(attr => { const attrValue = node.getAttribute(attr); axe.utils.tokenList(attrValue).forEach(id => { - axe._cache.idRefs[id] = true; + axe._cache.get('idRefs')[id] = true; }); }); } @@ -35,8 +35,8 @@ aria.isAccessibleRef = function isAccessibleRef(node) { // because axe.commons is not available in axe.utils, we can't do // this caching when we build up the virtual tree - if (!axe._cache.idRefs) { - axe._cache.idRefs = {}; + if (!axe._cache.get('idRefs')) { + axe._cache.set('idRefs', {}); // Get all idref(s) attributes on the lookup table const refAttrs = Object.keys(aria.lookupTable.attributes).filter(attr => { const { type } = aria.lookupTable.attributes[attr]; @@ -46,5 +46,5 @@ aria.isAccessibleRef = function isAccessibleRef(node) { cacheIdRefs(root, refAttrs); } - return axe._cache.idRefs[id] === true; + return axe._cache.get('idRefs')[id] === true; }; diff --git a/lib/commons/dom/is-skip-link.js b/lib/commons/dom/is-skip-link.js index e45525f5a6..0c99776493 100644 --- a/lib/commons/dom/is-skip-link.js +++ b/lib/commons/dom/is-skip-link.js @@ -17,8 +17,8 @@ dom.isSkipLink = function(element) { } let firstPageLink; - if (typeof axe._cache.firstPageLink !== 'undefined') { - firstPageLink = axe._cache.firstPageLink; + if (typeof axe._cache.get('firstPageLink') !== 'undefined') { + firstPageLink = axe._cache.get('firstPageLink'); } else { // define a skip link as any anchor element whose href starts with `#...` // and which precedes the first anchor element whose href doesn't start @@ -29,7 +29,7 @@ dom.isSkipLink = function(element) { )[0]; // null will signify no first page link - axe._cache.firstPageLink = firstPageLink || null; + axe._cache.set('firstPageLink', firstPageLink || null); } // if there are no page links then all all links will need to be diff --git a/lib/core/base/cache.js b/lib/core/base/cache.js new file mode 100644 index 0000000000..68bef2172f --- /dev/null +++ b/lib/core/base/cache.js @@ -0,0 +1,33 @@ +(function() { + 'use strict'; + let _cache = {}; + + const cache = { + /** + * Set an item in the cache. + * @param {String} key - Name of the key. + * @param {*} value - Value to store. + */ + set(key, value) { + _cache[key] = value; + }, + + /** + * Retrieve an item from the cache. + * @param {String} key - Name of the key the value was stored as. + * @returns {*} The item stored + */ + get(key) { + return _cache[key]; + }, + + /** + * Clear the cache. + */ + clear() { + _cache = {}; + } + }; + + axe._cache = cache; +})(); diff --git a/lib/core/public/run-rules.js b/lib/core/public/run-rules.js index d740531371..32be4c952b 100644 --- a/lib/core/public/run-rules.js +++ b/lib/core/public/run-rules.js @@ -3,7 +3,7 @@ // Clean up after resolve / reject function cleanup() { - axe._cache = undefined; + axe._cache.clear(); axe._tree = undefined; axe._selectorData = undefined; } @@ -19,10 +19,6 @@ function cleanup() { function runRules(context, options, resolve, reject) { 'use strict'; try { - axe._cache = { - nodeMap: new WeakMap() - }; - context = new Context(context); axe._tree = context.flatTree; axe._selectorData = axe.utils.getSelectorData(context.flatTree); diff --git a/lib/core/utils/flattened-tree.js b/lib/core/utils/flattened-tree.js index 68dad0f270..cc324c77c3 100644 --- a/lib/core/utils/flattened-tree.js +++ b/lib/core/utils/flattened-tree.js @@ -48,7 +48,7 @@ function virtualDOMfromNode(node, shadowId) { return vNodeCache._tabbableElements; } }; - axe._cache.nodeMap.set(node, vNode); + axe._cache.get('nodeMap').set(node, vNode); return vNode; } @@ -78,11 +78,11 @@ function getSlotChildren(node) { * @param {String} shadowId, optional ID of the shadow DOM that is the closest shadow * ancestor of the node */ -axe.utils.getFlattenedTree = function(node, shadowId) { +function flattenTree(node, shadowId) { // using a closure here and therefore cannot easily refactor toreduce the statements var retVal, realArray, nodeName; function reduceShadowDOM(res, child) { - var replacements = axe.utils.getFlattenedTree(child, shadowId); + var replacements = flattenTree(child, shadowId); if (replacements) { res = res.concat(replacements); } @@ -144,6 +144,19 @@ axe.utils.getFlattenedTree = function(node, shadowId) { return undefined; } } +} + +/** + * Recursvely returns an array of the virtual DOM nodes at this level + * excluding comment nodes and the shadow DOM nodes and + * + * @param {Node} node the current node + * @param {String} shadowId, optional ID of the shadow DOM that is the closest shadow + * ancestor of the node + */ +axe.utils.getFlattenedTree = function(node, shadowId) { + axe._cache.set('nodeMap', new WeakMap()); + return flattenTree(node, shadowId); }; /** @@ -154,5 +167,5 @@ axe.utils.getFlattenedTree = function(node, shadowId) { */ axe.utils.getNodeFromTree = function(vNode, node) { const el = node || vNode; - return axe._cache.nodeMap.get(el); + return axe._cache.get('nodeMap') ? axe._cache.get('nodeMap').get(el) : null; }; diff --git a/test/core/base/cache.js b/test/core/base/cache.js new file mode 100644 index 0000000000..a2da9981c4 --- /dev/null +++ b/test/core/base/cache.js @@ -0,0 +1,32 @@ +describe('axe._cache', function() { + 'use strict'; + + it('should set items from the cache without error', function() { + function fn() { + axe._cache.set('foo', 'bar'); + } + assert.doesNotThrow(fn); + }); + + it('should not throw for non-string keys', function() { + function fn() { + axe._cache.set(1, 'bar'); + axe._cache.set({}, 'bar'); + axe._cache.set(null, 'bar'); + } + assert.doesNotThrow(fn); + }); + + it('should get an item from the cache', function() { + axe._cache.set('foo', 'bar'); + var value = axe._cache.get('foo'); + assert.equal(value, 'bar'); + }); + + it('should clear the cache', function() { + axe._cache.set('foo', 'bar'); + axe._cache.clear(); + var value = axe._cache.get('foo'); + assert.isUndefined(value); + }); +}); diff --git a/test/integration/full/plugin/plugin.html b/test/integration/full/plugin/plugin.html new file mode 100644 index 0000000000..7d8335a90f --- /dev/null +++ b/test/integration/full/plugin/plugin.html @@ -0,0 +1,27 @@ + + + + Plugin Test + + + + + + + +

Hello World

+
+ + + + diff --git a/test/integration/full/plugin/plugin.js b/test/integration/full/plugin/plugin.js new file mode 100644 index 0000000000..a0f7ac90fd --- /dev/null +++ b/test/integration/full/plugin/plugin.js @@ -0,0 +1,107 @@ +describe('plugin test', function() { + it('should register the plugin', function() { + axe.registerPlugin({ + id: 'doStuff', + run: function(id, action, options, callback) { + var frames; + var q = axe.utils.queue(); + var that = this; + frames = axe.utils.toArray(document.querySelectorAll('iframe, frame')); + + frames.forEach(function(frame) { + q.defer(function(done) { + axe.utils.sendCommandToFrame( + frame, + { + options: options, + command: 'run-doStuff', + parameter: id, + action: action + }, + function() { + done(); + } + ); + }); + }); + + if (!options.context.length) { + q.defer(function(done) { + that._registry[id][action].call( + that._registry[id], + document, + options, + done + ); + }); + } + q.then(callback); + }, + commands: [ + { + id: 'run-doStuff', + callback: function(data, callback) { + return axe.plugins.doStuff.run( + data.parameter, + data.action, + data.options, + callback + ); + } + } + ] + }); + + assert.isOk(axe.plugins.doStuff); + }); + + it('should add plugin instance', function() { + var highlight = { + id: 'highlight', + highlighter: function(node) { + node.style.backgroundColor = 'yellow'; + this._node = node; + }, + run: function(contextNode, options, done) { + var that = this; + Array.prototype.slice + .call(contextNode.querySelectorAll(options.selector)) + .forEach(function(node) { + that.highlighter(node, options); + }); + done(); + }, + cleanup: function(done) { + this._node.style.backgroundColor = ''; + done(); + } + }; + + axe.plugins.doStuff.add(highlight); + assert.equal(axe.plugins.doStuff._registry.highlight, highlight); + }); + + it('should run the plugin', function(done) { + var h1 = document.querySelector('.my-heading'); + + axe.plugins.doStuff.run( + 'highlight', + 'run', + { + selector: '.my-heading', + context: [] + }, + function() { + assert.equal(h1.style.backgroundColor, 'yellow'); + done(); + } + ); + }); + + it('should cleanup the plugin', function() { + var h1 = document.querySelector('.my-heading'); + + axe.cleanup(); + assert.equal(h1.style.backgroundColor, ''); + }); +}); diff --git a/test/testutils.js b/test/testutils.js index a2384834cf..12573c8635 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -308,8 +308,6 @@ testUtils.isIE11 = (function isIE11(navigator) { axe.testUtils = testUtils; -beforeEach(function() { - axe._cache = { - nodeMap: new WeakMap() - }; +afterEach(function() { + axe._cache.clear(); }); From bf98da6b7e7905b2f05f313401199e8ac2ead919 Mon Sep 17 00:00:00 2001 From: Honoka Hatakeyama Date: Fri, 10 May 2019 22:18:09 +0900 Subject: [PATCH 6/7] chore(i18n): Update Japanese locale (#1549) Translate new rule for avoid-inline-spacing --- locales/ja.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/locales/ja.json b/locales/ja.json index 795fa1d9ea..67b6dc44a8 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -61,6 +61,10 @@ "description": "autocomplete属性が正しく、かつフォームフィールドに対して適切であることを確認します", "help": "autocomplete属性は正しく使用しなければなりません" }, + "avoid-inline-spacing": { + "description": "style属性で指定されたテキストの間隔は、カスタムスタイルシートにより調整可能であることを確認します", + "help": "インラインのテキスト間隔設定はカスタムスタイルシートによって調整可能でなければなりません" + }, "blink": { "description": "要素が使用されていないことを確認します", "help": "要素は廃止されており、使用するべきではありません" @@ -617,6 +621,10 @@ "pass": "aria-labelledby属性が存在し、スクリーン・リーダーに認識可能な要素を参照しています", "fail": "aria-labelledby属性が存在しない、存在しない要素を参照している、または空の要素を参照しています" }, + "avoid-inline-spacing": { + "pass": "テキストの間隔に影響する '!important' のついたインラインスタイルは指定されていません", + "fail": "ほとんどのブラウザーで上書きすることはサポートされていないため、インラインスタイル {{=it.data.join(', ')}}, から '!important' を削除します" + }, "button-has-visible-text": { "pass": "要素にスクリーン・リーダーが認識可能な内部テキストが存在しています", "fail": "要素にスクリーン・リーダーが認識可能な内部テキストが存在していません" From ed15ed307e2459fd43d29d917786da55975b6241 Mon Sep 17 00:00:00 2001 From: Paul Grenier Date: Fri, 10 May 2019 11:20:41 -0400 Subject: [PATCH 7/7] feat(reporter): adds the rawEnv reporter which wraps raw and env data (#1556) --- lib/core/reporters/raw-env.js | 12 +++ test/core/reporters/raw-env.js | 142 +++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 lib/core/reporters/raw-env.js create mode 100644 test/core/reporters/raw-env.js diff --git a/lib/core/reporters/raw-env.js b/lib/core/reporters/raw-env.js new file mode 100644 index 0000000000..092b549427 --- /dev/null +++ b/lib/core/reporters/raw-env.js @@ -0,0 +1,12 @@ +/*global helpers */ +axe.addReporter('rawEnv', function(results, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + function rawCallback(raw) { + const env = helpers.getEnvironmentData(); + callback({ raw, env }); + } + axe.getReporter('raw')(results, options, rawCallback); +}); diff --git a/test/core/reporters/raw-env.js b/test/core/reporters/raw-env.js new file mode 100644 index 0000000000..9985d51595 --- /dev/null +++ b/test/core/reporters/raw-env.js @@ -0,0 +1,142 @@ +/*global helpers */ +describe('reporters - raw-env', function() { + 'use strict'; + + var fixture = document.getElementById('fixture'); + + function createDqElement() { + var node = document.createElement('div'); + fixture.appendChild(node); + return new axe.utils.DqElement(node); + } + + var mockResults; + var orig; + var rawResults; + const env = helpers.getEnvironmentData(); + + before(function() { + mockResults = [ + { + id: 'gimmeLabel', + helpUrl: 'things', + description: 'something nifty', + tags: ['tag1'], + result: 'passed', + violations: [], + passes: [ + { + result: 'passed', + any: [ + { + result: true, + data: 'minkey' + } + ], + all: [], + none: [], + node: createDqElement() + } + ] + }, + { + id: 'idkStuff', + description: 'something more nifty', + pageLevel: true, + result: 'failed', + impact: 'cats', + tags: ['tag2'], + passes: [], + violations: [ + { + result: 'failed', + all: [ + { + result: false, + data: 'pillock', + impact: 'cats' + } + ], + any: [], + none: [], + node: createDqElement(), + impact: 'cats' + } + ] + }, + { + id: 'bypass', + description: 'something even more nifty', + tags: ['tag3'], + impact: 'monkeys', + result: 'failed', + passes: [], + violations: [ + { + result: 'failed', + impact: 'monkeys', + none: [ + { + data: 'foon', + impact: 'monkeys', + result: true + } + ], + any: [], + all: [], + node: createDqElement() + } + ] + }, + { + id: 'blinky', + description: 'something awesome', + tags: ['tag4'], + violations: [], + result: 'passed', + passes: [ + { + result: 'passed', + none: [ + { + data: 'clueso', + result: true + } + ], + node: createDqElement() + } + ] + } + ]; + + axe.testUtils.fixtureSetup(); + + axe._load({}); + orig = axe._runRules; + axe._runRules = function(_, __, cb) { + cb(mockResults, function noop() {}); + }; + axe.run({ reporter: 'raw' }, function(err, results) { + if (err) { + return {}; + } + rawResults = results; + }); + }); + + after(function() { + axe._runRules = orig; + fixture.innerHTML = ''; + }); + + it('should pass raw results and env object', function(done) { + axe.run({ reporter: 'rawEnv' }, function(err, results) { + if (err) { + return done(err); + } + assert.deepEqual(results.raw, rawResults); + assert.deepEqual(results.env, env); + done(); + }); + }); +});