diff --git a/doc/check-options.md b/doc/check-options.md index 6487f0092e..90a30b47d4 100644 --- a/doc/check-options.md +++ b/doc/check-options.md @@ -32,6 +32,7 @@ - [avoid-inline-spacing](#avoid-inline-spacing) - [scope-value](#scope-value) - [region](#region) + - [inline-style-property](#inline-style-property) ## How Checks Work @@ -491,3 +492,23 @@ h6:not([role]), | Option | Default | Description | | --------------- | :--------------------------------------------- | :-------------------------------------------------------------------------- | | `regionMatcher` |
dialog, [role=dialog], svg
| A matcher object or CSS selector to allow elements to be treated as regions | + +### inline-style-property-evaluate + +This evaluate method is used in the following checks. Default vary between checks + +- important-letter-spacing +- important-word-spacing +- important-line-height + +| Option | Description | +| ---------------- | :---------------------------------------------------------------------------- | +| `cssProperty` | Which property to check the value of, for example letter-spacing or font-size | +| `absoluteValues` | Whether or not to calculate value in pixels (true) or in em (false) | +| `noImportant` | While false, the check returns `true` except if !important is used | +| `multiLineOnly` | If true, | +| `minValue` | Returns `false` when the value is less than `minValue` | +| `maxValue` | Returns `false` when the value is more than `maxValue` | +| `normalValue` | The value to use when `normal` is set, defaults to `0` | + +If `minValue` and `maxValue` are both undefined, the check returns `false` if the property is used with !important. If done along with `noImportant: true`, the check returns false if the property is set at all in the style attribute. diff --git a/lib/checks/shared/important-letter-spacing.json b/lib/checks/shared/important-letter-spacing.json new file mode 100644 index 0000000000..85c8d4253c --- /dev/null +++ b/lib/checks/shared/important-letter-spacing.json @@ -0,0 +1,15 @@ +{ + "id": "important-letter-spacing", + "evaluate": "inline-style-property-evaluate", + "options": { + "cssProperty": "letter-spacing", + "minValue": 0.12 + }, + "metadata": { + "impact": "serious", + "messages": { + "pass": "Letter-spacing in the style attribute is not set to !important, or meets the minimum", + "fail": "letter-spacing in the style attribute must not use !important, or be at ${data.minValue}em (current ${data.value}em)" + } + } +} diff --git a/lib/checks/shared/important-line-height.json b/lib/checks/shared/important-line-height.json new file mode 100644 index 0000000000..95c50b5688 --- /dev/null +++ b/lib/checks/shared/important-line-height.json @@ -0,0 +1,17 @@ +{ + "id": "important-line-height", + "evaluate": "inline-style-property-evaluate", + "options": { + "multiLineOnly": true, + "cssProperty": "line-height", + "minValue": 1.5, + "normalValue": 1 + }, + "metadata": { + "impact": "serious", + "messages": { + "pass": "line-height in the style attribute is not set to !important, or meets the minimum", + "fail": "line-height in the style attribute must not use !important, or be at ${data.minValue}em (current ${data.value}em)" + } + } +} diff --git a/lib/checks/shared/important-word-spacing.json b/lib/checks/shared/important-word-spacing.json new file mode 100644 index 0000000000..218733f4f4 --- /dev/null +++ b/lib/checks/shared/important-word-spacing.json @@ -0,0 +1,15 @@ +{ + "id": "important-word-spacing", + "evaluate": "inline-style-property-evaluate", + "options": { + "cssProperty": "word-spacing", + "minValue": 0.16 + }, + "metadata": { + "impact": "serious", + "messages": { + "pass": "word-spacing in the style attribute is not set to !important, or meets the minimum", + "fail": "word-spacing in the style attribute must not use !important, or be at ${data.minValue}em (current ${data.value}em)" + } + } +} diff --git a/lib/checks/shared/inline-style-property-evaluate.js b/lib/checks/shared/inline-style-property-evaluate.js new file mode 100644 index 0000000000..725fa867f8 --- /dev/null +++ b/lib/checks/shared/inline-style-property-evaluate.js @@ -0,0 +1,80 @@ +import { isMultiline } from '../../commons/dom'; + +/** + * Check if a CSS property, !important or not is within an allowed range + */ +export default function inlineStyleProperty(node, options) { + const { + cssProperty, + absoluteValues, + minValue, + maxValue, + normalValue = 0, + noImportant, + multiLineOnly + } = options; + if ( + (!noImportant && + node.style.getPropertyPriority(cssProperty) !== `important`) || + (multiLineOnly && !isMultiline(node)) + ) { + return true; + } + + const data = {}; + if (typeof minValue === 'number') { + data.minValue = minValue; + } + if (typeof maxValue === 'number') { + data.maxValue = maxValue; + } + + // These do not set the actual value to important, instead they + // say that it is important to use the inherited / root value. + // The actual value can still be modified + const declaredPropValue = node.style.getPropertyValue(cssProperty); + if ( + ['inherit', 'unset', 'revert', 'revert-layer'].includes(declaredPropValue) + ) { + this.data({ value: declaredPropValue, ...data }); + return true; + } + + const value = getNumberValue(node, { + absoluteValues, + cssProperty, + normalValue + }); + this.data({ value, ...data }); + if (typeof value !== 'number') { + return undefined; // Renderer did something it shouldn't + } + + if ( + (typeof minValue !== 'number' || value >= minValue) && + (typeof maxValue !== 'number' || value <= maxValue) + ) { + return true; + } + return false; +} + +function getNumberValue(domNode, { cssProperty, absoluteValues, normalValue }) { + const computedStyle = window.getComputedStyle(domNode); + const cssPropValue = computedStyle.getPropertyValue(cssProperty); + if (cssPropValue === 'normal') { + return normalValue; + } + const parsedValue = parseFloat(cssPropValue); + if (absoluteValues) { + return parsedValue; + } + + const fontSize = parseFloat(computedStyle.getPropertyValue('font-size')); + // Make the value relative to the font-size + const value = Math.round((parsedValue / fontSize) * 100) / 100; + if (isNaN(value)) { + return cssPropValue; // Something went wrong, return the string instead + } + return value; +} diff --git a/lib/commons/dom/index.js b/lib/commons/dom/index.js index 5acd13c076..d9823fed26 100644 --- a/lib/commons/dom/index.js +++ b/lib/commons/dom/index.js @@ -25,6 +25,7 @@ export { default as isHiddenWithCSS } from './is-hidden-with-css'; export { default as isHTML5 } from './is-html5'; export { default as isInTextBlock } from './is-in-text-block'; export { default as isModalOpen } from './is-modal-open'; +export { default as isMultiline } from './is-multiline'; export { default as isNativelyFocusable } from './is-natively-focusable'; export { default as isNode } from './is-node'; export { default as isOffscreen } from './is-offscreen'; diff --git a/lib/commons/dom/is-multiline.js b/lib/commons/dom/is-multiline.js new file mode 100644 index 0000000000..528fb7ffa8 --- /dev/null +++ b/lib/commons/dom/is-multiline.js @@ -0,0 +1,28 @@ +/** + * Returns true if content has client rects that have no vertical overlap. + * I.e. they are rendered on different "lines". + * @param {Element} domNode + * @param {number} margin (default: 2) + * @returns {number} + */ +export default function isMultiline(domNode, margin = 2) { + const range = domNode.ownerDocument.createRange(); + range.setStart(domNode, 0); + range.setEnd(domNode, domNode.childNodes.length); + let lastLineEnd = 0; + let lineCount = 0; + for (const rect of range.getClientRects()) { + if (rect.height <= margin) { + continue; + } + if (lastLineEnd > rect.top + margin) { + lastLineEnd = Math.max(lastLineEnd, rect.bottom); + } else if (lineCount === 0) { + lastLineEnd = rect.bottom; + lineCount++; + } else { + return true; + } + } + return false; +} diff --git a/lib/rules/avoid-inline-spacing.json b/lib/rules/avoid-inline-spacing.json index e6756cd05e..9e0ceacf88 100644 --- a/lib/rules/avoid-inline-spacing.json +++ b/lib/rules/avoid-inline-spacing.json @@ -1,13 +1,18 @@ { "id": "avoid-inline-spacing", "selector": "[style]", + "matches": "is-visible-matches", "tags": ["cat.structure", "wcag21aa", "wcag1412", "ACT"], "actIds": ["24afc2", "9e45ec", "78fd32"], "metadata": { "description": "Ensure that text spacing set through style attributes can be adjusted with custom stylesheets", "help": "Inline text spacing must be adjustable with custom stylesheets" }, - "all": ["avoid-inline-spacing"], + "all": [ + "important-letter-spacing", + "important-word-spacing", + "important-line-height" + ], "any": [], "none": [] } diff --git a/lib/rules/is-visible-matches.js b/lib/rules/is-visible-matches.js new file mode 100644 index 0000000000..6625f7b3bf --- /dev/null +++ b/lib/rules/is-visible-matches.js @@ -0,0 +1,5 @@ +import { isVisible } from '../commons/dom'; + +export default function hasVisibleTextMatches(node) { + return isVisible(node, false); +} diff --git a/test/act-mapping/letter-spacing-not-important.json b/test/act-mapping/letter-spacing-not-important.json new file mode 100644 index 0000000000..24e56a23ff --- /dev/null +++ b/test/act-mapping/letter-spacing-not-important.json @@ -0,0 +1,5 @@ +{ + "id": "24afc2", + "title": "Letter spacing in style attributes is not !important", + "axeRules": ["avoid-inline-spacing"] +} diff --git a/test/act-mapping/line-height-not-important.json b/test/act-mapping/line-height-not-important.json new file mode 100644 index 0000000000..a0ee6f2e33 --- /dev/null +++ b/test/act-mapping/line-height-not-important.json @@ -0,0 +1,5 @@ +{ + "id": "78fd32", + "title": "Line height in style attributes is not !important", + "axeRules": ["avoid-inline-spacing"] +} diff --git a/test/act-mapping/word-spacing-not-important.json b/test/act-mapping/word-spacing-not-important.json new file mode 100644 index 0000000000..8e27c08a3c --- /dev/null +++ b/test/act-mapping/word-spacing-not-important.json @@ -0,0 +1,5 @@ +{ + "id": "9e45ec", + "title": "Word spacing in style attributes is not !important", + "axeRules": ["avoid-inline-spacing"] +} diff --git a/test/checks/shared/inline-style-property.js b/test/checks/shared/inline-style-property.js new file mode 100644 index 0000000000..a3be3a98e9 --- /dev/null +++ b/test/checks/shared/inline-style-property.js @@ -0,0 +1,460 @@ +describe('inline-style-property tests', function() { + 'use strict'; + var fixture = document.getElementById('fixture'); + var checkSetup = axe.testUtils.checkSetup; + var isIE11 = axe.testUtils.isIE11; + + afterEach(function() { + fixture.innerHTML = ''; + }); + + describe('important-letter-spacing check', function() { + var checkEvaluate = axe.testUtils.getCheckEvaluate( + 'important-letter-spacing' + ); + var checkContext = axe.testUtils.MockCheckContext(); + afterEach(function() { + checkContext.reset(); + }); + + it('is true when the property is not set in the style attribute', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.isNull(checkContext._data); + }); + + it('is false when letter-spacing is less than 0.12em and !important', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 0.1, + minValue: 0.12 + }); + }); + + it('is true when !important is not used', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.isNull(checkContext._data); + }); + + it('is true when letter-spacing is 0.15 times the font-size', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 0.15, + minValue: 0.12 + }); + }); + + it('uses the highest priority value if multiple are set', function() { + var style = [ + 'letter-spacing: 0.15em !important', + 'letter-spacing: 0.1em !important', + 'letter-spacing: 0.2em' + ].join('; '); + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 0.1, + minValue: 0.12 + }); + }); + + describe('handles different font-sizes', function() { + it('is true when the font is 0.15 time the spacing', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 0.15, + minValue: 0.12 + }); + }); + + it('is false when the font is 0.10 times the spacing', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 0.1, + minValue: 0.12 + }); + }); + }); + + describe('with non-number values', function() { + it('is false when `normal` (which is 0) is used along with !important', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 0, + minValue: 0.12 + }); + }); + + (isIE11 ? xit : it)( + 'is false when `initial` (meaning `normal`) is used along with !important', + function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 0, + minValue: 0.12 + }); + } + ); + + it('is true when `inherited` is used along with !important', function() { + var params = checkSetup( + '

' + + 'Hello world' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 'inherit', + minValue: 0.12 + }); + }); + + (isIE11 ? xit : it)( + 'is true when `unset` is used along with !important', + function() { + var params = checkSetup( + '

' + + 'Hello world' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 'unset', + minValue: 0.12 + }); + } + ); + + (isIE11 ? xit : it)( + 'is true when `revert` is used along with !important', + function() { + var params = checkSetup( + '

' + + 'Hello world' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 'revert', + minValue: 0.12 + }); + } + ); + + (isIE11 ? xit : it)( + 'is true when `revert-layer` is used along with !important', + function() { + var params = checkSetup( + '

' + + 'Hello world' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 'revert-layer', + minValue: 0.12 + }); + } + ); + }); + }); + + describe('important-word-spacing check', function() { + var checkEvaluate = axe.testUtils.getCheckEvaluate( + 'important-word-spacing' + ); + var checkContext = axe.testUtils.MockCheckContext(); + afterEach(function() { + checkContext.reset(); + }); + + it('is true when word-spacing is not set in the style attribute', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.isNull(checkContext._data); + }); + + it('is true when below 0.16em and not !important', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.isNull(checkContext._data); + }); + + it('is false when below 0.16em and !important', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 0.1, + minValue: 0.16 + }); + }); + + it('is true when 0.16em and !important', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 0.16, + minValue: 0.16 + }); + }); + }); + + describe('important-line-height check', function() { + var checkEvaluate = axe.testUtils.getCheckEvaluate('important-line-height'); + var checkContext = axe.testUtils.MockCheckContext(); + afterEach(function() { + checkContext.reset(); + }); + + it('is true when line-height is not set in the style attribute', function() { + var params = checkSetup( + '

Hello world

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.isNull(checkContext._data); + }); + + it('is true when below 1.5em and not !important', function() { + var params = checkSetup( + '

' + + ' The toy brought back fond memories of being lost in the rain forest.' + + '

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.isNull(checkContext._data); + }); + + (isIE11 ? xit : it)('is false when below 1.5em and !important', function() { + var params = checkSetup( + '

' + + ' The toy brought back fond memories of being lost in the rain forest.' + + '

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 1.2, + minValue: 1.5 + }); + }); + + it('is true when 1.5em and !important', function() { + var params = checkSetup( + '

' + + ' The toy brought back fond memories of being lost in the rain forest.' + + '

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 1.5, + minValue: 1.5 + }); + }); + + it('returns the 1em for `normal !important`', function() { + var params = checkSetup( + '

' + + ' The toy brought back fond memories of being lost in the rain forest.' + + '

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 1, + minValue: 1.5 + }); + }); + + it('is true for single line texts', function() { + var params = checkSetup( + '

' + + ' Short' + + '

' + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.isNull(checkContext._data); + }); + }); + + describe('With options configured for font-size', function() { + var checkEvaluate = axe.testUtils.getCheckEvaluate( + 'important-letter-spacing' + ); + var checkContext = axe.testUtils.MockCheckContext(); + var options = { + cssProperty: 'font-size', + minValue: 16, + maxValue: 42, + absoluteValues: true, + noImportant: false + }; + + afterEach(function() { + checkContext.reset(); + }); + + it('is false when !important and below the minValue', function() { + var params = checkSetup( + '

Hello world

', + options + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 12, + minValue: 16, + maxValue: 42 + }); + }); + + it('is false when !important and above the maxValue', function() { + var params = checkSetup( + '

Hello world

', + options + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 43, + minValue: 16, + maxValue: 42 + }); + }); + + it('is true when not !important', function() { + var params = checkSetup( + '

Hello world

', + options + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.isNull(checkContext._data); + }); + + it('is false when not !important and {noImportant: true}', function() { + var opt = Object.assign({}, options, { noImportant: true }); + var params = checkSetup( + '

Hello world

', + opt + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 12, + minValue: 16, + maxValue: 42 + }); + }); + + it('returns the normal value when `normal` is used', function() { + // Using line-height, since font-size cannot be normal + var options = { + cssProperty: 'line-height', + normalValue: 3, + minValue: 5 + }; + var params = checkSetup( + '

Hello world

', + options + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isFalse(result); + assert.deepEqual(checkContext._data, { + value: 3, + minValue: 5 + }); + }); + + it('is true when above the minValue', function() { + var params = checkSetup( + '

Hello world

', + options + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 16, + minValue: 16, + maxValue: 42 + }); + }); + + it('ignores minValue when not a number', function() { + var opt = Object.assign({}, options, { minValue: '16' }); + var params = checkSetup( + '

Hello world

', + opt + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 12, + maxValue: 42 + }); + }); + + it('ignores maxValue when not a number', function() { + var opt = Object.assign({}, options, { maxValue: '42' }); + var params = checkSetup( + '

Hello world

', + opt + ); + var result = checkEvaluate.apply(checkContext, params); + assert.isTrue(result); + assert.deepEqual(checkContext._data, { + value: 50, + minValue: 16 + }); + }); + }); +}); diff --git a/test/commons/dom/is-multiline.js b/test/commons/dom/is-multiline.js new file mode 100644 index 0000000000..86feeefc30 --- /dev/null +++ b/test/commons/dom/is-multiline.js @@ -0,0 +1,51 @@ +describe('dom.isMultiline', function() { + var isMultiline = axe.commons.dom.isMultiline; + var fixture = document.querySelector('#fixture'); + + afterEach(function() { + fixture.innerHTML = ''; + }); + + it('returns false if there is a single line', function() { + fixture.innerHTML = '

hello

'; + assert.isFalse(isMultiline(fixture.firstChild)); + }); + + it('returns true if there are two lines', function() { + fixture.innerHTML = '

hello
world

'; + assert.isTrue(isMultiline(fixture.firstChild)); + }); + + it('handles single-line texts with varying font-sizes', function() { + fixture.innerHTML = + '

' + + ' small ' + + ' large' + + ' medium' + + '

'; + assert.isFalse(isMultiline(fixture.firstChild)); + }); + + describe('with non-text elements', function() { + it('is true when on a multiple lines', function() { + fixture.innerHTML = + '

' + + '
' + + '
' + + ' ' + + '

'; + assert.isTrue(isMultiline(fixture.firstChild)); + }); + + it('is false when on a single line', function() { + fixture.innerHTML = + '

' + + ' Hello ' + + ' ' + + ' ' + + ' ' + + '

'; + assert.isFalse(isMultiline(fixture.firstChild)); + }); + }); +}); diff --git a/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.html b/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.html index 0ecd6ccc7b..ca46e9d864 100644 --- a/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.html +++ b/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.html @@ -1,6 +1,3 @@ - -

I am so blue I'm greener than purple.

-

I stepped on a Corn Flake, now I'm a Cereal Killer @@ -20,18 +17,41 @@ > Look, a distraction!

+

Banana
pass

+ +

Banana

+

+ We need more cheeeeeeessseeeee!!! +

+

+ The cheese grater is in the way! +

+ + +

Banana

+

Banana

+

Banana
pass

+

+ Banana
pass +

-

Banana error

-

+

Banana
error

+

We need more cheeeeeeessseeeee!!!

-

+

The cheese grater is in the way!

Yo Darth Vader

+ + +

I am so blue I'm greener than purple.

+

+ Some hidden text +

diff --git a/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.json b/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.json index 3265acd2d7..e9b04dbd03 100644 --- a/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.json +++ b/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.json @@ -2,5 +2,19 @@ "description": "avoid-inline-spacing tests", "rule": "avoid-inline-spacing", "violations": [["#fail1"], ["#fail2"], ["#fail3"], ["#fail4"]], - "passes": [["#pass1"], ["#pass2"], ["#pass3"], ["#pass4"], ["#pass5"]] + "passes": [ + ["#pass1"], + ["#pass2"], + ["#pass3"], + ["#pass4"], + ["#pass5"], + ["#pass6"], + ["#pass7"], + ["#pass8"], + ["#pass9"], + ["#pass10"], + ["#pass11"], + ["#pass12"], + ["#pass13"] + ] } diff --git a/test/rule-matches/is-visible-matches.js b/test/rule-matches/is-visible-matches.js new file mode 100644 index 0000000000..a08410496c --- /dev/null +++ b/test/rule-matches/is-visible-matches.js @@ -0,0 +1,31 @@ +describe('is-visible-matches', function() { + 'use strict'; + + var rule; + var fixture = document.getElementById('fixture'); + + beforeEach(function() { + fixture.innerHTML = ''; + rule = axe.utils.getRule('avoid-inline-spacing'); + }); + + it('returns true for visible elements', function() { + fixture.innerHTML = '

Hello world

'; + assert.isTrue(rule.matches(fixture.firstChild)); + }); + + it('returns false for elements with hidden', function() { + fixture.innerHTML = '' + assert.isFalse(rule.matches(fixture.firstChild)); + }); + + it('returns true for visible elements with aria-hidden="true"', function() { + fixture.innerHTML = '' + assert.isTrue(rule.matches(fixture.firstChild)); + }); + + it('returns false for opacity:0 elements with accessible text', function() { + fixture.innerHTML = '

Hello world

'; + assert.isFalse(rule.matches(fixture.firstChild)); + }); +});