Skip to content

Commit

Permalink
tests: check that every check has a message for every return type and…
Browse files Browse the repository at this point in the history
… messageKey (dequelabs#3181)

* tests: check that every check has a message for every return type and messageKey

* finalize

* fixes

* typo
  • Loading branch information
straker authored Oct 5, 2021
1 parent a7ec0a8 commit 6e714f4
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 21 deletions.
42 changes: 24 additions & 18 deletions lib/checks/color/color-contrast-evaluate.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ export default function colorContrastEvaluate(node, options, virtualNode) {

// if element or a parent has pseudo content then we need to mark
// as needs review
const pseudoElm = findPseudoElement(virtualNode, { ignorePseudo, pseudoSizeThreshold })
const pseudoElm = findPseudoElement(virtualNode, {
ignorePseudo,
pseudoSizeThreshold
});
if (pseudoElm) {
this.data({ messageKey: 'pseudoContent' });
this.relatedNodes(pseudoElm.actualNode);
Expand Down Expand Up @@ -103,15 +106,15 @@ export default function colorContrastEvaluate(node, options, virtualNode) {
let missing;
if (bgColor === null) {
missing = incompleteData.get('bgColor');
} else {
} else if (!isValid) {
missing = contrastContributor;
}

const equalRatio = truncatedResult === 1;
const shortTextContent = visibleText.length === 1;
if (equalRatio) {
missing = incompleteData.set('bgColor', 'equalRatio');
} else if (shortTextContent && !ignoreLength) {
} else if (!isValid && shortTextContent && !ignoreLength) {
// Check that the text content is a single character long
missing = 'shortTextContent';
}
Expand Down Expand Up @@ -148,27 +151,31 @@ export default function colorContrastEvaluate(node, options, virtualNode) {
return isValid;
}

function findPseudoElement(vNode, {
pseudoSizeThreshold = 0.25,
ignorePseudo = false
}) {
function findPseudoElement(
vNode,
{ pseudoSizeThreshold = 0.25, ignorePseudo = false }
) {
if (ignorePseudo) {
return;
}
const rect = vNode.boundingClientRect;
const minimumSize = rect.width * rect.height * pseudoSizeThreshold;
do {
const beforeSize = getPseudoElementArea(vNode.actualNode, ':before')
const afterSize = getPseudoElementArea(vNode.actualNode, ':after')
const beforeSize = getPseudoElementArea(vNode.actualNode, ':before');
const afterSize = getPseudoElementArea(vNode.actualNode, ':after');
if (beforeSize + afterSize > minimumSize) {
return vNode // Combined area of before and after exceeds the minimum size
return vNode; // Combined area of before and after exceeds the minimum size
}
} while (vNode = vNode.parent);
} while ((vNode = vNode.parent));
}

const getPseudoElementArea = memoize(function getPseudoElementArea(node, pseudo) {
const getPseudoElementArea = memoize(function getPseudoElementArea(
node,
pseudo
) {
const style = window.getComputedStyle(node, pseudo);
const matchPseudoStyle = (prop, value) => style.getPropertyValue(prop) === value;
const matchPseudoStyle = (prop, value) =>
style.getPropertyValue(prop) === value;
if (
matchPseudoStyle('content', 'none') ||
matchPseudoStyle('display', 'none') ||
Expand All @@ -190,18 +197,17 @@ const getPseudoElementArea = memoize(function getPseudoElementArea(node, pseudo)
const pseudoHeight = parseUnit(style.getPropertyValue('height'));
if (pseudoWidth.unit !== 'px' || pseudoHeight.unit !== 'px') {
// IE doesn't normalize to px. Infinity gets everything to undefined
return (pseudoWidth.value === 0 || pseudoHeight.value === 0
? 0 : Infinity
);
return pseudoWidth.value === 0 || pseudoHeight.value === 0 ? 0 : Infinity;
}
return pseudoWidth.value * pseudoHeight.value;
});

function textIsEmojis(visibleText) {
const options = { nonBmp: true };
const hasUnicodeChars = hasUnicode(visibleText, options);
const hasNonUnicodeChars = sanitize(removeUnicode(visibleText, options)) === ''
return hasUnicodeChars && hasNonUnicodeChars
const hasNonUnicodeChars =
sanitize(removeUnicode(visibleText, options)) === '';
return hasUnicodeChars && hasNonUnicodeChars;
}

function parseUnit(str) {
Expand Down
2 changes: 1 addition & 1 deletion test/checks/shared/non-empty-if-present.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('non-empty-if-present', function() {

assert.isFalse(
axe.testUtils
.getCheckEvaluate('non-empty-if-present')
.getCheckEvaluate('non-empty-if-present', { verifyMessage: false })
.call(checkContext, null, {}, vNode)
);
assert.equal(checkContext._data.messageKey, 'has-label');
Expand Down
78 changes: 76 additions & 2 deletions test/testutils.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,30 @@ if (!fixture) {
document.body.insertBefore(fixture, document.body.firstChild);
}

// determine which checks are used only in the `none` array of rules
var noneChecks = [];

function verifyIsNoneCheck(check) {
var index = noneChecks.indexOf(check);
if (index !== -1) {
noneChecks.splice(index, 1);
}
}

axe._audit.rules.forEach(function(rule) {
rule.none.forEach(function(check) {
check = check.id || check;
if (noneChecks.indexOf(check) === -1) {
noneChecks.push(check);
}
});
});

axe._audit.rules.forEach(function(rule) {
rule.any.forEach(verifyIsNoneCheck);
rule.all.forEach(verifyIsNoneCheck);
});

/**
* Create a check context for mocking/resetting data and relatedNodes in tests
*
Expand Down Expand Up @@ -408,11 +432,61 @@ testUtils.queryFixture = function queryFixture(html, query) {
* @param {String} checkId - ID of the check
* @return Function
*/
testUtils.getCheckEvaluate = function getCheckEvaluate(checkId) {
testUtils.getCheckEvaluate = function getCheckEvaluate(checkId, testOptions) {
var check = checks[checkId];
testOptions = testOptions || {};

return function evaluateWrapper(node, options, virtualNode, context) {
var opts = check.getOptions(options);
return check.evaluate.call(this, node, opts, virtualNode, context);
var result = check.evaluate.call(this, node, opts, virtualNode, context);

// ensure that every result has a corresponding message
if (testOptions.verifyMessage !== false) {
var messages = axe._audit.data.checks[checkId].messages;
var messageKey = this._data && this._data.messageKey;

// see how the check is used to know where to find the message
// e.g. a check used only in the `none` array of a rule will look at
// the messageKey of a passing result in the `fail` messages
var keyResult = result;
var isNoneCheck = noneChecks.indexOf(checkId) !== -1;
if (isNoneCheck) {
keyResult = result === true ? false : result === false ? true : result;
}

var key =
keyResult === true
? 'pass'
: keyResult === false
? 'fail'
: 'incomplete';
var noneCheckMessage = isNoneCheck
? '. Note that since this check is only used in the "none" array of all rules, the messages use the inverse of the result (e.g. a result of false uses the "pass" messages)'
: '';

assert.exists(
messages[key],
'Missing "' +
key +
'" message for check result of ' +
result +
noneCheckMessage
);
if (messageKey) {
assert.exists(
messages[key][messageKey],
'Missing ' +
key +
' message key "' +
messageKey +
'" for check result of ' +
result +
noneCheckMessage
);
}
}

return result;
};
};

Expand Down

0 comments on commit 6e714f4

Please sign in to comment.