Skip to content

Commit 8f249fd

Browse files
authored
fix: color contrast fails for oklch and oklab with none (#4959)
Adds error handling for color-contrast checks. This now will report an 'incomplete' error message when the foreground and/or background color strings were not able to be parsed into an actual color. Tests also verify that text-shadow color is handled should that occur. fixes: [4894](#4894) --- Developer Notes: I was not able to have a test case trigger the colorParse error on text-shadow colorParse issues. This case was getting caught already but in the form of the 'complexTextShadows' error.
2 parents 853f043 + 80e769f commit 8f249fd

10 files changed

Lines changed: 132 additions & 18 deletions

File tree

lib/checks/color/color-contrast-enhanced.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
"equalRatio": "Element has a 1:1 contrast ratio with the background",
4545
"shortTextContent": "Element content is too short to determine if it is actual text content",
4646
"nonBmp": "Element content contains only non-text characters",
47-
"pseudoContent": "Element's background color could not be determined due to a pseudo element"
47+
"pseudoContent": "Element's background color could not be determined due to a pseudo element",
48+
"colorParse": "Could not parse color string ${data.colorParse}"
4849
}
4950
}
5051
}

lib/checks/color/color-contrast-evaluate.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,24 @@ export default function colorContrastEvaluate(node, options, virtualNode) {
122122

123123
// if fgColor or bgColor are missing, get more information.
124124
let missing;
125+
let colorParse;
126+
125127
if (bgColor === null) {
126-
missing = incompleteData.get('bgColor');
128+
if (incompleteData.get('colorParse')) {
129+
missing = 'colorParse';
130+
colorParse = incompleteData.get('colorParse');
131+
} else {
132+
missing = incompleteData.get('bgColor');
133+
}
127134
} else if (!isValid) {
128135
missing = contrastContributor;
129136
}
130137

138+
if (fgColor === null && incompleteData.get('colorParse')) {
139+
missing = 'colorParse';
140+
colorParse = incompleteData.get('colorParse');
141+
}
142+
131143
const equalRatio = truncatedResult === 1;
132144
const shortTextContent = visibleText.length === 1;
133145
if (equalRatio) {
@@ -146,7 +158,8 @@ export default function colorContrastEvaluate(node, options, virtualNode) {
146158
fontWeight: bold ? 'bold' : 'normal',
147159
messageKey: missing,
148160
expectedContrastRatio: expected + ':1',
149-
shadowColor: shadowColor ? shadowColor.toHexString() : undefined
161+
shadowColor: shadowColor ? shadowColor.toHexString() : undefined,
162+
colorParse: colorParse
150163
});
151164

152165
// We don't know, so we'll put it into Can't Tell

lib/checks/color/color-contrast.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
"equalRatio": "Element has a 1:1 contrast ratio with the background",
4747
"shortTextContent": "Element content is too short to determine if it is actual text content",
4848
"nonBmp": "Element content contains only non-text characters",
49-
"pseudoContent": "Element's background color could not be determined due to a pseudo element"
49+
"pseudoContent": "Element's background color could not be determined due to a pseudo element",
50+
"colorParse": "Could not parse color string ${data.colorParse}"
5051
}
5152
}
5253
}

lib/commons/color/color.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Colorjs, ArrayFrom } from '../../core/imports';
2+
import incompleteData from './incomplete-data';
23

