From e0f6c0cfb8425bc0f7548c79919ac2cbd8393e83 Mon Sep 17 00:00:00 2001 From: Wilco Fiers Date: Tue, 27 Jul 2021 18:55:40 +0200 Subject: [PATCH] fix(color-contrast): check for size before ignoring pseudo elements (#3097) * fix(color-contrast): check for size before ignoring pseudo elements * chore(eslint): disable no-use-bef-re-define * chore: tweak tests for IE * chore: address IE11 problem * Apply suggestions from code review Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> * chore: tweak * Update lib/checks/color/color-contrast-evaluate.js Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> Co-authored-by: Steven Lambert <2433219+straker@users.noreply.github.com> --- .eslintrc.js | 3 +- doc/check-options.md | 3 +- lib/checks/color/color-contrast-evaluate.js | 143 ++-- lib/checks/color/color-contrast.json | 1 + test/checks/color/color-contrast.js | 784 +++++++++++--------- 5 files changed, 504 insertions(+), 430 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index aae185cdc2..a6617baecd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -83,7 +83,8 @@ module.exports = { }, rules: { 'func-names': [2, 'as-needed'], - 'prefer-const': 2 + 'prefer-const': 2, + 'no-use-before-define': 'off' } }, { diff --git a/doc/check-options.md b/doc/check-options.md index 2c05905801..17f5adac6c 100644 --- a/doc/check-options.md +++ b/doc/check-options.md @@ -205,11 +205,12 @@ All checks allow these global options: | ----------------------------------------------------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ignoreUnicode` | `true` | Do not check the color contrast of Unicode characters | | `ignoreLength` | `false` | Do not check the color contrast of short text content | -| `ignorePseudo` | `false` | Do not mark pseudo elements as Needs Review | +| `ignorePseudo` | `false` | Do not mark pseudo elements as Needs Review | | `boldValue` | `700` | The minimum CSS `font-weight` value that designates bold text | | `boldTextPt` | `14` | The minimum CSS `font-size` pt value that designates bold text as being large | | `largeTextPt` | `18` | The minimum CSS `font-size` pt value that designates text as being large | | `shadowOutlineEmMax` | `0.1` | The maximum `blur-radius` value (in ems) of the CSS `text-shadow` property. `blur-radius` values greater than this value will be treated as a background color rather than an outline color. | +| `pseudoSizeThreshold` | `0.25` | Minimum area of the pseudo element, relative to the text element, below which it will be ignored for colot contrast. | | `contrastRatio` | N/A | Contrast ratio options | |   `contrastRatio.normal` | N/A | Contrast ratio requirements for normal text (non-bold text or text smaller than `largeTextPt`) | |     `contrastRatio.normal.expected` | `4.5` | The expected contrast ratio for normal text | diff --git a/lib/checks/color/color-contrast-evaluate.js b/lib/checks/color/color-contrast-evaluate.js index bbed6f0c05..eabf6aa536 100644 --- a/lib/checks/color/color-contrast-evaluate.js +++ b/lib/checks/color/color-contrast-evaluate.js @@ -16,29 +16,7 @@ import { } from '../../commons/color'; import { memoize } from '../../core/utils'; -const hasPseudoElement = memoize(function hasPseudoElement(node, pseudo) { - const style = window.getComputedStyle(node, pseudo); - const backgroundColor = getOwnBackgroundColor(style); - - // element has a non-transparent color or image, has - // non-zero width, and is visible - return ( - style.getPropertyValue('content') !== 'none' && - style.getPropertyValue('position') === 'absolute' && - parseInt(style.getPropertyValue('width')) !== 0 && - parseInt(style.getPropertyValue('height')) !== 0 && - style.getPropertyValue('display') !== 'none' && - style.getPropertyValue('visibility') !== 'hidden' && - (backgroundColor.alpha !== 0 || - style.getPropertyValue('background-image') !== 'none') - ); -}); - -function colorContrastEvaluate(node, options, virtualNode) { - if (!isVisible(node, false)) { - return true; - } - +export default function colorContrastEvaluate(node, options, virtualNode) { const { ignoreUnicode, ignoreLength, @@ -47,25 +25,29 @@ function colorContrastEvaluate(node, options, virtualNode) { boldTextPt, largeTextPt, contrastRatio, - shadowOutlineEmMax + shadowOutlineEmMax, + pseudoSizeThreshold } = options; + if (!isVisible(node, false)) { + return true; + } + const visibleText = visibleVirtual(virtualNode, false, true); - const textContainsOnlyUnicode = - hasUnicode(visibleText, { - nonBmp: true - }) && - sanitize( - removeUnicode(visibleText, { - nonBmp: true - }) - ) === ''; - - if (textContainsOnlyUnicode && ignoreUnicode) { + if (ignoreUnicode && textIsEmojis(visibleText)) { this.data({ messageKey: 'nonBmp' }); return undefined; } + // if element or a parent has pseudo content then we need to mark + // as needs review + const pseudoElm = findPseudoElement(virtualNode, { ignorePseudo, pseudoSizeThreshold }) + if (pseudoElm) { + this.data({ messageKey: 'pseudoContent' }); + this.relatedNodes(pseudoElm.actualNode); + return undefined; + } + const bgNodes = []; const bgColor = getBackgroundColor(node, bgNodes, shadowOutlineEmMax); const fgColor = getForegroundColor(node, false, bgColor); @@ -105,26 +87,6 @@ function colorContrastEvaluate(node, options, virtualNode) { : contrastRatio.large; const isValid = contrast > expected; - // if element or a parent has pseudo content then we need to mark - // as needs review - if (!ignorePseudo) { - let nodeToCheck = node; - while (nodeToCheck) { - if ( - hasPseudoElement(nodeToCheck, ':before') || - hasPseudoElement(nodeToCheck, ':after') - ) { - this.data({ - messageKey: 'pseudoContent' - }); - this.relatedNodes(nodeToCheck); - return undefined; - } - - nodeToCheck = nodeToCheck.parentElement; - } - } - // ratio is outside range if ( (typeof minThreshold === 'number' && contrast < minThreshold) || @@ -155,7 +117,7 @@ function colorContrastEvaluate(node, options, virtualNode) { } // need both independently in case both are missing - const data = { + this.data({ fgColor: fgColor ? fgColor.toHexString() : undefined, bgColor: bgColor ? bgColor.toHexString() : undefined, contrastRatio: truncatedResult, @@ -164,9 +126,7 @@ function colorContrastEvaluate(node, options, virtualNode) { messageKey: missing, expectedContrastRatio: expected + ':1', shadowColor: shadowColor ? shadowColor.toHexString() : undefined - }; - - this.data(data); + }); // We don't know, so we'll put it into Can't Tell if ( @@ -188,4 +148,67 @@ function colorContrastEvaluate(node, options, virtualNode) { return isValid; } -export default colorContrastEvaluate; +function findPseudoElement(vNode, { + pseudoSizeThreshold = 0.25, + ignorePseudo = false +}) { + if (ignorePseudo) { + return; + } + const rect = vNode.boundingClientRect; + const minimumSize = rect.width * rect.height * pseudoSizeThreshold; + do { + const beforeSize = getPseudoElementArea(vNode.actualNode, ':before') + const afterSize = getPseudoElementArea(vNode.actualNode, ':after') + if (beforeSize + afterSize > minimumSize) { + return vNode // Combined area of before and after exceeds the minimum size + } + } while (vNode = vNode.parent); +} + +const getPseudoElementArea = memoize(function getPseudoElementArea(node, pseudo) { + const style = window.getComputedStyle(node, pseudo); + const matchPseudoStyle = (prop, value) => style.getPropertyValue(prop) === value; + if ( + matchPseudoStyle('content', 'none') || + matchPseudoStyle('display', 'none') || + matchPseudoStyle('visibility', 'hidden') || + matchPseudoStyle('position', 'absolute') === false + ) { + return 0; // The pseudo element isn't visible + } + + if ( + getOwnBackgroundColor(style).alpha === 0 && + matchPseudoStyle('background-image', 'none') + ) { + return 0; // There is no background + } + + // Find the size of the pseudo element; + const pseudoWidth = parseUnit(style.getPropertyValue('width')); + const pseudoHeight = parseUnit(style.getPropertyValue('height')); + if (pseudoWidth.unit !== 'px' || pseudoHeight.unit !== 'px') { + // IE doesn't normalize to px. Infinity gets everything to undefined + return (pseudoWidth.value === 0 || pseudoHeight.value === 0 + ? 0 : Infinity + ); + } + return pseudoWidth.value * pseudoHeight.value; +}); + +function textIsEmojis(visibleText) { + const options = { nonBmp: true }; + const hasUnicodeChars = hasUnicode(visibleText, options); + const hasNonUnicodeChars = sanitize(removeUnicode(visibleText, options)) === '' + return hasUnicodeChars && hasNonUnicodeChars +} + +function parseUnit(str) { + const unitRegex = /^([0-9.]+)([a-z]+)$/i; + const [, value = '', unit = ''] = str.match(unitRegex) || []; + return { + value: parseFloat(value), + unit: unit.toLowerCase() + }; +} diff --git a/lib/checks/color/color-contrast.json b/lib/checks/color/color-contrast.json index ee11dd7db3..52cd48361c 100644 --- a/lib/checks/color/color-contrast.json +++ b/lib/checks/color/color-contrast.json @@ -16,6 +16,7 @@ "expected": 3 } }, + "pseudoSizeThreshold": 0.25, "shadowOutlineEmMax": 0.1 }, "metadata": { diff --git a/test/checks/color/color-contrast.js b/test/checks/color/color-contrast.js index 80389501cb..f2f322eb4a 100644 --- a/test/checks/color/color-contrast.js +++ b/test/checks/color/color-contrast.js @@ -6,6 +6,7 @@ describe('color-contrast', function() { var checkSetup = axe.testUtils.checkSetup; var shadowSupported = axe.testUtils.shadowSupport.v1; var shadowCheckSetup = axe.testUtils.shadowCheckSetup; + var isIE11 = axe.testUtils.isIE11; var checkContext = axe.testUtils.MockCheckContext(); var contrastEvaluate = axe.testUtils.getCheckEvaluate('color-contrast'); @@ -357,434 +358,481 @@ describe('color-contrast', function() { ); }); - it('should return undefined if :before pseudo element has a background color', function() { - var params = checkSetup( - '' + - '

Content

' - ); - - assert.isUndefined(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._data, { - messageKey: 'pseudoContent' + describe('with pseudo elements', function () { + it('should return undefined if :before pseudo element has a background color', function() { + var params = checkSetup( + '' + + '

Content

' + ); + + assert.isUndefined(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._data, { + messageKey: 'pseudoContent' + }); + assert.equal( + checkContext._relatedNodes[0], + document.querySelector('#background') + ); }); - assert.equal( - checkContext._relatedNodes[0], - document.querySelector('#background') - ); - }); - - it('should return undefined if :after pseudo element has a background color', function() { - var params = checkSetup( - '' + - '

Content

' - ); - - assert.isUndefined(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._data, { - messageKey: 'pseudoContent' + + it('should return undefined if :after pseudo element has a background color', function() { + var params = checkSetup( + '' + + '

Content

' + ); + + assert.isUndefined(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._data, { + messageKey: 'pseudoContent' + }); + assert.equal( + checkContext._relatedNodes[0], + document.querySelector('#background') + ); }); - assert.equal( - checkContext._relatedNodes[0], - document.querySelector('#background') - ); - }); - - it('should not return undefined if ignorePseudo option is used', function() { - var params = checkSetup( - '' + - '

Content

', - { - ignorePseudo: true - } - ); - - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - }); - - it('should return undefined if pseudo element has a background image', function() { - var dataURI = - 'data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/' + - 'XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkA' + - 'ABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKU' + - 'E1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7'; - - var params = checkSetup( - '' + - '

Content

' - ); - - assert.isUndefined(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._data, { - messageKey: 'pseudoContent' + + it('should return undefined if pseudo element has a background image', function() { + var dataURI = + 'data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/' + + 'XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkA' + + 'ABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKU' + + 'E1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7'; + + var params = checkSetup( + '' + + '

Content

' + ); + + assert.isUndefined(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._data, { + messageKey: 'pseudoContent' + }); + assert.equal( + checkContext._relatedNodes[0], + document.querySelector('#background') + ); + }); + + it('should not return undefined if pseudo element has no content', function() { + var params = checkSetup( + '' + + '

Content

' + ); + + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + }); + + it('should not return undefined if pseudo element is not absolutely positioned no content', function() { + var params = checkSetup( + '' + + '

Content

' + ); + + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + }); + + it('should not return undefined if pseudo element is has zero dimension', function() { + var params = checkSetup( + '' + + '

Content

' + ); + + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + }); + + it("should not return undefined if pseudo element doesn't have a background", function() { + var params = checkSetup( + '' + + '

Content

' + ); + + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + }); + + it('should not return undefined if pseudo element has visibility: hidden', function() { + var params = checkSetup( + '' + + '

Content

' + ); + + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + }); + + it('should not return undefined if pseudo element has display: none', function() { + var params = checkSetup( + '' + + '

Content

' + ); + + assert.isTrue(contrastEvaluate.apply(checkContext, params)); }); - assert.equal( - checkContext._relatedNodes[0], - document.querySelector('#background') - ); - }); - - it('should not return undefined if pseudo element has no content', function() { - var params = checkSetup( - '' + - '

Content

' - ); - - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - }); - - it('should not return undefined if pseudo element is not absolutely positioned no content', function() { - var params = checkSetup( - '' + - '

Content

' - ); - - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - }); - - it('should not return undefined if pseudo element is has zero dimension', function() { - var params = checkSetup( - '' + - '

Content

' - ); - - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - }); - - it("should not return undefined if pseudo element doesn't have a background", function() { - var params = checkSetup( - '' + - '

Content

' - ); - - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - }); - - it('should not return undefined if pseudo element has visibility: hidden', function() { - var params = checkSetup( - '' + - '

Content

' - ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - }); + it('should return undefined if pseudo element is more than 25% of the element', function () { + var params = checkSetup( + '' + + '

Content

' + ); + assert.isUndefined(contrastEvaluate.apply(checkContext, params)); + }); - it('should not return undefined if pseudo element has display: none', function() { - var params = checkSetup( - '' + - '

Content

' - ); + it('should not return undefined if pseudo element is 25% of the element', function () { + var params = checkSetup( + '' + + '

Content

' + ); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + }); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); + (isIE11 ? it : xit)('should return undefined if the unit is not in px', function () { + var params = checkSetup( + '' + + '

Content

' + ); + assert.isUndefined(contrastEvaluate.apply(checkContext, params)); + }); }); - it('should return undefined for a single character text with insufficient contrast', function() { - var params = checkSetup( - '
' + - '
X
' + - '
' - ); + describe('with special texts', function () { + 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.messageKey, 'shortTextContent'); - }); + var actual = contrastEvaluate.apply(checkContext, params); + assert.isUndefined(actual); + assert.equal(checkContext._data.messageKey, 'shortTextContent'); + }); - it('should return true for a single character text with insufficient contrast', function() { - var params = checkSetup( - '
' + - '
X
' + - '
' - ); + it('should return true 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.messageKey, 'shortTextContent'); - }); + 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 when the ignoreUnicode option is true', function() { - var params = checkSetup( - '
' + - '
₠ ₡ ₢ ₣
' + - '
', - { - ignoreUnicode: true - } - ); + it('should return undefined when the text only contains nonBmp unicode when the ignoreUnicode option is true', function() { + var params = checkSetup( + '
' + + '
₠ ₡ ₢ ₣
' + + '
', + { + ignoreUnicode: true + } + ); - var actual = contrastEvaluate.apply(checkContext, params); - assert.isUndefined(actual); - assert.equal(checkContext._data.messageKey, 'nonBmp'); - }); + var actual = contrastEvaluate.apply(checkContext, params); + assert.isUndefined(actual); + assert.equal(checkContext._data.messageKey, 'nonBmp'); + }); - 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( - '
' + - '
' + - '
', - { - ignoreUnicode: false - } - ); + 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( + '
' + + '
' + + '
', + { + ignoreUnicode: false + } + ); - var actual = contrastEvaluate.apply(checkContext, params); - assert.isTrue(actual); - }); + 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( - '
' + - '
' + - '
', - { - ignoreUnicode: false - } - ); + 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( + '
' + + '
' + + '
', + { + ignoreUnicode: false + } + ); - var actual = contrastEvaluate.apply(checkContext, params); - assert.isUndefined(actual); - assert.equal(checkContext._data.messageKey, 'shortTextContent'); - }); + 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( - '
' + - '
' + - '
', - { - ignoreUnicode: false, - ignoreLength: true - } - ); + 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( + '
' + + '
' + + '
', + { + ignoreUnicode: false, + ignoreLength: true + } + ); - var actual = contrastEvaluate.apply(checkContext, params); - assert.isFalse(actual); + var actual = contrastEvaluate.apply(checkContext, params); + assert.isFalse(actual); + }); }); - it('should support options.boldValue', function() { - var params = checkSetup( - '
' + - 'My text
', - { - boldValue: 100 - } - ); + describe('options', function () { + it('should support options.boldValue', function() { + var params = checkSetup( + '
' + + 'My text
', + { + boldValue: 100 + } + ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._relatedNodes, []); - }); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._relatedNodes, []); + }); - it('should support options.boldTextPt', function() { - var params = checkSetup( - '
' + - 'My text
', - { - boldTextPt: 6 - } - ); + it('should support options.boldTextPt', function() { + var params = checkSetup( + '
' + + 'My text
', + { + boldTextPt: 6 + } + ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._relatedNodes, []); - }); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._relatedNodes, []); + }); - it('should support options.largeTextPt', function() { - var params = checkSetup( - '
' + - 'My text
', - { - largeTextPt: 6 - } - ); + it('should support options.largeTextPt', function() { + var params = checkSetup( + '
' + + 'My text
', + { + largeTextPt: 6 + } + ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._relatedNodes, []); - }); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._relatedNodes, []); + }); - it('should support options.contrastRatio.normal.expected', function() { - var params = checkSetup( - '
' + - 'My text
', - { - contrastRatio: { - normal: { - expected: 2.5 + it('should support options.contrastRatio.normal.expected', function() { + var params = checkSetup( + '
' + + 'My text
', + { + contrastRatio: { + normal: { + expected: 2.5 + } } } - } - ); + ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._relatedNodes, []); - }); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._relatedNodes, []); + }); - it('should support options.contrastRatio.normal.minThreshold', function() { - var params = checkSetup( - '
' + - 'My text
', - { - contrastRatio: { - normal: { - minThreshold: 3 + it('should support options.contrastRatio.normal.minThreshold', function() { + var params = checkSetup( + '
' + + 'My text
', + { + contrastRatio: { + normal: { + minThreshold: 3 + } } } - } - ); + ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._relatedNodes, []); - }); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._relatedNodes, []); + }); - it('should support options.contrastRatio.normal.maxThreshold', function() { - var params = checkSetup( - '
' + - 'My text
', - { - contrastRatio: { - normal: { - maxThreshold: 2 + it('should support options.contrastRatio.normal.maxThreshold', function() { + var params = checkSetup( + '
' + + 'My text
', + { + contrastRatio: { + normal: { + maxThreshold: 2 + } } } - } - ); + ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._relatedNodes, []); - }); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._relatedNodes, []); + }); - it('should support options.contrastRatio.large.expected', function() { - var params = checkSetup( - '
' + - 'My text
', - { - contrastRatio: { - large: { - expected: 1.5 + it('should support options.contrastRatio.large.expected', function() { + var params = checkSetup( + '
' + + 'My text
', + { + contrastRatio: { + large: { + expected: 1.5 + } } } - } - ); + ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._relatedNodes, []); - }); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._relatedNodes, []); + }); - it('should support options.contrastRatio.large.minThreshold', function() { - var params = checkSetup( - '
' + - 'My text
', - { - contrastRatio: { - large: { - minThreshold: 2 + it('should support options.contrastRatio.large.minThreshold', function() { + var params = checkSetup( + '
' + + 'My text
', + { + contrastRatio: { + large: { + minThreshold: 2 + } } } - } - ); + ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._relatedNodes, []); - }); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._relatedNodes, []); + }); - it('should support options.contrastRatio.large.maxThreshold', function() { - var params = checkSetup( - '
' + - 'My text
', - { - contrastRatio: { - large: { - maxThreshold: 1.2 + it('should support options.contrastRatio.large.maxThreshold', function() { + var params = checkSetup( + '
' + + 'My text
', + { + contrastRatio: { + large: { + maxThreshold: 1.2 + } } } - } - ); + ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - assert.deepEqual(checkContext._relatedNodes, []); - }); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + assert.deepEqual(checkContext._relatedNodes, []); + }); - (shadowSupported ? it : xit)( - 'returns colors across Shadow DOM boundaries', - function() { - var params = shadowCheckSetup( - '
', - '

