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. (#1969)

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 #1906
  • Loading branch information
KyleBastien authored and WilcoFiers committed Jan 9, 2020
1 parent 5ec7894 commit 0cd4037
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
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
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
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
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
Expand Up @@ -319,14 +319,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
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 0cd4037

Please sign in to comment.