diff --git a/lib/checks/color/color-contrast.js b/lib/checks/color/color-contrast.js
index c2144a2b9a..e68a1bbe26 100644
--- a/lib/checks/color/color-contrast.js
+++ b/lib/checks/color/color-contrast.js
@@ -1,42 +1,44 @@
-if (!axe.commons.dom.isVisible(node, false)) {
+const { dom, color, text } = axe.commons;
+
+if (!dom.isVisible(node, false)) {
return true;
}
-var noScroll = !!(options || {}).noScroll;
-var bgNodes = [],
- bgColor = axe.commons.color.getBackgroundColor(node, bgNodes, noScroll),
- fgColor = axe.commons.color.getForegroundColor(node, noScroll);
+const noScroll = !!(options || {}).noScroll;
+const bgNodes = [];
+const bgColor = color.getBackgroundColor(node, bgNodes, noScroll);
+const fgColor = color.getForegroundColor(node, noScroll);
-var nodeStyle = window.getComputedStyle(node);
-var fontSize = parseFloat(nodeStyle.getPropertyValue('font-size'));
-var fontWeight = nodeStyle.getPropertyValue('font-weight');
-var bold =
+const nodeStyle = window.getComputedStyle(node);
+const fontSize = parseFloat(nodeStyle.getPropertyValue('font-size'));
+const fontWeight = nodeStyle.getPropertyValue('font-weight');
+const bold =
['bold', 'bolder', '600', '700', '800', '900'].indexOf(fontWeight) !== -1;
-var cr = axe.commons.color.hasValidContrastRatio(
- bgColor,
- fgColor,
- fontSize,
- bold
-);
+const cr = color.hasValidContrastRatio(bgColor, fgColor, fontSize, bold);
// truncate ratio to three digits while rounding down
// 4.499 = 4.49, 4.019 = 4.01
-var truncatedResult = Math.floor(cr.contrastRatio * 100) / 100;
+const truncatedResult = Math.floor(cr.contrastRatio * 100) / 100;
// if fgColor or bgColor are missing, get more information.
-var missing;
+let missing;
if (bgColor === null) {
- missing = axe.commons.color.incompleteData.get('bgColor');
+ missing = color.incompleteData.get('bgColor');
}
-let equalRatio = false;
-if (truncatedResult === 1) {
- equalRatio = true;
- missing = axe.commons.color.incompleteData.set('bgColor', 'equalRatio');
+const equalRatio = truncatedResult === 1;
+const shortTextContent =
+ text.visibleVirtual(virtualNode, false, true).length === 1;
+if (equalRatio) {
+ missing = color.incompleteData.set('bgColor', 'equalRatio');
+} else if (shortTextContent) {
+ // Check that the text content is a single character long
+ missing = 'shortTextContent';
}
+
// need both independently in case both are missing
-var data = {
+const data = {
fgColor: fgColor ? fgColor.toHexString() : undefined,
bgColor: bgColor ? bgColor.toHexString() : undefined,
contrastRatio: cr ? truncatedResult : undefined,
@@ -48,13 +50,21 @@ var data = {
this.data(data);
-//We don't know, so we'll put it into Can't Tell
-if (fgColor === null || bgColor === null || equalRatio) {
+// We don't know, so we'll put it into Can't Tell
+if (
+ fgColor === null ||
+ bgColor === null ||
+ equalRatio ||
+ (shortTextContent && !cr.isValid)
+) {
missing = null;
- axe.commons.color.incompleteData.clear();
+ color.incompleteData.clear();
this.relatedNodes(bgNodes);
return undefined;
-} else if (!cr.isValid) {
+}
+
+if (!cr.isValid) {
this.relatedNodes(bgNodes);
}
+
return cr.isValid;
diff --git a/lib/checks/color/color-contrast.json b/lib/checks/color/color-contrast.json
index 7787be990f..234bee2593 100644
--- a/lib/checks/color/color-contrast.json
+++ b/lib/checks/color/color-contrast.json
@@ -16,6 +16,7 @@
"elmPartiallyObscuring": "Element's background color could not be determined because it partially overlaps other elements",
"outsideViewport": "Element's background color could not be determined because it's outside the viewport",
"equalRatio": "Element has a 1:1 contrast ratio with the background",
+ "shortTextContent": "Element content is too short to determine if it is actual text content",
"default": "Unable to determine contrast ratio"
}
}
diff --git a/test/checks/color/color-contrast.js b/test/checks/color/color-contrast.js
index 542f2ef3bb..e9bde325a2 100644
--- a/test/checks/color/color-contrast.js
+++ b/test/checks/color/color-contrast.js
@@ -3,9 +3,11 @@ describe('color-contrast', function() {
var fixture = document.getElementById('fixture');
var fixtureSetup = axe.testUtils.fixtureSetup;
+ var checkSetup = axe.testUtils.checkSetup;
var shadowSupported = axe.testUtils.shadowSupport.v1;
var shadowCheckSetup = axe.testUtils.shadowCheckSetup;
var checkContext = axe.testUtils.MockCheckContext();
+ var contrastEvaluate = checks['color-contrast'].evaluate;
afterEach(function() {
fixture.innerHTML = '';
@@ -14,14 +16,14 @@ describe('color-contrast', function() {
});
it('should return the proper values stored in data', function() {
- fixture.innerHTML =
- '
' +
- 'My text
';
- var target = fixture.querySelector('#target');
-
- assert.isTrue(checks['color-contrast'].evaluate.call(checkContext, target));
+ var params = checkSetup(
+ '
' +
+ 'My text
'
+ );
var white = new axe.commons.color.Color(255, 255, 255, 1);
var black = new axe.commons.color.Color(0, 0, 0, 1);
+
+ assert.isTrue(contrastEvaluate.apply(checkContext, params));
assert.equal(checkContext._data.bgColor, white.toHexString());
assert.equal(checkContext._data.fgColor, black.toHexString());
assert.equal(checkContext._data.contrastRatio, '21.00');
@@ -31,148 +33,138 @@ describe('color-contrast', function() {
});
it('should return true when there is sufficient contrast because of bold tag', function() {
- fixture.innerHTML =
- '
' +
- 'My text
';
- var target = fixture.querySelector('#target');
+ var params = checkSetup(
+ '
' +
+ 'My text
'
+ );
- assert.isTrue(checks['color-contrast'].evaluate.call(checkContext, target));
+ assert.isTrue(contrastEvaluate.apply(checkContext, params));
assert.deepEqual(checkContext._relatedNodes, []);
});
it('should return true when there is sufficient contrast because of font weight', function() {
- fixture.innerHTML =
+ var params = checkSetup(
'
' +
- 'My text
';
- var target = fixture.querySelector('#target');
- assert.isTrue(checks['color-contrast'].evaluate.call(checkContext, target));
+ 'My text'
+ );
+
+ assert.isTrue(contrastEvaluate.apply(checkContext, params));
assert.deepEqual(checkContext._relatedNodes, []);
});
it('should return true when there is sufficient contrast because of font size', function() {
- fixture.innerHTML =
+ var params = checkSetup(
'
' +
- 'My text
';
- var target = fixture.querySelector('#target');
- assert.isTrue(checks['color-contrast'].evaluate.call(checkContext, target));
+ 'My text'
+ );
+ assert.isTrue(contrastEvaluate.apply(checkContext, params));
assert.deepEqual(checkContext._relatedNodes, []);
});
it('should return false when there is not sufficient contrast because of font size', function() {
- fixture.innerHTML =
+ var params = checkSetup(
'
' +
- 'My text
';
- var target = fixture.querySelector('#target');
- assert.isFalse(
- checks['color-contrast'].evaluate.call(checkContext, target)
+ 'My text'
);
- assert.deepEqual(checkContext._relatedNodes, [target]);
+
+ assert.isFalse(contrastEvaluate.apply(checkContext, params));
+ assert.deepEqual(checkContext._relatedNodes, [params[0]]);
});
it('should return true when there is sufficient contrast with explicit transparency', function() {
- fixture.innerHTML =
+ var params = checkSetup(
'
' +
- 'My text
';
- var target = fixture.querySelector('#target');
+ 'My text'
+ );
- assert.isTrue(checks['color-contrast'].evaluate.call(checkContext, target));
+ assert.isTrue(contrastEvaluate.apply(checkContext, params));
assert.deepEqual(checkContext._relatedNodes, []);
});
it('should return true when there is sufficient contrast with implicit transparency', function() {
- fixture.innerHTML =
+ var params = checkSetup(
'
' +
- 'My text
';
- var target = fixture.querySelector('#target');
+ 'My text'
+ );
- assert.isTrue(checks['color-contrast'].evaluate.call(checkContext, target));
+ assert.isTrue(contrastEvaluate.apply(checkContext, params));
assert.deepEqual(checkContext._relatedNodes, []);
});
it('should return true when there is sufficient contrast', function() {
- fixture.innerHTML =
+ var params = checkSetup(
'
' +
- 'My text
';
- var target = fixture.querySelector('#target');
- assert.isTrue(checks['color-contrast'].evaluate.call(checkContext, target));
+ 'My text'
+ );
+
+ assert.isTrue(contrastEvaluate.apply(checkContext, params));
assert.deepEqual(checkContext._relatedNodes, []);
});
it('should return true for inline elements with sufficient contrast spanning multiple lines', function() {
- fixture.innerHTML =
- '
' +
+ ''
);
+ assert.isUndefined(contrastEvaluate.apply(checkContext, params));
assert.equal(
checkContext._relatedNodes[0],
document.querySelector('#background')
);
});
+ it('should return undefined for a single character text with insufficient contrast', function() {
+ var params = checkSetup(
+ '
' +
+ '
X
' +
+ '
'
+ );
+
+ var actual = contrastEvaluate.apply(checkContext, params);
+ assert.isUndefined(actual);
+ assert.equal(checkContext._data.missingData, 'shortTextContent');
+ });
+
+ it('should return true for a single character text with insufficient contrast', function() {
+ var params = checkSetup(
+ '
' +
+ '
X
' +
+ '
'
+ );
+
+ var actual = contrastEvaluate.apply(checkContext, params);
+ assert.isTrue(actual);
+ });
+
(shadowSupported ? it : xit)(
'returns colors across Shadow DOM boundaries',
function() {
@@ -341,10 +343,7 @@ describe('color-contrast', function() {
'
Text
'
);
var container = fixture.querySelector('#container');
- var result = checks['color-contrast'].evaluate.apply(
- checkContext,
- params
- );
+ var result = contrastEvaluate.apply(checkContext, params);
assert.isFalse(result);
assert.deepEqual(checkContext._relatedNodes, [container]);
}