Skip to content

Commit

Permalink
feat(rule): add color-contrast check for unicode characters, behind f…
Browse files Browse the repository at this point in the history
…lags.

Adding two new config flags to color-contrast so it can check unicode character. Sometimes ligature based icons utilize unicode characters as stand-in's, so these flags become useful to be able to do color-contrast checks on these 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.

Resolves discussion in dequelabs#1906
  • Loading branch information
Kyle Bastien committed Jan 7, 2020
1 parent c3049ab commit 187613e
Show file tree
Hide file tree
Showing 6 changed files with 85 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 ${data.contrastRatio}",
"fail": "Element has insufficient color contrast of ${data.contrastRatio} (foreground color: ${data.fgColor}, background color: ${data.bgColor}, font size: ${data.fontSize}, font weight: ${data.fontWeight}). Expected contrast ratio of ${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
59 changes: 58 additions & 1 deletion test/checks/color/color-contrast.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,14 +327,71 @@ describe('color-contrast', function() {
it('should return true for a single character text with insufficient contrast', function() {
var params = checkSetup(
'<div style="background-color: #FFF;">' +
'<div style="color:#000;" id="target">X</div>' +
'<div style="color:#DDD;" id="target">X</div>' +
'</div>'
);

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

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

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

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.messageKey, '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 187613e

Please sign in to comment.