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
' + + ' 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 = + '' +
+ '
' +
+ '
' +
+ ' ' +
+ '
' + + ' 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 = 'Hello world
' + 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)); + }); +});