34
const hexRegex = /^#[0-9a-f]{3,8}$/i;
45
const hslRegex = /hsl\(\s*([-\d.]+)(rad|turn)/;
@@ -172,6 +173,7 @@ export default class Color {
172173
// color.alpha is a Number object so convert it to a number
173174
this.alpha = +color.alpha;
174175
} catch {
176+
incompleteData.set('colorParse', colorString);
175177
throw new Error(`Unable to parse color "${colorString}"`);
176178
}
177179

lib/commons/color/get-background-color.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,17 @@ function _getBackgroundColor(elm, bgElms, shadowOutlineEmMax) {
7575
}
7676

7777
// Get the background color
78-
const bgColor = getOwnBackgroundColor(bgElmStyle);
79-
if (bgColor.alpha === 0) {
80-
continue;
78+
let bgColor;
79+
try {
80+
bgColor = getOwnBackgroundColor(bgElmStyle);
81+
if (bgColor.alpha === 0) {
82+
continue;
83+
}
84+
} catch (error) {
85+
if (error && incompleteData.get('colorParse')) {
86+
return null;
87+
}
88+
throw error;
8189
}
8290

8391
// abort if a node is partially obscured and obscuring element has a background

lib/commons/color/get-foreground-color.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,24 @@ export default function getForegroundColor(node, _, bgColor, options = {}) {
3232
];
3333
let fgColors = [];
3434

35-
for (const colorFn of colorStack) {
36-
const color = colorFn();
37-
if (!color) {
38-
continue;
39-
}
35+
try {
36+
for (const colorFn of colorStack) {
37+
const color = colorFn();
38+
if (!color) {
39+
continue;
40+
}
4041

41-
fgColors = fgColors.concat(color);
42+
fgColors = fgColors.concat(color);
4243

43-
if (color.alpha === 1) {
44-
break;
44+
if (color.alpha === 1) {
45+
break;
46+
}
47+
}
48+
} catch (error) {
49+
if (error && incompleteData.get('colorParse')) {
50+
return null;
4551
}
52+
throw error;
4653
}
4754

4855
const fgColor = fgColors.reduce((source, backdrop) => {

locales/_template.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,8 @@
628628
"equalRatio": "Element has a 1:1 contrast ratio with the background",
629629
"shortTextContent": "Element content is too short to determine if it is actual text content",
630630
"nonBmp": "Element content contains only non-text characters",
631-
"pseudoContent": "Element's background color could not be determined due to a pseudo element"
631+
"pseudoContent": "Element's background color could not be determined due to a pseudo element",
632+
"colorParse": "Could not parse color string ${data.colorParse}"
632633
}
633634
},
634635
"color-contrast": {
@@ -655,7 +656,8 @@
655656
"equalRatio": "Element has a 1:1 contrast ratio with the background",
656657
"shortTextContent": "Element content is too short to determine if it is actual text content",
657658
"nonBmp": "Element content contains only non-text characters",
658-
"pseudoContent": "Element's background color could not be determined due to a pseudo element"
659+
"pseudoContent": "Element's background color could not be determined due to a pseudo element",
660+
"colorParse": "Could not parse color string ${data.colorParse}"
659661
}
660662
},
661663
"link-in-text-block-style": {

test/checks/color/color-contrast.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,45 @@ describe('color-contrast', function () {
1515
axe._tree = undefined;
1616
});
1717

18+
it('should return undefined if cannot handle color', function () {
19+
var params = checkSetup(
20+
'<div id="divundertest" style="color: oklch(0.961073 0.000047911 none / 0.2); background-color: black; font-size: 14pt; font-weight: 900;">' +
21+
'<span id="target" style="font-weight:lighter;">My text</span></div>'
22+
);
23+
24+
var expectedRelatedNodes = fixture.querySelector('#divundertest');
25+
assert.isUndefined(contrastEvaluate.apply(checkContext, params));
26+
assert.deepEqual(checkContext._relatedNodes, [expectedRelatedNodes]);
27+
assert.deepEqual(checkContext._data.messageKey, 'colorParse');
28+
assert.equal(
29+
checkContext._data.colorParse,
30+
'oklch(0.961073 0.000047911 none / 0.2)'
31+
);
32+
});
33+
34+
it('should return undefined if cannot handle backgroundcolor', function () {
35+
var params = checkSetup(
36+
'<div style="color: gray; background-color: oklch(0.961073 0.000047911 none / 0.2); font-size: 14pt; font-weight: 900;">' +
37+
'<span id="target" style="font-weight:lighter;">My text</span></div>'
38+
);
39+
assert.isUndefined(contrastEvaluate.apply(checkContext, params));
40+
assert.deepEqual(checkContext._relatedNodes, []);
41+
assert.deepEqual(checkContext._data.messageKey, 'colorParse');
42+
assert.equal(
43+
checkContext._data.colorParse,
44+
'oklch(0.961073 0.000047911 none / 0.2)'
45+
);
46+
});
47+
48+
it('should return undefined if cannot handle text-shadow', function () {
49+
var params = checkSetup(
50+
'<div id="target" style="background-color: #fff; color:#000; text-shadow: 1px 1px oklch(0.961073 0.000047911 none / 0.2);">My text</div>'
51+
);
52+
53+
assert.isUndefined(contrastEvaluate.apply(checkContext, params));
54+
assert.deepEqual(checkContext._relatedNodes, []);
55+
});
56+
1857
it('should return true for hidden element', function () {
1958
var params = checkSetup(
2059
'<div style="color: gray; background-color: white; font-size: 14pt; font-weight: 100;">' +

test/integration/rules/color-contrast/color-contrast.html

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,44 @@
282282
</div>
283283
</div>
284284

285+
<div
286+
style="
287+
color: oklch(0.961073 0.000047911 none / 0.2);
288+
background-color: black;
289+
font-size: 14pt;
290+
font-weight: 900;
291+
"
292+
>
293+
<span id="canttell21" style="font-weight: lighter"
294+
>This is a foreground colorParse fail!</span
295+
>
296+
</div>
297+
298+
<div
299+
style="
300+
color: gray;
301+
background-color: oklch(0.961073 0.000047911 none / 0.2);
302+
font-size: 14pt;
303+
font-weight: 900;
304+
"
305+
>
306+
<span id="canttell22" style="font-weight: lighter"
307+
>This is a background colorParse fail!</span
308+
>
309+
</div>
310+
311+
<div
312+
style="
313+
background-color: #fff;
314+
color: #000;
315+
text-shadow: 1px 1px oklch(0.961073 0.000047911 none / 0.2);
316+
"
317+
>
318+
<span id="canttell23" style="font-weight: lighter"
319+
>This is a text-shadow colorParse fail!</span
320+
>
321+
</div>
322+
285323
<p
286324
style="
287325
text-overflow: ellipsis;

test/integration/rules/color-contrast/color-contrast.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@
5959
["#canttell17"],
6060
["#canttell18"],
6161
["#canttell19"],
62-
["#canttell20"]
62+
["#canttell20"],
63+
["#canttell21"],
64+
["#canttell22"],
65+
["#canttell23"]
6366
]
6467
}

0 commit comments

Comments
 (0)