From c4e012f66e415d900afe3143d8e9a892af903451 Mon Sep 17 00:00:00 2001 From: Steven Lambert Date: Fri, 15 May 2020 10:21:55 -0600 Subject: [PATCH] feat(rule): add reviewOnFail option to have rule return as needs review instead of violation --- lib/core/base/rule.js | 48 +++++++++++++++++- test/core/base/rule.js | 107 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) diff --git a/lib/core/base/rule.js b/lib/core/base/rule.js index 8783f6ad21..d794ee1366 100644 --- a/lib/core/base/rule.js +++ b/lib/core/base/rule.js @@ -46,6 +46,13 @@ function Rule(spec, parentAudit) { */ this.pageLevel = typeof spec.pageLevel === 'boolean' ? spec.pageLevel : false; + /** + * Flag to force the rule to return as needs review rather than a violation if any of the checks fail. + * @type {Boolean} + */ + this.reviewOnFail = + typeof spec.reviewOnFail === 'boolean' ? spec.reviewOnFail : false; + /** * Checks that any may return true to satisfy rule * @type {Array} @@ -226,11 +233,28 @@ Rule.prototype.run = function(context, options = {}, resolve, reject) { }); checkQueue - .then(function(results) { + .then(results => { const result = getResult(results); if (result) { result.node = new DqElement(node.actualNode, options); ruleResult.nodes.push(result); + + // mark rule as incomplete rather than failure for rules with reviewOnFail + if (this.reviewOnFail) { + ['any', 'all'].forEach(type => { + result[type].forEach(checkResult => { + if (checkResult.result === false) { + checkResult.result = undefined; + } + }); + }); + + result.none.forEach(checkResult => { + if (checkResult.result === true) { + checkResult.result = undefined; + } + }); + } } resolveNode(); }) @@ -285,6 +309,23 @@ Rule.prototype.runSync = function(context, options = {}) { ? new DqElement(node.actualNode, options) : null; ruleResult.nodes.push(result); + + // mark rule as incomplete rather than failure for rules with reviewOnFail + if (this.reviewOnFail) { + ['any', 'all'].forEach(type => { + result[type].forEach(checkResult => { + if (checkResult.result === false) { + checkResult.result = undefined; + } + }); + }); + + result.none.forEach(checkResult => { + if (checkResult.result === true) { + checkResult.result = undefined; + } + }); + } } }); @@ -519,6 +560,11 @@ Rule.prototype.configure = function(spec) { typeof spec.pageLevel === 'boolean' ? spec.pageLevel : false; } + if (spec.hasOwnProperty('reviewOnFail')) { + this.reviewOnFail = + typeof spec.reviewOnFail === 'boolean' ? spec.reviewOnFail : false; + } + if (spec.hasOwnProperty('any')) { this.any = spec.any; } diff --git a/test/core/base/rule.js b/test/core/base/rule.js index bd615617e6..282255503f 100644 --- a/test/core/base/rule.js +++ b/test/core/base/rule.js @@ -677,6 +677,47 @@ describe('Rule', function() { ); }); + it('should mark checks as incomplete if reviewOnFail is set to true', function(done) { + var rule = new Rule( + { + reviewOnFail: true, + all: ['cats'], + any: ['cats'], + none: ['dogs'] + }, + { + checks: { + cats: new Check({ + id: 'cats', + evaluate: function() { + return false; + } + }), + dogs: new Check({ + id: 'dogs', + evaluate: function() { + return true; + } + }) + } + } + ); + + rule.run( + { + include: [axe.utils.getFlattenedTree(fixture)[0]] + }, + {}, + function(results) { + assert.isUndefined(results.nodes[0].all[0].result); + assert.isUndefined(results.nodes[0].any[0].result); + assert.isUndefined(results.nodes[0].none[0].result); + done(); + }, + isNotCalled + ); + }); + describe('NODE rule', function() { it('should create a RuleResult', function() { var orig = window.RuleResult; @@ -1305,6 +1346,44 @@ describe('Rule', function() { } }); + it('should mark checks as incomplete if reviewOnFail is set to true', function() { + var rule = new Rule( + { + reviewOnFail: true, + all: ['cats'], + any: ['cats'], + none: ['dogs'] + }, + { + checks: { + cats: new Check({ + id: 'cats', + evaluate: function() { + return false; + } + }), + dogs: new Check({ + id: 'dogs', + evaluate: function() { + return true; + } + }) + } + } + ); + + var results = rule.runSync( + { + include: [axe.utils.getFlattenedTree(fixture)[0]] + }, + {} + ); + + assert.isUndefined(results.nodes[0].all[0].result); + assert.isUndefined(results.nodes[0].any[0].result); + assert.isUndefined(results.nodes[0].none[0].result); + }); + describe.skip('NODE rule', function() { it('should create a RuleResult', function() { var orig = window.RuleResult; @@ -1615,6 +1694,27 @@ describe('Rule', function() { }); }); + describe('.reviewOnFail', function() { + it('should be set', function() { + var spec = { + reviewOnFail: true + }; + assert.equal(new Rule(spec).reviewOnFail, spec.reviewOnFail); + }); + + it('should default to false', function() { + var spec = {}; + assert.isFalse(new Rule(spec).reviewOnFail); + }); + + it('should default to false if given a bad value', function() { + var spec = { + reviewOnFail: 'monkeys' + }; + assert.isFalse(new Rule(spec).reviewOnFail); + }); + }); + describe('.id', function() { it('should be set', function() { var spec = { @@ -1775,6 +1875,13 @@ describe('Rule', function() { rule.configure({ pageLevel: true }); assert.equal(rule._get('pageLevel'), true); }); + it('should override reviewOnFail', function() { + var rule = new Rule({ reviewOnFail: false }); + + assert.equal(rule._get('reviewOnFail'), false); + rule.configure({ reviewOnFail: true }); + assert.equal(rule._get('reviewOnFail'), true); + }); it('should override any', function() { var rule = new Rule({ any: ['one', 'two'] });