Skip to content

Commit

Permalink
feat(rule): add color-contrast check for unicode characters.
Browse files Browse the repository at this point in the history
Adding two new config flags to color-contrast so it can check unicode character based icons.

ignoreUnicode, defaults to true and retains the behavior of ignoring all unicode characters when doing color contrast. This can be turned off to start checking unicode characters for color contrast.

ignoreLength, defaults to false and retains the behavior that single character nodes do not contain enough information to say whether or not they have color contrast issues. This can be turned on to ignore this length check and always check if a node has color contrast issues.

Fixes issues described in dequelabs#1906.
  • Loading branch information
Kyle McNutt authored and Kyle Bastien committed Dec 5, 2019
1 parent a9506a0 commit 2e2f71d
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 9 deletions.
20 changes: 16 additions & 4 deletions lib/checks/color/color-contrast.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ if (!dom.isVisible(node, false)) {
return true;
}

const visibleText = text.visibleVirtual(virtualNode, false, true);
const ignoreUnicode = !!(options || {}).ignoreUnicode;
const textContainsOnlyUnicode = !text.removeUnicode(visibleText, {
emoji: false,
nonBmp: true,
punctuations: false
}).length;

if (textContainsOnlyUnicode && ignoreUnicode) {
return true;
}

const noScroll = !!(options || {}).noScroll;
const bgNodes = [];
const bgColor = color.getBackgroundColor(node, bgNodes, noScroll);
Expand All @@ -28,11 +40,11 @@ if (bgColor === null) {
}

const equalRatio = truncatedResult === 1;
const shortTextContent =
text.visibleVirtual(virtualNode, false, true).length === 1;
const shortTextContent = visibleText.length === 1;
const ignoreLength = !!(options || {}).ignoreLength;
if (equalRatio) {
missing = color.incompleteData.set('bgColor', 'equalRatio');
} else if (shortTextContent) {
} else if (shortTextContent && !ignoreLength) {
// Check that the text content is a single character long
missing = 'shortTextContent';
}
Expand All @@ -55,7 +67,7 @@ if (
fgColor === null ||
bgColor === null ||
equalRatio ||
(shortTextContent && !cr.isValid)
(shortTextContent && !ignoreLength && !cr.isValid)
) {
missing = null;
color.incompleteData.clear();
Expand Down
5 changes: 5 additions & 0 deletions lib/checks/color/color-contrast.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"evaluate": "color-contrast.js",
"metadata": {
"impact": "serious",
"options": {
"noScroll": false,
"ignoreUnicode": true,
"ignoreLength": false
},
"messages": {
"pass": "Element has sufficient color contrast of {{=it.data.contrastRatio}}",
"fail": "Element has insufficient color contrast of {{=it.data.contrastRatio}} (foreground color: {{=it.data.fgColor}}, background color: {{=it.data.bgColor}}, font size: {{=it.data.fontSize}}, font weight: {{=it.data.fontWeight}}). Expected contrast ratio of {{=it.data.expectedContrastRatio}}",
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/color-contrast-matches.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ if (
visibleText === '' ||
axe.commons.text.removeUnicode(visibleText, {
emoji: true,
nonBmp: true,
nonBmp: false,
punctuations: true
}) === ''
) {
Expand Down
4 changes: 3 additions & 1 deletion lib/rules/color-contrast.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"matches": "color-contrast-matches.js",
"excludeHidden": false,
"options": {
"noScroll": false
"noScroll": false,
"ignoreUnicode": true,
"ignoreLength": false
},
"tags": ["cat.color", "wcag2aa", "wcag143"],
"metadata": {
Expand Down
57 changes: 56 additions & 1 deletion test/checks/color/color-contrast.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ describe('color-contrast', function() {
assert.equal(checkContext._data.missingData, 'shortTextContent');
});

it('should return true for a single character text with insufficient contrast', function() {
it('should return true for a single character text with sufficient contrast', function() {
var params = checkSetup(
'<div style="background-color: #FFF;">' +
'<div style="color:#000;" id="target">X</div>' +
Expand All @@ -335,6 +335,61 @@ describe('color-contrast', function() {
assert.isTrue(actual);
});

it('should return true when the text only contains nonBmp unicode by default', function() {
var params = checkSetup(
'<div style="background-color: #FFF;">' +
'<div style="color:#000;" id="target">◓</div>' +
'</div>'
);

var actual = contrastEvaluate.apply(checkContext, params);
assert.isTrue(actual);
});

it('should return true when the text only contains nonBmp unicode when the ignoreUnicode option is false, and there is sufficient contrast', function() {
var params = checkSetup(
'<div style="background-color: #FFF;">' +
'<div style="color:#000;" id="target">◓</div>' +
'</div>',
{
ignoreUnicode: false
}
);

var actual = contrastEvaluate.apply(checkContext, params);
assert.isTrue(actual);
});

it('should return undefined when the text only contains nonBmp unicode when the ignoreUnicode option is false and the ignoreLength option is default, and there is insufficient contrast', function() {
var params = checkSetup(
'<div style="background-color: #FFF;">' +
'<div style="color:#DDD;" id="target">◓</div>' +
'</div>',
{
ignoreUnicode: false
}
);

var actual = contrastEvaluate.apply(checkContext, params);
assert.isUndefined(actual);
assert.equal(checkContext._data.missingData, 'shortTextContent');
});

it('should return false when the text only contains nonBmp unicode when the ignoreUnicode option is false and the ignoreLength option is true, and there is insufficient contrast', function() {
var params = checkSetup(
'<div style="background-color: #FFF;">' +
'<div style="color:#DDD;" id="target">◓</div>' +
'</div>',
{
ignoreUnicode: false,
ignoreLength: true
}
);

var actual = contrastEvaluate.apply(checkContext, params);
assert.isFalse(actual);
});

(shadowSupported ? it : xit)(
'returns colors across Shadow DOM boundaries',
function() {
Expand Down
4 changes: 2 additions & 2 deletions test/rule-matches/color-contrast-matches.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ describe('color-contrast-matches', function() {
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(target)));
});

it('should not match when text only contains nonBmp unicode', function() {
it('should match when text only contains nonBmp unicode', function() {
fixture.innerHTML =
'<div style="color: yellow; background-color: white;" id="target">' +
'◓</div>';
var target = fixture.querySelector('#target');
axe.testUtils.flatTreeSetup(fixture);
assert.isFalse(rule.matches(target, axe.utils.getNodeFromTree(target)));
assert.isTrue(rule.matches(target, axe.utils.getNodeFromTree(target)));
});

it('should not match when there is text that is out of the container', function() {
Expand Down

0 comments on commit 2e2f71d

Please sign in to comment.