From 430b07fa36ac9f6932a0eb63fed1fb0524f26a02 Mon Sep 17 00:00:00 2001 From: Jey Date: Wed, 17 Apr 2019 11:13:14 +0100 Subject: [PATCH] feat(rule): Inline text spacing must be adjustable with custom stylesheets (#1446) Ensure text spacing is not affected by inline spacing styles that affects CSS specificity Closes issue: - https://github.com/dequelabs/axe-core/issues/1301 **Note: to make sure to make it clear in the failure message that this is due to limited support for custom stylesheets.** ## Reviewer checks **Required fields, to be filled out by PR reviewer(s)** - [x] Follows the commit message policy, appropriate for next version - [x] Has documentation updated, a DU ticket, or requires no documentation change - [x] Includes new tests, or was unnecessary - [x] Code is reviewed for security by: Steve --- doc/rule-descriptions.md | 1 + lib/checks/shared/avoid-inline-spacing.js | 18 +++ lib/checks/shared/avoid-inline-spacing.json | 11 ++ lib/rules/avoid-inline-spacing.json | 12 ++ test/checks/shared/avoid-inline-spacing.js | 121 ++++++++++++++++++ .../avoid-inline-spacing.html | 15 +++ .../avoid-inline-spacing.json | 6 + test/playground.html | 9 +- 8 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 lib/checks/shared/avoid-inline-spacing.js create mode 100644 lib/checks/shared/avoid-inline-spacing.json create mode 100644 lib/rules/avoid-inline-spacing.json create mode 100644 test/checks/shared/avoid-inline-spacing.js create mode 100644 test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.html create mode 100644 test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.json diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index b2b25f9810..c6bf76f40a 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -15,6 +15,7 @@ | aria-valid-attr | Ensures attributes that begin with aria- are valid ARIA attributes | Critical | cat.aria, wcag2a, wcag412 | true | | audio-caption | Ensures <audio> elements have captions | Critical | cat.time-and-media, wcag2a, wcag121, section508, section508.22.a | false | | autocomplete-valid | Ensure the autocomplete attribute is correct and suitable for the form field | Serious | cat.forms, wcag21aa, wcag135 | true | +| avoid-inline-spacing | Ensure that text spacing set through style attributes can be adjusted with custom stylesheets | Serious | wcag21, wcag1412 | true | | blink | Ensures <blink> elements are not used | Serious | cat.time-and-media, wcag2a, wcag222, section508, section508.22.j | true | | button-name | Ensures buttons have discernible text | Serious, Critical | cat.name-role-value, wcag2a, wcag412, section508, section508.22.a | true | | bypass | Ensures each page has at least one mechanism for a user to bypass navigation and jump straight to the content | Serious | cat.keyboard, wcag2a, wcag241, section508, section508.22.o | true | diff --git a/lib/checks/shared/avoid-inline-spacing.js b/lib/checks/shared/avoid-inline-spacing.js new file mode 100644 index 0000000000..ae25015b97 --- /dev/null +++ b/lib/checks/shared/avoid-inline-spacing.js @@ -0,0 +1,18 @@ +const inlineSpacingCssProperties = [ + 'line-height', + 'letter-spacing', + 'word-spacing' +]; + +const overriddenProperties = inlineSpacingCssProperties.filter(property => { + if (node.style.getPropertyPriority(property) === `important`) { + return property; + } +}); + +if (overriddenProperties.length > 0) { + this.data(overriddenProperties); + return false; +} + +return true; diff --git a/lib/checks/shared/avoid-inline-spacing.json b/lib/checks/shared/avoid-inline-spacing.json new file mode 100644 index 0000000000..74c1b5075a --- /dev/null +++ b/lib/checks/shared/avoid-inline-spacing.json @@ -0,0 +1,11 @@ +{ + "id": "avoid-inline-spacing", + "evaluate": "avoid-inline-spacing.js", + "metadata": { + "impact": "serious", + "messages": { + "pass": "No inline styles with '!important' that affect text spacing has been specified", + "fail": "Remove '!important' from inline style{{=it.data && it.data.length > 1 ? 's' : ''}} {{=it.data.join(', ')}}, as overriding this is not supported by most browsers" + } + } +} diff --git a/lib/rules/avoid-inline-spacing.json b/lib/rules/avoid-inline-spacing.json new file mode 100644 index 0000000000..95e8a5d46e --- /dev/null +++ b/lib/rules/avoid-inline-spacing.json @@ -0,0 +1,12 @@ +{ + "id": "avoid-inline-spacing", + "selector": "[style]", + "tags": ["wcag21", "wcag1412"], + "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"], + "any": [], + "none": [] +} diff --git a/test/checks/shared/avoid-inline-spacing.js b/test/checks/shared/avoid-inline-spacing.js new file mode 100644 index 0000000000..ff500bba19 --- /dev/null +++ b/test/checks/shared/avoid-inline-spacing.js @@ -0,0 +1,121 @@ +describe('avoid-inline-spacing tests', function() { + 'use strict'; + + var fixture = document.getElementById('fixture'); + var queryFixture = axe.testUtils.queryFixture; + var check = checks['avoid-inline-spacing']; + var checkContext = axe.testUtils.MockCheckContext(); + + afterEach(function() { + fixture.innerHTML = ''; + checkContext.reset(); + }); + + it('returns true when no inline spacing styles are specified', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isTrue(actual); + assert.isNull(checkContext._data); + }); + + it('returns true when inline spacing styles has invalid value', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isTrue(actual); + assert.isNull(checkContext._data); + }); + + it('returns true when inline spacing styles has invalid value and `!important` priority', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isTrue(actual); + assert.isNull(checkContext._data); + }); + + it('returns true when `line-height` style specified has no `!important` priority', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isTrue(actual); + assert.isNull(checkContext._data); + }); + + it('returns true when `letter-spacing` style specified has no `!important` priority', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isTrue(actual); + assert.isNull(checkContext._data); + }); + + it('returns true when `word-spacing` style specified has no `!important` priority', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isTrue(actual); + assert.isNull(checkContext._data); + }); + + it('returns true when none of the multiple inline spacing styles specified have priority of `!important`', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isTrue(actual); + assert.isNull(checkContext._data); + }); + + it('returns false when `line-height` style specified has `!important` priority', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isFalse(actual); + assert.deepEqual(checkContext._data, ['line-height']); + }); + + it('returns false when `letter-spacing` style specified has `!important` priority', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isFalse(actual); + assert.deepEqual(checkContext._data, ['letter-spacing']); + }); + + it('returns false when `word-spacing` style specified has `!important` priority', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isFalse(actual); + assert.deepEqual(checkContext._data, ['word-spacing']); + }); + + it('returns false when any of the multiple inline spacing styles specifies priority of `!important`', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isFalse(actual); + assert.deepEqual(checkContext._data, ['letter-spacing']); + }); + + it('returns false when multiple inline spacing styles specifies priority of `!important`', function() { + var vNode = queryFixture( + '

The quick brown fox jumped over the lazy dog

' + ); + var actual = check.evaluate.call(checkContext, vNode.actualNode); + assert.isFalse(actual); + assert.deepEqual(checkContext._data, ['line-height', 'letter-spacing']); + }); +}); diff --git a/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.html b/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.html new file mode 100644 index 0000000000..5b7b1df557 --- /dev/null +++ b/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.html @@ -0,0 +1,15 @@ + +

I am so blue I'm greener than purple.

+ + +

I stepped on a Corn Flake, now I'm a Cereal Killer

+

On a scale from one to ten what is your favourite colour of the alphabet.

+

The quick brown fox jumped over the lazy dog

+

A group of 24 Caterpillars have 694 legs

+

Look, a distraction!

+ + +

Banana error

+

We need more cheeeeeeessseeeee!!!

+

The cheese grater is in the way!

+

Yo Darth Vader

\ No newline at end of file diff --git a/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.json b/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.json new file mode 100644 index 0000000000..85beb3062e --- /dev/null +++ b/test/integration/rules/avoid-inline-spacing/avoid-inline-spacing.json @@ -0,0 +1,6 @@ +{ + "description": "avoid-inline-spacing tests", + "rule": "avoid-inline-spacing", + "violations": [["#fail1"], ["#fail2"], ["#fail3"], ["#fail4"]], + "passes": [["#pass1"], ["#pass2"], ["#pass3"], ["#pass4"], ["#pass5"]] +} diff --git a/test/playground.html b/test/playground.html index afcbbf9e76..3b00512f9e 100644 --- a/test/playground.html +++ b/test/playground.html @@ -2,12 +2,17 @@ O hai -
foo
+

The quick brown fox jumped over the lazy dog