From 44664b7ded371d97417e9ca884def0848e196018 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Tue, 22 Nov 2022 13:54:25 +0100 Subject: [PATCH 1/2] fix(context): Allow multiple strings for include/exclude --- lib/core/base/context.js | 280 +++++------------- .../{ => context}/create-frame-context.js | 2 +- lib/core/base/context/normalize-context.js | 52 ++++ lib/core/base/context/parse-selector-array.js | 77 +++++ test/core/base/context.js | 189 ++++++------ 5 files changed, 293 insertions(+), 307 deletions(-) rename lib/core/base/{ => context}/create-frame-context.js (91%) create mode 100644 lib/core/base/context/normalize-context.js create mode 100644 lib/core/base/context/parse-selector-array.js diff --git a/lib/core/base/context.js b/lib/core/base/context.js index f5000dd9c5..8de7144d88 100644 --- a/lib/core/base/context.js +++ b/lib/core/base/context.js @@ -1,7 +1,8 @@ -import createFrameContext from './create-frame-context'; +import { createFrameContext } from './context/create-frame-context'; +import { normalizeContext } from './context/normalize-context'; +import { parseSelectorArray } from './context/parse-selector-array'; import { findBy, - getNodeFromTree, getFlattenedTree, select, isNodeInContext, @@ -11,208 +12,6 @@ import { } from '../utils'; import { isVisibleToScreenReaders } from '../../commons/dom'; -/** - * Pushes a unique frame onto `frames` array, filtering any hidden iframes - * @private - * @param {Object} Context Parent context for the frame - * @param {HTMLElement} frame The frame to push onto Context - */ -function pushUniqueFrame(context, frame) { - if ( - !isVisibleToScreenReaders(frame) || - findBy(context.frames, 'node', frame) - ) { - return; - } - context.frames.push(createFrameContext(frame, context)); -} - -/** - * Check if a normalized context tests the full page - * @private - */ -function isPageContext({ include }) { - return ( - include.length === 1 && include[0].actualNode === document.documentElement - ); -} - -/** - * Unshift selectors of matching iframes - * @private - * @param {Context} context The context object to operate on and assign to - * @param {String} type The "type" of context, 'include' or 'exclude' - * @param {Array} selectorArray Array of CSS selectors, each element represents a frame; - * where the last element is the actual node - */ -function pushUniqueFrameSelector(context, type, selectorArray) { - context.frames = context.frames || []; - - const frameSelector = selectorArray.shift(); - const frames = document.querySelectorAll(frameSelector); - - Array.from(frames).forEach(frame => { - context.frames.forEach(contextFrame => { - if (contextFrame.node === frame) { - contextFrame[type].push(selectorArray); - } - }); - - if (!context.frames.find(result => result.node === frame)) { - const result = createFrameContext(frame, context); - if (selectorArray) { - result[type].push(selectorArray); - } - - context.frames.push(result); - } - }); -} - -/** - * Normalize the input of "context" so that many different methods of input are accepted - * @private - * @param {Mixed} context The configuration object passed to `Context` - * @return {Object} Normalized context spec to include both `include` and `exclude` arrays - */ -function normalizeContext(context) { - // typeof NodeList.length in PhantomJS === function - if ( - (context && typeof context === 'object') || - context instanceof window.NodeList - ) { - if (context instanceof window.Node) { - return { - include: [context], - exclude: [] - }; - } - - if ( - context.hasOwnProperty('include') || - context.hasOwnProperty('exclude') - ) { - return { - include: - context.include && +context.include.length - ? context.include - : [document], - exclude: context.exclude || [] - }; - } - - if (context.length === +context.length) { - return { - include: context, - exclude: [] - }; - } - } - - if (typeof context === 'string') { - return { - include: [context], - exclude: [] - }; - } - - return { - include: [document], - exclude: [] - }; -} - -/** - * Finds frames in context, converts selectors to Element references and pushes unique frames - * @private - * @param {Context} context The instance of Context to operate on - * @param {String} type The "type" of thing to parse, "include" or "exclude" - * @return {Array} Parsed array of matching elements - */ -function parseSelectorArray(context, type) { - var item, - result = [], - nodeList; - for (var i = 0, l = context[type].length; i < l; i++) { - item = context[type][i]; - // selector - if (typeof item === 'string') { - nodeList = Array.from(document.querySelectorAll(item)); - //eslint no-loop-func:0 - result = result.concat( - nodeList.map(node => { - return getNodeFromTree(node); - }) - ); - break; - } else if (item && item.length && !(item instanceof window.Node)) { - if (item.length > 1) { - pushUniqueFrameSelector(context, type, item); - } else { - nodeList = Array.from(document.querySelectorAll(item[0])); - //eslint no-loop-func:0 - result = result.concat( - nodeList.map(node => { - return getNodeFromTree(node); - }) - ); - } - } else if (item instanceof window.Node) { - if (item.documentElement instanceof window.Node) { - result.push(context.flatTree[0]); - } else { - result.push(getNodeFromTree(item)); - } - } - } - - // filter nulls - return result.filter(r => r); -} - -/** - * Check that the context, as well as each frame includes at least 1 element - * @private - * @param {context} context - * @return {Error} - */ -function validateContext(context) { - if (context.include.length === 0) { - if (context.frames.length === 0) { - var env = respondable.isInFrame() ? 'frame' : 'page'; - return new Error('No elements found for include in ' + env + ' Context'); - } - context.frames.forEach((frame, i) => { - if (frame.include.length === 0) { - return new Error( - 'No elements found for include in Context of frame ' + i - ); - } - }); - } -} - -/** - * For a context-like object, find its shared root node - */ -function getRootNode({ include, exclude }) { - const selectors = Array.from(include).concat(Array.from(exclude)); - // Find the first Element.ownerDocument or Document - for (var i = 0; i < selectors.length; ++i) { - var item = selectors[i]; - - if (item instanceof window.Element) { - return item.ownerDocument.documentElement; - } - - if (item instanceof window.Document) { - return item.documentElement; - } - } - - return document.documentElement; -} - /** * Holds context of includes, excludes and frames for analysis. * @@ -266,12 +65,77 @@ export default function Context(spec, flatTree) { } // Validate outside of a frame - var err = validateContext(this); - if (err instanceof Error) { - throw err; - } + validateContext(this); + if (!Array.isArray(this.include)) { this.include = Array.from(this.include); } this.include.sort(nodeSorter); // ensure that the order of the include nodes is document order } + +/** + * Pushes a unique frame onto `frames` array, filtering any hidden iframes + * @private + * @param {Object} Context Parent context for the frame + * @param {HTMLElement} frame The frame to push onto Context + */ +function pushUniqueFrame(context, frame) { + if ( + !isVisibleToScreenReaders(frame) || + findBy(context.frames, 'node', frame) + ) { + return; + } + context.frames.push(createFrameContext(frame, context)); +} + +/** + * Check if a normalized context tests the full page + * @private + */ +function isPageContext({ include }) { + return ( + include.length === 1 && include[0].actualNode === document.documentElement + ); +} + +/** + * Check that the context, as well as each frame includes at least 1 element + * @private + * @param {context} context + * @return {Error} + */ +function validateContext(context) { + if (context.include.length !== 0) { + return; + } + if (context.frames.length === 0) { + const env = respondable.isInFrame() ? 'frame' : 'page'; + throw new Error('No elements found for include in ' + env + ' Context'); + } + + context.frames.forEach((frame, i) => { + if (frame.include.length === 0) { + throw new Error('No elements found for include in Context of frame ' + i); + } + }); +} + +/** + * For a context-like object, find its shared root node + */ +function getRootNode({ include, exclude }) { + const selectors = Array.from(include).concat(Array.from(exclude)); + // Find the first Element.ownerDocument or Document + for (let i = 0; i < selectors.length; i++) { + const item = selectors[i]; + if (item instanceof window.Element) { + return item.ownerDocument.documentElement; + } + + if (item instanceof window.Document) { + return item.documentElement; + } + } + return document.documentElement; +} diff --git a/lib/core/base/create-frame-context.js b/lib/core/base/context/create-frame-context.js similarity index 91% rename from lib/core/base/create-frame-context.js rename to lib/core/base/context/create-frame-context.js index 67c10baabf..cbe52eb3aa 100644 --- a/lib/core/base/create-frame-context.js +++ b/lib/core/base/context/create-frame-context.js @@ -1,4 +1,4 @@ -export default function createFrameContext(frame, { focusable, page }) { +export function createFrameContext(frame, { focusable, page }) { return { node: frame, include: [], diff --git a/lib/core/base/context/normalize-context.js b/lib/core/base/context/normalize-context.js new file mode 100644 index 0000000000..41461f0e56 --- /dev/null +++ b/lib/core/base/context/normalize-context.js @@ -0,0 +1,52 @@ +/** + * Normalize the input of "context" so that many different methods of input are accepted + * @private + * @param {Mixed} context The configuration object passed to `Context` + * @return {Object} Normalized context spec to include both `include` and `exclude` arrays + */ +export function normalizeContext(context) { + // typeof NodeList.length in PhantomJS === function + if ( + (context && typeof context === 'object') || + context instanceof window.NodeList + ) { + if (context instanceof window.Node) { + return { + include: [context], + exclude: [] + }; + } + + if ( + context.hasOwnProperty('include') || + context.hasOwnProperty('exclude') + ) { + return { + include: + context.include && +context.include.length + ? context.include + : [document], + exclude: context.exclude || [] + }; + } + + if (context.length === +context.length) { + return { + include: context, + exclude: [] + }; + } + } + + if (typeof context === 'string') { + return { + include: [context], + exclude: [] + }; + } + + return { + include: [document], + exclude: [] + }; +} diff --git a/lib/core/base/context/parse-selector-array.js b/lib/core/base/context/parse-selector-array.js new file mode 100644 index 0000000000..cc50c5d558 --- /dev/null +++ b/lib/core/base/context/parse-selector-array.js @@ -0,0 +1,77 @@ +import { createFrameContext } from './create-frame-context'; +import { getNodeFromTree } from '../../utils'; + +/** + * Finds frames in context, converts selectors to Element references and pushes unique frames + * @private + * @param {Context} context The instance of Context to operate on + * @param {String} type The "type" of thing to parse, "include" or "exclude" + * @return {Array} Parsed array of matching elements + */ +export function parseSelectorArray(context, type) { + const result = []; + for (let i = 0, l = context[type].length; i < l; i++) { + const item = context[type][i]; + // selector + if (typeof item === 'string') { + const nodeList = Array.from(document.querySelectorAll(item)); + result.push(...nodeList.map(node => getNodeFromTree(node))); + continue; + } + + // Handle nodes + if (item instanceof window.Node) { + if (item.documentElement instanceof window.Node) { + result.push(context.flatTree[0]); + } else { + result.push(getNodeFromTree(item)); + } + continue; + } + + // Handle Iframe selection + if (item && item.length) { + if (item.length > 1) { + pushUniqueFrameSelector(context, type, item); + } else { + const nodeList = Array.from(document.querySelectorAll(item[0])); + result.push(...nodeList.map(node => getNodeFromTree(node))); + } + } + } + + // filter nulls + return result.filter(r => r); +} + +/** + * Unshift selectors of matching iframes + * @private + * @param {Context} context The context object to operate on and assign to + * @param {String} type The "type" of context, 'include' or 'exclude' + * @param {Array} selectorArray Array of CSS selectors, each element represents a frame; + * where the last element is the actual node + */ +function pushUniqueFrameSelector(context, type, selectorArray) { + context.frames = context.frames || []; + + const frameSelector = selectorArray.shift(); + const frames = document.querySelectorAll(frameSelector); + + Array.from(frames).forEach(frame => { + context.frames.forEach(contextFrame => { + if (contextFrame.node === frame) { + contextFrame[type].push(selectorArray); + } + }); + + if (!context.frames.find(result => result.node === frame)) { + const result = createFrameContext(frame, context); + if (selectorArray) { + result[type].push(selectorArray); + } + + context.frames.push(result); + } + }); +} diff --git a/test/core/base/context.js b/test/core/base/context.js index 8daa625af8..23bd219cf5 100644 --- a/test/core/base/context.js +++ b/test/core/base/context.js @@ -1,43 +1,36 @@ /*eslint no-unused-vars:0*/ -describe('Context', function () { - 'use strict'; - - var Context = axe._thisWillBeDeletedDoNotUse.base.Context; +describe('Context', () => { + const { Context } = axe._thisWillBeDeletedDoNotUse.base; + const fixture = document.getElementById('fixture'); function $id(id) { return document.getElementById(id); } - var fixture = document.getElementById('fixture'); - - afterEach(function () { - fixture.innerHTML = ''; - }); - - it('should not mutate exclude in input', function () { + it('should not mutate exclude in input', () => { fixture.innerHTML = '
'; - var context = { exclude: [['iframe', '#foo']] }; + const context = { exclude: [['iframe', '#foo']] }; // eslint-disable-next-line no-new new Context(context); assert.deepEqual(context, { exclude: [['iframe', '#foo']] }); }); - it('should not mutate its include input', function () { + it('should not mutate its include input', () => { fixture.innerHTML = '
'; - var context = { include: [['#foo']] }; + const context = { include: [['#foo']] }; // eslint-disable-next-line no-new new Context(context); assert.deepEqual(context, { include: [['#foo']] }); }); - it('should not share memory with complex object', function () { + it('should not share memory with complex object', () => { fixture.innerHTML = '
Click me
'; - var spec = { + const spec = { include: [['#foo'], ['a']], exclude: [['iframe', '#foo2']], size: { width: 100, height: 100 } }; - var context = new Context(spec); + const context = new Context(spec); assert.notStrictEqual(spec.include, context.include); spec.include.forEach(function (_, index) { assert.notStrictEqual(spec.include[index], context.include[index]); @@ -49,24 +42,24 @@ describe('Context', function () { assert.notStrictEqual(spec.size, context.size); }); - it('should not share memory with simple array', function () { + it('should not share memory with simple array', () => { fixture.innerHTML = '
'; - var spec = ['#foo']; - var context = new Context(spec); + const spec = ['#foo']; + const context = new Context(spec); assert.notStrictEqual(spec, context.include); }); - describe('include', function () { - it('should accept a single selector', function () { + describe('include', () => { + it('should accept a single selector', () => { fixture.innerHTML = '
'; - var result = new Context('#foo'); + const result = new Context('#foo'); assert.deepEqual([result.include[0].actualNode], [$id('foo')]); }); - it('should accept multiple selectors', function () { + it('should accept multiple selectors', () => { fixture.innerHTML = '
'; - var result = new Context([['#foo'], ['#bar']]); + const result = new Context(['#foo', '#bar']); assert.deepEqual( [result.include[0].actualNode, result.include[1].actualNode], @@ -74,43 +67,43 @@ describe('Context', function () { ); }); - it('should accept a node reference', function () { - var div = document.createElement('div'); + it('should accept a node reference', () => { + const div = document.createElement('div'); fixture.appendChild(div); - var result = new Context(div); + const result = new Context(div); assert.deepEqual([result.include[0].actualNode], [div]); }); - it('should accept a node reference consisting of nested divs', function () { - var div1 = document.createElement('div'); - var div2 = document.createElement('div'); + it('should accept a node reference consisting of nested divs', () => { + const div1 = document.createElement('div'); + const div2 = document.createElement('div'); div1.appendChild(div2); fixture.appendChild(div1); - var result = new Context(div1); + const result = new Context(div1); assert.deepEqual([result.include[0].actualNode], [div1]); }); - it('should accept a node reference consisting of a form with nested controls', function () { - var form = document.createElement('form'); - var input = document.createElement('input'); + it('should accept a node reference consisting of a form with nested controls', () => { + const form = document.createElement('form'); + const input = document.createElement('input'); form.appendChild(input); fixture.appendChild(form); - var result = new Context(form); + const result = new Context(form); assert.deepEqual([result.include[0].actualNode], [form]); }); - it('should accept an array of node references', function () { + it('should accept an array of node references', () => { fixture.innerHTML = '
'; - var result = new Context([$id('foo'), $id('bar')]); + const result = new Context([$id('foo'), $id('bar')]); assert.deepEqual( [result.include[0].actualNode, result.include[1].actualNode], @@ -118,10 +111,10 @@ describe('Context', function () { ); }); - it('should remove any non-matched reference', function () { + it('should remove any non-matched reference', () => { fixture.innerHTML = '
'; - var result = new Context([['#foo'], ['#baz'], ['#bar']]); + const result = new Context([['#foo'], ['#baz'], ['#bar']]); assert.deepEqual( result.include.map(function (n) { @@ -131,11 +124,11 @@ describe('Context', function () { ); }); - it('should sort the include nodes in document order', function () { + it('should sort the include nodes in document order', () => { fixture.innerHTML = '
'; - var result = new Context([['#foo'], ['#baz'], ['#bar']]); + const result = new Context([['#foo'], ['#baz'], ['#bar']]); assert.deepEqual( result.include.map(function (n) { @@ -145,10 +138,10 @@ describe('Context', function () { ); }); - it('should remove any null reference', function () { + it('should remove any null reference', () => { fixture.innerHTML = '
'; - var result = new Context([$id('foo'), $id('bar'), null]); + const result = new Context([$id('foo'), $id('bar'), null]); assert.deepEqual( result.include.map(function (n) { @@ -158,13 +151,13 @@ describe('Context', function () { ); }); - it('should accept mixed', function () { + it('should accept mixed', () => { fixture.innerHTML = '
'; - var div = document.createElement('div'); + const div = document.createElement('div'); div.id = 'baz'; fixture.appendChild(div); - var result = new Context([['#foo'], ['#bar'], div]); + const result = new Context([['#foo'], ['#bar'], div]); assert.deepEqual( result.include.map(function (n) { @@ -174,17 +167,17 @@ describe('Context', function () { ); }); - it('should support jQuery-like objects', function () { + it('should support jQuery-like objects', () => { fixture.innerHTML = '
'; - var $test = { + const $test = { 0: $id('foo'), 1: $id('bar'), 2: $id('baz'), length: 3 }; - var result = new Context($test); + const result = new Context($test); assert.deepEqual( result.include.map(function (n) { @@ -194,36 +187,36 @@ describe('Context', function () { ); }); - describe('throwing errors', function () { - var isInFrame; + describe('throwing errors', () => { + let isInFrame; - beforeEach(function () { + beforeEach(() => { isInFrame = axe.utils.respondable.isInFrame; }); - afterEach(function () { + afterEach(() => { axe.utils.respondable.isInFrame = isInFrame; }); - it('should throw when no elements match the context', function () { + it('should throw when no elements match the context', () => { fixture.innerHTML = '
'; assert.throws( - function () { - var ctxt = new Context('#notAnElement'); + () => { + const ctxt = new Context('#notAnElement'); }, Error, 'No elements found for include in page Context' ); }); - it.skip('should throw when no elements match the context inside a frame', function () { - axe.utils.respondable.isInFrame = function () { + it.skip('should throw when no elements match the context inside a frame', () => { + axe.utils.respondable.isInFrame = () => { return true; }; fixture.innerHTML = '
'; assert.throws( - function () { - var ctxt = new Context('#notAnElement'); + () => { + const ctxt = new Context('#notAnElement'); }, Error, 'No elements found for include in frame Context' @@ -231,8 +224,8 @@ describe('Context', function () { }); }); - it('should create a flatTree property', function () { - var context = new Context({ include: [document] }); + it('should create a flatTree property', () => { + const context = new Context({ include: [document] }); assert.isArray(context.flatTree); assert.isAtLeast(context.flatTree.length, 1); }); @@ -240,7 +233,7 @@ describe('Context', function () { describe('object definition', function () { it('should assign include/exclude', function () { - var context = new Context({ + const context = new Context({ include: ['#fixture'], exclude: ['#mocha'] }); @@ -260,8 +253,8 @@ describe('Context', function () { }); it('should disregard bad input, non-matching selectors', function () { - var flatTree = axe.utils.getFlattenedTree(document); - var context = new Context({ + const flatTree = axe.utils.getFlattenedTree(document); + const context = new Context({ include: ['#fixture', '#monkeys'], exclude: ['#bananas'] }); @@ -284,7 +277,7 @@ describe('Context', function () { }); it('should disregard bad input (null)', function () { - var result = new Context(); + const result = new Context(); assert.lengthOf(result.include, 1); assert.equal(result.include[0].actualNode, document.documentElement); @@ -298,7 +291,7 @@ describe('Context', function () { }); it('should default include to document', function () { - var result = new Context({ exclude: ['#fixture'] }); + const result = new Context({ exclude: ['#fixture'] }); assert.lengthOf(result.include, 1); assert.equal(result.include[0].actualNode, document.documentElement); @@ -312,7 +305,7 @@ describe('Context', function () { }); it('should default empty include to document', function () { - var result = new Context({ include: [], exclude: [] }); + const result = new Context({ include: [], exclude: [] }); assert.lengthOf(result.include, 1); assert.equal(result.include[0].actualNode, document.documentElement); }); @@ -320,7 +313,7 @@ describe('Context', function () { describe('initiator', function () { it('should not be clobbered', function () { - var result = new Context({ + const result = new Context({ initiator: false }); assert.lengthOf(result.include, 1); @@ -336,10 +329,10 @@ describe('Context', function () { // document.hasOwnProperty is undefined in Firefox content scripts it('should not throw given really weird circumstances when hasOwnProperty is deleted from a document node?', function () { - var spec = document.implementation.createHTMLDocument('ie is dumb'); + const spec = document.implementation.createHTMLDocument('ie is dumb'); spec.hasOwnProperty = undefined; - var result = new Context(spec); + const result = new Context(spec); assert.lengthOf(result.include, 1); assert.equal(result.include[0].actualNode, spec.documentElement); @@ -387,19 +380,19 @@ describe('Context', function () { describe('focusable', function () { it('should default to true', function () { - var result = new Context(); + const result = new Context(); assert.isTrue(result.focusable); }); it('should use passed in value', function () { - var result = new Context({ + const result = new Context({ focusable: false }); assert.isFalse(result.focusable); }); it('should reject bad values', function () { - var result = new Context({ + const result = new Context({ focusable: 'hello' }); assert.isTrue(result.focusable); @@ -408,12 +401,12 @@ describe('Context', function () { describe('size', function () { it('should default to empty object', function () { - var result = new Context(); + const result = new Context(); assert.deepEqual(result.size, {}); }); it('should use passed in value', function () { - var result = new Context({ + const result = new Context({ size: { width: 10, height: 20 @@ -426,7 +419,7 @@ describe('Context', function () { }); it('should reject bad values', function () { - var result = new Context({ + const result = new Context({ size: 'hello' }); assert.deepEqual(result.size, {}); @@ -435,7 +428,7 @@ describe('Context', function () { describe('frames', function () { function iframeReady(src, context, id, cb, done) { - var iframe = document.createElement('iframe'); + const iframe = document.createElement('iframe'); iframe.addEventListener('load', function () { try { cb(iframe); @@ -456,7 +449,7 @@ describe('Context', function () { $id('outer'), 'target', function () { - var result = new Context('#target'); + const result = new Context('#target'); assert.lengthOf(result.frames, 1); assert.deepEqual(result.frames[0].node, $id('target')); }, @@ -471,7 +464,7 @@ describe('Context', function () { $id('outer'), 'target', function () { - var result = new Context('#outer'); + const result = new Context('#outer'); assert.lengthOf(result.frames, 1); assert.deepEqual(result.frames[0].node, $id('target')); }, @@ -486,7 +479,7 @@ describe('Context', function () { $id('outer'), 'target', function () { - var result = new Context([['#target', '#foo']]); + const result = new Context([['#target', '#foo']]); assert.lengthOf(result.frames, 1); assert.deepEqual(result.frames[0].node, $id('target')); @@ -504,7 +497,7 @@ describe('Context', function () { $id('outer'), 'target', function () { - var result = new Context({ + const result = new Context({ exclude: [['#target', '#foo']] }); assert.lengthOf(result.frames, 1); @@ -523,7 +516,7 @@ describe('Context', function () { $id('fixture'), 'target', function () { - var result = new Context(); + const result = new Context(); assert.isTrue(result.initiator); assert.lengthOf(result.frames, 1); assert.isFalse(result.frames[0].initiator); @@ -539,7 +532,7 @@ describe('Context', function () { $id('fixture'), 'target', function () { - var result = new Context({ + const result = new Context({ exclude: [['#mocha']] }); assert.lengthOf(result.frames, 1); @@ -555,7 +548,7 @@ describe('Context', function () { $id('fixture'), 'target', function () { - var result = new Context({ + const result = new Context({ include: [['#fixture']] }); assert.lengthOf(result.frames, 1); @@ -574,7 +567,7 @@ describe('Context', function () { 'target', function (iframe) { iframe.tabIndex = '0'; - var result = new Context(); + const result = new Context(); assert.lengthOf(result.frames, 1); assert.isTrue(result.frames[0].focusable); }, @@ -589,7 +582,7 @@ describe('Context', function () { 'target', function (iframe) { iframe.tabIndex = '-1'; - var result = new Context('#fixture'); + const result = new Context('#fixture'); assert.lengthOf(result.frames, 1); assert.isFalse(result.frames[0].focusable); }, @@ -603,7 +596,7 @@ describe('Context', function () { $id('fixture'), 'target', function () { - var result = new Context({ + const result = new Context({ include: ['#fixture'], focusable: false }); @@ -624,8 +617,8 @@ describe('Context', function () { function (iframe) { iframe.width = '100'; iframe.height = '200'; - var result = new Context('#fixture'); - var size = result.frames[0].size; + const result = new Context('#fixture'); + const size = result.frames[0].size; assert.closeTo(size.width, 105, 10); assert.closeTo(size.height, 205, 10); }, @@ -640,8 +633,8 @@ describe('Context', function () { 'target', function (iframe) { iframe.setAttribute('style', 'width: 100px; height: 200px'); - var result = new Context('#fixture'); - var size = result.frames[0].size; + const result = new Context('#fixture'); + const size = result.frames[0].size; assert.closeTo(size.width, 105, 10); assert.closeTo(size.height, 205, 10); }, @@ -657,7 +650,7 @@ describe('Context', function () { $id('outer'), 'target', function () { - var result = new Context([ + const result = new Context([ ['#target', '#foo'], ['#target', '#bar'] ]); @@ -678,7 +671,7 @@ describe('Context', function () { $id('outer'), 'target', function () { - var result = new Context([$id('target'), $id('target')]); + const result = new Context([$id('target'), $id('target')]); assert.lengthOf(result.frames, 1); assert.deepEqual(result.frames[0].node, $id('target')); }, @@ -693,10 +686,10 @@ describe('Context', function () { $id('outer'), 'target', function () { - var frame = $id('target'); + const frame = $id('target'); frame.setAttribute('hidden', 'hidden'); - var result = new Context([$id('target')]); + const result = new Context([$id('target')]); assert.deepEqual(result.frames, []); }, done @@ -711,7 +704,7 @@ describe('Context', function () { 'target', function () { assert.throws(function () { - var ctxt = new Context(['#notAFrame', '#foo']); + const ctxt = new Context(['#notAFrame', '#foo']); }); }, done From 5afc80ecc063744f43b157b41efa342c39684886 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Wed, 23 Nov 2022 13:43:19 +0100 Subject: [PATCH 2/2] Complete refactor --- lib/core/base/context.js | 11 +-------- lib/core/base/context/normalize-context.js | 2 +- lib/core/base/context/parse-selector-array.js | 2 +- test/core/base/context.js | 24 +++++++++---------- test/core/public/finish-run.js | 14 +++++------ test/core/public/run-partial.js | 4 ++-- test/core/utils/get-frame-contexts.js | 4 ++-- 7 files changed, 26 insertions(+), 35 deletions(-) diff --git a/lib/core/base/context.js b/lib/core/base/context.js index 8de7144d88..6375bc31f4 100644 --- a/lib/core/base/context.js +++ b/lib/core/base/context.js @@ -106,19 +106,10 @@ function isPageContext({ include }) { * @return {Error} */ function validateContext(context) { - if (context.include.length !== 0) { - return; - } - if (context.frames.length === 0) { + if (context.include.length === 0 && context.frames.length === 0) { const env = respondable.isInFrame() ? 'frame' : 'page'; throw new Error('No elements found for include in ' + env + ' Context'); } - - context.frames.forEach((frame, i) => { - if (frame.include.length === 0) { - throw new Error('No elements found for include in Context of frame ' + i); - } - }); } /** diff --git a/lib/core/base/context/normalize-context.js b/lib/core/base/context/normalize-context.js index 41461f0e56..b4eaa779a9 100644 --- a/lib/core/base/context/normalize-context.js +++ b/lib/core/base/context/normalize-context.js @@ -40,7 +40,7 @@ export function normalizeContext(context) { if (typeof context === 'string') { return { - include: [context], + include: [[context]], exclude: [] }; } diff --git a/lib/core/base/context/parse-selector-array.js b/lib/core/base/context/parse-selector-array.js index cc50c5d558..1d4e183eff 100644 --- a/lib/core/base/context/parse-selector-array.js +++ b/lib/core/base/context/parse-selector-array.js @@ -16,7 +16,7 @@ export function parseSelectorArray(context, type) { if (typeof item === 'string') { const nodeList = Array.from(document.querySelectorAll(item)); result.push(...nodeList.map(node => getNodeFromTree(node))); - continue; + break; } // Handle nodes diff --git a/test/core/base/context.js b/test/core/base/context.js index 23bd219cf5..9098ba8a31 100644 --- a/test/core/base/context.js +++ b/test/core/base/context.js @@ -44,7 +44,7 @@ describe('Context', () => { it('should not share memory with simple array', () => { fixture.innerHTML = '
'; - const spec = ['#foo']; + const spec = [['#foo']]; const context = new Context(spec); assert.notStrictEqual(spec, context.include); }); @@ -59,7 +59,7 @@ describe('Context', () => { it('should accept multiple selectors', () => { fixture.innerHTML = '
'; - const result = new Context(['#foo', '#bar']); + const result = new Context([['#foo'], ['#bar']]); assert.deepEqual( [result.include[0].actualNode, result.include[1].actualNode], @@ -234,8 +234,8 @@ describe('Context', () => { describe('object definition', function () { it('should assign include/exclude', function () { const context = new Context({ - include: ['#fixture'], - exclude: ['#mocha'] + include: [['#fixture']], + exclude: [['#mocha']] }); assert.isNotNull(context); assert.hasAllKeys(context, [ @@ -255,8 +255,8 @@ describe('Context', () => { it('should disregard bad input, non-matching selectors', function () { const flatTree = axe.utils.getFlattenedTree(document); const context = new Context({ - include: ['#fixture', '#monkeys'], - exclude: ['#bananas'] + include: [['#fixture'], ['#monkeys']], + exclude: [['#bananas']] }); assert.isNotNull(context); assert.hasAllKeys(context, [ @@ -291,7 +291,7 @@ describe('Context', () => { }); it('should default include to document', function () { - const result = new Context({ exclude: ['#fixture'] }); + const result = new Context({ exclude: [['#fixture']] }); assert.lengthOf(result.include, 1); assert.equal(result.include[0].actualNode, document.documentElement); @@ -364,8 +364,8 @@ describe('Context', () => { // not that parts within that are excluded assert.isTrue( new Context({ - include: [document], - exclude: ['#mocha'] + include: document, + exclude: [['#mocha']] }).page ); }); @@ -373,8 +373,8 @@ describe('Context', () => { it('is false if the context does not include documentElement', function () { assert.isFalse(new Context(fixture).page); assert.isFalse(new Context('#fixture').page); - assert.isFalse(new Context(['#fixture']).page); - assert.isFalse(new Context({ include: ['#fixture'] }).page); + assert.isFalse(new Context([['#fixture']]).page); + assert.isFalse(new Context({ include: [['#fixture']] }).page); }); }); @@ -597,7 +597,7 @@ describe('Context', () => { 'target', function () { const result = new Context({ - include: ['#fixture'], + include: [['#fixture']], focusable: false }); assert.lengthOf(result.frames, 1); diff --git a/test/core/public/finish-run.js b/test/core/public/finish-run.js index 16efc131ea..8abe92f935 100644 --- a/test/core/public/finish-run.js +++ b/test/core/public/finish-run.js @@ -104,7 +104,7 @@ describe('axe.finishRun', function () { it('can report violations results', function (done) { fixture.innerHTML = '
'; axe - .runPartial({ include: ['#fixture'] }, { runOnly: 'aria-allowed-attr' }) + .runPartial({ include: [['#fixture']] }, { runOnly: 'aria-allowed-attr' }) .then(function (result) { return axe.finishRun([result]); }) @@ -122,7 +122,7 @@ describe('axe.finishRun', function () { fixture.innerHTML = '
'; axe - .runPartial({ include: ['#fixture'] }, { runOnly: 'aria-allowed-attr' }) + .runPartial({ include: [['#fixture']] }, { runOnly: 'aria-allowed-attr' }) .then(function (result) { return axe.finishRun([result]); }) @@ -141,7 +141,7 @@ describe('axe.finishRun', function () { axe .runPartial( - { include: ['#fixture'] }, + { include: [['#fixture']] }, { runOnly: 'aria-valid-attr-value' } ) .then(function (result) { @@ -159,7 +159,7 @@ describe('axe.finishRun', function () { it('can report inapplicable results', function (done) { axe - .runPartial({ include: ['#fixture'] }, { runOnly: 'aria-allowed-attr' }) + .runPartial({ include: [['#fixture']] }, { runOnly: 'aria-allowed-attr' }) .then(function (result) { return axe.finishRun([result]); }) @@ -181,18 +181,18 @@ describe('axe.finishRun', function () { var allResults = []; axe - .runPartial({ include: ['#pass'] }, { runOnly: 'aria-allowed-attr' }) + .runPartial({ include: [['#pass']] }, { runOnly: 'aria-allowed-attr' }) .then(function (results) { allResults.push(results); return axe.runPartial( - { include: ['#fail'] }, + { include: [['#fail']] }, { runOnly: 'aria-allowed-attr' } ); }) .then(function (results) { allResults.push(results); return axe.runPartial( - { include: ['#incomplete'] }, + { include: [['#incomplete']] }, { runOnly: 'aria-valid-attr-value' } ); }) diff --git a/test/core/public/run-partial.js b/test/core/public/run-partial.js index 29273068e5..515de33548 100644 --- a/test/core/public/run-partial.js +++ b/test/core/public/run-partial.js @@ -132,7 +132,7 @@ describe('axe.runPartial', function () { describe('environmentData', function () { it('includes environment data for the initiator', function (done) { var context = { - include: ['#fixture'] + include: [['#fixture']] }; axe .runPartial(context, { runOnly: 'image-alt' }) @@ -146,7 +146,7 @@ describe('axe.runPartial', function () { it('is undefined for frames', function (done) { var context = { - include: ['#fixture'], + include: [['#fixture']], initiator: false }; axe diff --git a/test/core/utils/get-frame-contexts.js b/test/core/utils/get-frame-contexts.js index 30f72d29e9..963e8d9b4e 100644 --- a/test/core/utils/get-frame-contexts.js +++ b/test/core/utils/get-frame-contexts.js @@ -190,13 +190,13 @@ describe('utils.getFrameContexts', function () { assert.deepEqual(frameContexts[0].frameContext.include, []); assert.deepEqual(frameContexts[0].frameContext.exclude, []); - frameContexts = getFrameContexts({ include: ['#f1'] }); + frameContexts = getFrameContexts({ include: [['#f1']] }); assert.lengthOf(frameContexts, 1); assert.include(frameContexts[0].frameSelector, 'iframe:nth-child(1)'); assert.deepEqual(frameContexts[0].frameContext.include, []); assert.deepEqual(frameContexts[0].frameContext.exclude, []); - frameContexts = getFrameContexts({ exclude: ['#f2'] }); + frameContexts = getFrameContexts({ exclude: [['#f2']] }); assert.lengthOf(frameContexts, 1); assert.include(frameContexts[0].frameSelector, 'iframe:nth-child(1)'); assert.deepEqual(frameContexts[0].frameContext.include, []);