Text

' + it('should ignore pseudo element with options.ignorePseudo', function() { + var params = checkSetup( + '' + + '

Content

', + { + ignorePseudo: true + } ); - var container = fixture.querySelector('#container'); - var result = contrastEvaluate.apply(checkContext, params); - assert.isFalse(result); - assert.deepEqual(checkContext._relatedNodes, [container]); - } - ); - - it('passes if thin text shadows have sufficient contrast with the text', function() { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); + + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + }); + + it('should adjust the pseudo element minimum size with the options.pseudoSizeThreshold', function () { + var params = checkSetup( + '' + + '

Content

', + { + pseudoSizeThreshold: 0.20 + } + ); + assert.isUndefined(contrastEvaluate.apply(checkContext, params)); + }); }); - it('does not count text shadows of offset 0, blur 0 as part of the background color', function() { - var params = checkSetup( - '
' + - ' Hello world' + - '
' + describe('with shadowDOM', function () { + (shadowSupported ? it : xit)( + 'returns colors across Shadow DOM boundaries', + function() { + var params = shadowCheckSetup( + '
', + '

Text

' + ); + var container = fixture.querySelector('#container'); + var result = contrastEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._relatedNodes, [container]); + } ); - var white = new axe.commons.color.Color(255, 255, 255, 1); + it('passes if thin text shadows have sufficient contrast with the text', function() { + var params = checkSetup( + '
' + + ' Hello world' + + '
' + ); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + }); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - assert.equal(checkContext._data.bgColor, white.toHexString()); - assert.equal(checkContext._data.fgColor, '#0f833e'); - assert.equal(checkContext._data.contrastRatio, '4.84'); - }); + it('does not count text shadows of offset 0, blur 0 as part of the background color', function() { + var params = checkSetup( + '
' + + ' Hello world' + + '
' + ); - it('passes if thin text shadows have sufficient contrast with the background', function() { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); - assert.isTrue(contrastEvaluate.apply(checkContext, params)); - }); + var white = new axe.commons.color.Color(255, 255, 255, 1); - it('fails if text shadows have sufficient contrast with the background if its width is thicker than `shadowOutlineEmMax`', function() { - var checkOptions = { shadowOutlineEmMax: 0.05 }; - var params = checkSetup( - '
' + - ' Hello world' + - '
', - checkOptions - ); - assert.isFalse(contrastEvaluate.apply(checkContext, params)); - assert.equal(checkContext._data.messageKey, null); - }); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + assert.equal(checkContext._data.bgColor, white.toHexString()); + assert.equal(checkContext._data.fgColor, '#0f833e'); + assert.equal(checkContext._data.contrastRatio, '4.84'); + }); - it('fails if text shadows do not have sufficient contrast with the foreground', function() { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); - assert.isFalse(contrastEvaluate.apply(checkContext, params)); - assert.equal(checkContext._data.messageKey, 'fgOnShadowColor'); - }); + it('passes if thin text shadows have sufficient contrast with the background', function() { + var params = checkSetup( + '
' + + ' Hello world' + + '
' + ); + assert.isTrue(contrastEvaluate.apply(checkContext, params)); + }); - it('fails if text shadows do not have sufficient contrast with the background', function() { - var params = checkSetup( - '
' + - ' Hello world' + - '
' - ); - assert.isFalse(contrastEvaluate.apply(checkContext, params)); - assert.equal(checkContext._data.messageKey, 'shadowOnBgColor'); + it('fails if text shadows have sufficient contrast with the background if its width is thicker than `shadowOutlineEmMax`', function() { + var checkOptions = { shadowOutlineEmMax: 0.05 }; + var params = checkSetup( + '
' + + ' Hello world' + + '
', + checkOptions + ); + assert.isFalse(contrastEvaluate.apply(checkContext, params)); + assert.equal(checkContext._data.messageKey, null); + }); + + it('fails if text shadows do not have sufficient contrast with the foreground', function() { + var params = checkSetup( + '
' + + ' Hello world' + + '
' + ); + assert.isFalse(contrastEvaluate.apply(checkContext, params)); + assert.equal(checkContext._data.messageKey, 'fgOnShadowColor'); + }); + + it('fails if text shadows do not have sufficient contrast with the background', function() { + var params = checkSetup( + '
' + + ' Hello world' + + '
' + ); + assert.isFalse(contrastEvaluate.apply(checkContext, params)); + assert.equal(checkContext._data.messageKey, 'shadowOnBgColor'); + }); }); });