From 66646a89ddecb1c092a67a8cb42ec9af10e34274 Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Thu, 4 Jan 2018 16:34:11 -0800 Subject: [PATCH 1/8] chore: rename lut in tests --- test/commons/aria/roles.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/commons/aria/roles.js b/test/commons/aria/roles.js index 4b3f686dfc..a481eb73b5 100644 --- a/test/commons/aria/roles.js +++ b/test/commons/aria/roles.js @@ -12,7 +12,7 @@ describe('aria.isValidRole', function () { }); - it('should return false if role is not found in the lut', function () { + it('should return false if role is not found in the lookup table', function () { assert.isFalse(axe.commons.aria.isValidRole('cats')); }); @@ -57,7 +57,7 @@ describe('aria.getRolesByType', function () { }); - it('should return empty array if role is not found in the lut', function () { + it('should return empty array if role is not found in the lookup table', function () { assert.deepEqual(axe.commons.aria.getRolesByType('blahblahblah'), []); }); }); @@ -77,7 +77,7 @@ describe('aria.getRoleType', function () { }); - it('should return null if role is not found in the lut', function () { + it('should return null if role is not found in the lookup table', function () { assert.isNull(axe.commons.aria.getRoleType('cats')); }); }); From cf9af49bcdb03e49ea55dad0fd90c9df62b61b71 Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Thu, 4 Jan 2018 16:34:38 -0800 Subject: [PATCH 2/8] feat: allow options in aria-allowed-attr --- lib/checks/aria/allowed-attr.js | 4 +++- test/checks/aria/allowed-attr.js | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/checks/aria/allowed-attr.js b/lib/checks/aria/allowed-attr.js index 3a9f614610..c33c38a8ea 100644 --- a/lib/checks/aria/allowed-attr.js +++ b/lib/checks/aria/allowed-attr.js @@ -1,3 +1,5 @@ +options = Array.isArray(options) ? options : []; + var invalid = []; var attr, attrName, allowed, @@ -12,7 +14,7 @@ if (role && allowed) { for (var i = 0, l = attrs.length; i < l; i++) { attr = attrs[i]; attrName = attr.name; - if (axe.commons.aria.validateAttr(attrName) && allowed.indexOf(attrName) === -1) { + if (options.indexOf(attrName) === -1 && axe.commons.aria.validateAttr(attrName) && allowed.indexOf(attrName) === -1) { invalid.push(attrName + '="' + attr.nodeValue + '"'); } } diff --git a/test/checks/aria/allowed-attr.js b/test/checks/aria/allowed-attr.js index 7ff80fd54e..dfffec63cb 100644 --- a/test/checks/aria/allowed-attr.js +++ b/test/checks/aria/allowed-attr.js @@ -119,4 +119,11 @@ describe('aria-allowed-attr', function () { assert.isNull(checkContext._data); }); + describe('options', function () { + it('should allow provided attribute names', function () { + fixture.innerHTML = ''; + var target = fixture.children[0]; + assert.isTrue(checks['aria-allowed-attr'].evaluate.call(checkContext, target, ['aria-valuenow', 'aria-valuemin', 'aria-valuemax'])); + }); + }); }); From e30a37228fa3a04e69ced0f9f18537e71e922593 Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Fri, 5 Jan 2018 16:18:39 -0800 Subject: [PATCH 3/8] chore: rename options integ. tests for clarity --- .../full/{options => options-parameter}/frames/frame.html | 0 .../options.html => options-parameter/options-parameter.html} | 2 +- .../options.js => options-parameter/options-parameter.js} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename test/integration/full/{options => options-parameter}/frames/frame.html (100%) rename test/integration/full/{options/options.html => options-parameter/options-parameter.html} (95%) rename test/integration/full/{options/options.js => options-parameter/options-parameter.js} (99%) diff --git a/test/integration/full/options/frames/frame.html b/test/integration/full/options-parameter/frames/frame.html similarity index 100% rename from test/integration/full/options/frames/frame.html rename to test/integration/full/options-parameter/frames/frame.html diff --git a/test/integration/full/options/options.html b/test/integration/full/options-parameter/options-parameter.html similarity index 95% rename from test/integration/full/options/options.html rename to test/integration/full/options-parameter/options-parameter.html index b4ff840854..b7db01a71d 100644 --- a/test/integration/full/options/options.html +++ b/test/integration/full/options-parameter/options-parameter.html @@ -38,7 +38,7 @@
- + diff --git a/test/integration/full/options/options.js b/test/integration/full/options-parameter/options-parameter.js similarity index 99% rename from test/integration/full/options/options.js rename to test/integration/full/options-parameter/options-parameter.js index 7e62278a4d..0e9a7eb632 100644 --- a/test/integration/full/options/options.js +++ b/test/integration/full/options-parameter/options-parameter.js @@ -1,4 +1,4 @@ -describe('Options', function() { +describe('Options parameter', function() { 'use strict'; before(function (done) { From 4f266c7c2e74d9931ff89d4364bb00b12b704706 Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Fri, 5 Jan 2018 16:24:13 -0800 Subject: [PATCH 4/8] feat: add required-attr options, integration tests --- lib/checks/aria/required-attr.js | 11 ++++ test/checks/aria/required-attr.js | 8 +++ .../configure-options/configure-options.html | 25 ++++++++++ .../configure-options/configure-options.js | 50 +++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 test/integration/full/configure-options/configure-options.html create mode 100644 test/integration/full/configure-options/configure-options.js diff --git a/lib/checks/aria/required-attr.js b/lib/checks/aria/required-attr.js index 23bfe0a3df..f21bb4446e 100644 --- a/lib/checks/aria/required-attr.js +++ b/lib/checks/aria/required-attr.js @@ -1,3 +1,11 @@ +options = Array.isArray(options) ? options : []; + +var uniqueArray = (arrArg) => { + return arrArg.filter((elem, pos, arr) => { + return arr.indexOf(elem) === pos; + }); +}; + var missing = []; if (node.hasAttributes()) { @@ -5,6 +13,9 @@ if (node.hasAttributes()) { role = node.getAttribute('role'), required = axe.commons.aria.requiredAttr(role); + if (options.length) { + required = uniqueArray(required.concat(options)); + } if (role && required) { for (var i = 0, l = required.length; i < l; i++) { attr = required[i]; diff --git a/test/checks/aria/required-attr.js b/test/checks/aria/required-attr.js index 8d410221da..3193aa2ce0 100644 --- a/test/checks/aria/required-attr.js +++ b/test/checks/aria/required-attr.js @@ -57,4 +57,12 @@ describe('aria-required-attr', function () { axe.commons.aria.requiredAttr = orig; }); + describe('options', function () { + it('should require provided attribute names', function () { + fixture.innerHTML = '
'; + var target = fixture.children[0]; + assert.isFalse(checks['aria-required-attr'].evaluate.call(checkContext, target, ['aria-valuemax', 'aria-bats'])); + assert.deepEqual(checkContext._data, ['aria-valuenow', 'aria-valuemax', 'aria-valuemin', 'aria-bats']); + }); + }); }); \ No newline at end of file diff --git a/test/integration/full/configure-options/configure-options.html b/test/integration/full/configure-options/configure-options.html new file mode 100644 index 0000000000..c6564d1201 --- /dev/null +++ b/test/integration/full/configure-options/configure-options.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + +
+ +
+ + + + diff --git a/test/integration/full/configure-options/configure-options.js b/test/integration/full/configure-options/configure-options.js new file mode 100644 index 0000000000..941e7ab8e5 --- /dev/null +++ b/test/integration/full/configure-options/configure-options.js @@ -0,0 +1,50 @@ +describe('Check Configure Options', function() { + 'use strict'; + + var target = document.querySelector('#target'); + + describe('aria-allowed-attr', function() { + it('should allow an attribute supplied in options', function(done) { + target.setAttribute('role', 'separator'); + target.setAttribute('aria-valuenow', '0'); + + axe.configure({ + checks: [{ + id: 'aria-allowed-attr', + options: ['aria-valuenow'] + }] + }); + axe.run(target, { + runOnly: { + type: 'rule', + values: [ 'aria-allowed-attr' ] + } + }, function(error, results) { + assert.lengthOf(results.violations, 0, 'violations'); + done(); + }); + }); + }); + + describe('aria-required-attr', function() { + it('should report unique attributes when supplied from options', function(done) { + target.setAttribute('role', 'slider'); + axe.configure({ + checks: [{ + id: 'aria-required-attr', + options: ['aria-checked'] + }] + }); + axe.run('#target', { + runOnly: { + type: 'rule', + values: [ 'aria-required-attr' ] + } + }, function(error, results) { + assert.lengthOf(results.violations, 1, 'violations'); + assert.sameMembers(results.violations[0].nodes[0].any[0].data, ['aria-valuemax', 'aria-valuemin', 'aria-checked']); + done(); + }); + }); + }); +}); From 69fba9d043798be16ecd561760ac42cfc0ffa6a7 Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Fri, 5 Jan 2018 16:38:47 -0800 Subject: [PATCH 5/8] chore: PR feedback for aria-allowed-attr --- lib/checks/aria/allowed-attr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/checks/aria/allowed-attr.js b/lib/checks/aria/allowed-attr.js index c33c38a8ea..01d3c51a50 100644 --- a/lib/checks/aria/allowed-attr.js +++ b/lib/checks/aria/allowed-attr.js @@ -14,7 +14,7 @@ if (role && allowed) { for (var i = 0, l = attrs.length; i < l; i++) { attr = attrs[i]; attrName = attr.name; - if (options.indexOf(attrName) === -1 && axe.commons.aria.validateAttr(attrName) && allowed.indexOf(attrName) === -1) { + if (!options.includes(attrName) && axe.commons.aria.validateAttr(attrName) && allowed.indexOf(attrName) === -1) { invalid.push(attrName + '="' + attr.nodeValue + '"'); } } From fd6d6711394b849f2163947d8f11c2ecebcae95a Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Wed, 10 Jan 2018 12:12:39 -0800 Subject: [PATCH 6/8] feat: use object for ARIA check options e.g. {separator: ['aria-valuenow', 'aria-valuemin', aria-valuemax']} --- lib/checks/aria/allowed-attr.js | 14 +++++- lib/checks/aria/required-attr.js | 16 +++---- lib/core/utils/to-array.js | 14 +++++- test/checks/aria/allowed-attr.js | 46 +++++++++++++++++-- test/checks/aria/required-attr.js | 21 +++++++-- test/core/utils/to-array.js | 15 +++++- .../configure-options/configure-options.js | 6 +-- 7 files changed, 109 insertions(+), 23 deletions(-) diff --git a/lib/checks/aria/allowed-attr.js b/lib/checks/aria/allowed-attr.js index 01d3c51a50..bcc8a4d5c6 100644 --- a/lib/checks/aria/allowed-attr.js +++ b/lib/checks/aria/allowed-attr.js @@ -1,4 +1,4 @@ -options = Array.isArray(options) ? options : []; +options = options || {}; var invalid = []; @@ -9,12 +9,22 @@ var attr, attrName, allowed, if (!role) { role = axe.commons.aria.implicitRole(node); } + allowed = axe.commons.aria.allowedAttr(role); + +if (Object.keys(options).length) { + for (var roleOption in options) { + if (roleOption === role) { + allowed = axe.utils.uniqueArray(options[role].concat(allowed)); + } + } +} + if (role && allowed) { for (var i = 0, l = attrs.length; i < l; i++) { attr = attrs[i]; attrName = attr.name; - if (!options.includes(attrName) && axe.commons.aria.validateAttr(attrName) && allowed.indexOf(attrName) === -1) { + if (axe.commons.aria.validateAttr(attrName) && !allowed.includes(attrName)) { invalid.push(attrName + '="' + attr.nodeValue + '"'); } } diff --git a/lib/checks/aria/required-attr.js b/lib/checks/aria/required-attr.js index f21bb4446e..832589c329 100644 --- a/lib/checks/aria/required-attr.js +++ b/lib/checks/aria/required-attr.js @@ -1,10 +1,4 @@ -options = Array.isArray(options) ? options : []; - -var uniqueArray = (arrArg) => { - return arrArg.filter((elem, pos, arr) => { - return arr.indexOf(elem) === pos; - }); -}; +options = options || {}; var missing = []; @@ -13,8 +7,12 @@ if (node.hasAttributes()) { role = node.getAttribute('role'), required = axe.commons.aria.requiredAttr(role); - if (options.length) { - required = uniqueArray(required.concat(options)); + if (Object.keys(options).length) { + for (var roleOption in options) { + if (roleOption === role) { + required = axe.utils.uniqueArray(options[role].concat(required)); + } + } } if (role && required) { for (var i = 0, l = required.length; i < l; i++) { diff --git a/lib/core/utils/to-array.js b/lib/core/utils/to-array.js index 5d04fc2adc..ecce937fd0 100644 --- a/lib/core/utils/to-array.js +++ b/lib/core/utils/to-array.js @@ -7,4 +7,16 @@ axe.utils.toArray = function (thing) { 'use strict'; return Array.prototype.slice.call(thing); -}; \ No newline at end of file +}; + + +/** + * Creates an array without duplicate values + * @param {Array} arrArg Array to filter + * @return {Array} + */ +axe.utils.uniqueArray = (arrArg) => { + return arrArg.filter((elem, pos, arr) => { + return arr.indexOf(elem) === pos; + }); +}; diff --git a/test/checks/aria/allowed-attr.js b/test/checks/aria/allowed-attr.js index dfffec63cb..5ce187f9f9 100644 --- a/test/checks/aria/allowed-attr.js +++ b/test/checks/aria/allowed-attr.js @@ -120,10 +120,50 @@ describe('aria-allowed-attr', function () { }); describe('options', function () { - it('should allow provided attribute names', function () { - fixture.innerHTML = ''; + it('should allow provided attribute names for a role', function () { + axe.commons.aria.lookupTable.role.mcheddarton = { + type: 'widget', + attributes: { + allowed: ['aria-checked'] + }, + owned: null, + nameFrom: ['author'], + context: null + }; + fixture.innerHTML = '
'; var target = fixture.children[0]; - assert.isTrue(checks['aria-allowed-attr'].evaluate.call(checkContext, target, ['aria-valuenow', 'aria-valuemin', 'aria-valuemax'])); + assert.isTrue(checks['aria-allowed-attr'].evaluate.call(checkContext, target, {'mccheddarton': ['aria-checked', 'aria-snuggles']})); + delete axe.commons.aria.lookupTable.role.mccheddarton; + }); + + it('should handle multiple roles provided in options', function () { + axe.commons.aria.lookupTable.role.mcheddarton = { + type: 'widget', + attributes: { + allowed: ['aria-checked'] + }, + owned: null, + nameFrom: ['author'], + context: null + }; + axe.commons.aria.lookupTable.role.bagley = { + type: 'widget', + attributes: { + allowed: ['aria-checked'] + }, + owned: null, + nameFrom: ['author'], + context: null + }; + fixture.innerHTML = '
'; + var target = fixture.children[0]; + var options = { + 'mccheddarton': ['aria-snuggles'], + 'bagley': ['aria-snuggles2'] + }; + assert.isTrue(checks['aria-allowed-attr'].evaluate.call(checkContext, target, options)); + delete axe.commons.aria.lookupTable.role.mccheddarton; + delete axe.commons.aria.lookupTable.role.bagley; }); }); }); diff --git a/test/checks/aria/required-attr.js b/test/checks/aria/required-attr.js index 3193aa2ce0..79ec2890e2 100644 --- a/test/checks/aria/required-attr.js +++ b/test/checks/aria/required-attr.js @@ -58,11 +58,24 @@ describe('aria-required-attr', function () { }); describe('options', function () { - it('should require provided attribute names', function () { - fixture.innerHTML = '
'; + it('should require provided attribute names for a role', function () { + axe.commons.aria.lookupTable.role.mccheddarton = { + type: 'widget', + attributes: { + required: ['aria-valuemax'] + }, + owned: null, + nameFrom: ['author'], + context: null + }; + fixture.innerHTML = '
'; var target = fixture.children[0]; - assert.isFalse(checks['aria-required-attr'].evaluate.call(checkContext, target, ['aria-valuemax', 'aria-bats'])); - assert.deepEqual(checkContext._data, ['aria-valuenow', 'aria-valuemax', 'aria-valuemin', 'aria-bats']); + var options = { + 'mccheddarton': ['aria-snuggles'] + }; + assert.isFalse(checks['aria-required-attr'].evaluate.call(checkContext, target, options)); + assert.deepEqual(checkContext._data, ['aria-snuggles', 'aria-valuemax']); + delete axe.commons.aria.lookupTable.role.mccheddarton; }); }); }); \ No newline at end of file diff --git a/test/core/utils/to-array.js b/test/core/utils/to-array.js index 315331b750..630d723bd8 100644 --- a/test/core/utils/to-array.js +++ b/test/core/utils/to-array.js @@ -1,6 +1,5 @@ describe('axe.utils.toArray', function () { 'use strict'; - it('should call Array.prototype.slice', function () { var orig = Array.prototype.slice, called = false, @@ -24,4 +23,18 @@ describe('axe.utils.toArray', function () { var result = axe.utils.toArray(arrayLike); assert.isArray(result); }); + +}); + +describe('axe.utils.uniqueArray', function () { + 'use strict'; + + it('should filter duplicate values', function () { + var array1 = [1, 2, 3, 4, 5]; + var array2 = [1, 3, 7]; + + var result = axe.utils.uniqueArray(array1.concat(array2)); + assert.isArray(result); + assert.includeMembers(result, [1, 2, 3, 4, 5, 7]); + }); }); \ No newline at end of file diff --git a/test/integration/full/configure-options/configure-options.js b/test/integration/full/configure-options/configure-options.js index 941e7ab8e5..9850846462 100644 --- a/test/integration/full/configure-options/configure-options.js +++ b/test/integration/full/configure-options/configure-options.js @@ -11,7 +11,7 @@ describe('Check Configure Options', function() { axe.configure({ checks: [{ id: 'aria-allowed-attr', - options: ['aria-valuenow'] + options: {'separator': ['aria-valuenow']} }] }); axe.run(target, { @@ -32,7 +32,7 @@ describe('Check Configure Options', function() { axe.configure({ checks: [{ id: 'aria-required-attr', - options: ['aria-checked'] + options: {slider: ['aria-snuggles']} }] }); axe.run('#target', { @@ -42,7 +42,7 @@ describe('Check 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-checked']); + assert.sameMembers(results.violations[0].nodes[0].any[0].data, ['aria-valuemax', 'aria-valuemin', 'aria-snuggles']); done(); }); }); From 86fd5bfcd4b60ff2c90740bb4596516a3bdf90d5 Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Thu, 18 Jan 2018 10:07:20 +0100 Subject: [PATCH 7/8] chore: remove duplicate to-array util The exact same code existed in axe.utils and axe.commons (as axe.utils.toArray) --- lib/commons/utils/to-array.js | 14 -------------- test/commons/utils/to-array.js | 26 -------------------------- 2 files changed, 40 deletions(-) delete mode 100644 lib/commons/utils/to-array.js delete mode 100644 test/commons/utils/to-array.js diff --git a/lib/commons/utils/to-array.js b/lib/commons/utils/to-array.js deleted file mode 100644 index 839b267706..0000000000 --- a/lib/commons/utils/to-array.js +++ /dev/null @@ -1,14 +0,0 @@ -/* global axe */ - -/** - * Converts thing to an Array - * @method toArray - * @memberof axe.commons.utils - * @instance - * @param {NodeList|HTMLCollection|String} thing - * @return {Array} - */ -axe.utils.toArray = function (thing) { - 'use strict'; - return Array.prototype.slice.call(thing); -}; diff --git a/test/commons/utils/to-array.js b/test/commons/utils/to-array.js deleted file mode 100644 index aa8dac324a..0000000000 --- a/test/commons/utils/to-array.js +++ /dev/null @@ -1,26 +0,0 @@ -describe('utils.toArray', function () { - 'use strict'; - - it('should call Array.prototype.slice', function () { - var orig = Array.prototype.slice, - called = false, - arrayLike = {'0': 'cats', length: 1}; - - Array.prototype.slice = function () { - called = true; - assert.equal(this, arrayLike); - }; - - axe.commons.utils.toArray(arrayLike); - - Array.prototype.slice = orig; - - assert.isTrue(called); - }); - it('should return an array', function () { - var arrayLike = {'0': 'cats', length: 1}; - - var result = axe.commons.utils.toArray(arrayLike); - assert.isArray(result); - }); -}); \ No newline at end of file From a51514d44b5f0231b8d79dbc3c3c1c0426687823 Mon Sep 17 00:00:00 2001 From: Marcy Sutton Date: Thu, 18 Jan 2018 10:09:55 +0100 Subject: [PATCH 8/8] chore: simplify ARIA options usage --- lib/checks/aria/allowed-attr.js | 8 ++------ lib/checks/aria/required-attr.js | 8 ++------ lib/core/utils/to-array.js | 9 +++++---- test/core/utils/to-array.js | 2 +- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/checks/aria/allowed-attr.js b/lib/checks/aria/allowed-attr.js index bcc8a4d5c6..63d889c5f3 100644 --- a/lib/checks/aria/allowed-attr.js +++ b/lib/checks/aria/allowed-attr.js @@ -12,12 +12,8 @@ if (!role) { allowed = axe.commons.aria.allowedAttr(role); -if (Object.keys(options).length) { - for (var roleOption in options) { - if (roleOption === role) { - allowed = axe.utils.uniqueArray(options[role].concat(allowed)); - } - } +if (Array.isArray(options[role])) { + allowed = axe.utils.uniqueArray(options[role].concat(allowed)); } if (role && allowed) { diff --git a/lib/checks/aria/required-attr.js b/lib/checks/aria/required-attr.js index 832589c329..74bce8d9ff 100644 --- a/lib/checks/aria/required-attr.js +++ b/lib/checks/aria/required-attr.js @@ -7,12 +7,8 @@ if (node.hasAttributes()) { role = node.getAttribute('role'), required = axe.commons.aria.requiredAttr(role); - if (Object.keys(options).length) { - for (var roleOption in options) { - if (roleOption === role) { - required = axe.utils.uniqueArray(options[role].concat(required)); - } - } + if (Array.isArray(options[role])) { + required = axe.utils.uniqueArray(options[role], required); } if (role && required) { for (var i = 0, l = required.length; i < l; i++) { diff --git a/lib/core/utils/to-array.js b/lib/core/utils/to-array.js index ecce937fd0..394972cd88 100644 --- a/lib/core/utils/to-array.js +++ b/lib/core/utils/to-array.js @@ -11,12 +11,13 @@ axe.utils.toArray = function (thing) { /** - * Creates an array without duplicate values - * @param {Array} arrArg Array to filter + * Creates an array without duplicate values from 2 array inputs + * @param {Array} arr1 First array + * @param {Array} arr2 Second array * @return {Array} */ -axe.utils.uniqueArray = (arrArg) => { - return arrArg.filter((elem, pos, arr) => { +axe.utils.uniqueArray = (arr1, arr2) => { + return arr1.concat(arr2).filter((elem, pos, arr) => { return arr.indexOf(elem) === pos; }); }; diff --git a/test/core/utils/to-array.js b/test/core/utils/to-array.js index 630d723bd8..133d432a8e 100644 --- a/test/core/utils/to-array.js +++ b/test/core/utils/to-array.js @@ -33,7 +33,7 @@ describe('axe.utils.uniqueArray', function () { var array1 = [1, 2, 3, 4, 5]; var array2 = [1, 3, 7]; - var result = axe.utils.uniqueArray(array1.concat(array2)); + var result = axe.utils.uniqueArray(array1, array2); assert.isArray(result); assert.includeMembers(result, [1, 2, 3, 4, 5, 7]); });