diff --git a/doc/check-options.md b/doc/check-options.md index 17f5adac6c..392c1e02e1 100644 --- a/doc/check-options.md +++ b/doc/check-options.md @@ -415,8 +415,25 @@ h6:not([role]), { "size": 1.4 } ] - Common CSS values used to display `p` elements as `h1-h6` elements determining if a `p` element is being improperly repurposed + + + passLength + + +
"passLength": 1
+ + Relative length, if the the candidate heading is X times or greater the length of the candidate paragraph, it will pass. + + + + faiLength + + +
"failLength": 0.5
+ + Relative length, if the the candidate heading is X times or less the length of the candidate paragraph, it can fail. + diff --git a/doc/rule-descriptions.md b/doc/rule-descriptions.md index 372def1e2b..e145658a76 100644 --- a/doc/rule-descriptions.md +++ b/doc/rule-descriptions.md @@ -124,7 +124,7 @@ Rules we are still testing and developing. They are not enabled by default in ax | [label-content-name-mismatch](https://dequeuniversity.com/rules/axe/4.3/label-content-name-mismatch?application=RuleDescription) | Ensures that elements labelled through their content must have their visible text as part of their accessible name | Serious | cat.semantics, wcag21a, wcag253, experimental | failure | [2ee8b8](https://act-rules.github.io/rules/2ee8b8) | | [link-in-text-block](https://dequeuniversity.com/rules/axe/4.3/link-in-text-block?application=RuleDescription) | Links can be distinguished without relying on color | Serious | cat.color, experimental, wcag2a, wcag141 | failure, needs review | | | [no-autoplay-audio](https://dequeuniversity.com/rules/axe/4.3/no-autoplay-audio?application=RuleDescription) | Ensures <video> or <audio> elements do not autoplay audio for more than 3 seconds without a control mechanism to stop or mute the audio | Moderate | cat.time-and-media, wcag2a, wcag142, experimental | failure, needs review | [80f0bf](https://act-rules.github.io/rules/80f0bf) | -| [p-as-heading](https://dequeuniversity.com/rules/axe/4.3/p-as-heading?application=RuleDescription) | Ensure p elements are not used to style headings | Serious | cat.semantics, wcag2a, wcag131, experimental | failure | | +| [p-as-heading](https://dequeuniversity.com/rules/axe/4.3/p-as-heading?application=RuleDescription) | Ensure p elements are not used to style headings | Serious | cat.semantics, wcag2a, wcag131, experimental | failure, needs review | | | [table-fake-caption](https://dequeuniversity.com/rules/axe/4.3/table-fake-caption?application=RuleDescription) | Ensure that tables with a caption use the <caption> element. | Serious | cat.tables, experimental, wcag2a, wcag131, section508, section508.22.g | failure | | | [td-has-header](https://dequeuniversity.com/rules/axe/4.3/td-has-header?application=RuleDescription) | Ensure that each non-empty data cell in a large table has one or more table headers | Critical | cat.tables, experimental, wcag2a, wcag131, section508, section508.22.g | failure | | diff --git a/lib/checks/navigation/p-as-heading-evaluate.js b/lib/checks/navigation/p-as-heading-evaluate.js index 61a9a1f3d1..4a7e35917f 100644 --- a/lib/checks/navigation/p-as-heading-evaluate.js +++ b/lib/checks/navigation/p-as-heading-evaluate.js @@ -79,6 +79,16 @@ function pAsHeadingEvaluate(node, options, virtualNode) { const nextStyle = nextSibling ? getStyleValues(nextSibling) : null; const prevStyle = prevSibling ? getStyleValues(prevSibling) : null; + const optionsPassLength = options.passLength; + const optionsFailLength = options.failLength; + + const headingLength = node.textContent.trim().length; + const paragraphLength = nextSibling?.textContent.trim().length; + + if (headingLength > paragraphLength * optionsPassLength) { + return true; + } + if (!nextStyle || !isHeaderStyle(currStyle, nextStyle, margins)) { return true; } @@ -92,6 +102,9 @@ function pAsHeadingEvaluate(node, options, virtualNode) { return undefined; } + if (headingLength > paragraphLength * optionsFailLength) { + return undefined; + } return false; } diff --git a/lib/checks/navigation/p-as-heading.json b/lib/checks/navigation/p-as-heading.json index 38272045c6..bb7d78a91c 100644 --- a/lib/checks/navigation/p-as-heading.json +++ b/lib/checks/navigation/p-as-heading.json @@ -18,13 +18,16 @@ { "size": 1.4 } - ] + ], + "passLength": 1, + "failLength": 0.5 }, "metadata": { "impact": "serious", "messages": { "pass": "

elements are not styled as headings", - "fail": "Heading elements should be used instead of styled p elements" + "fail": "Heading elements should be used instead of styled

elements", + "incomplete": "Unable to determine if

elements are styled as headings" } } } diff --git a/test/checks/navigation/p-as-heading.js b/test/checks/navigation/p-as-heading.js index 97d1fde38b..40edbfef05 100644 --- a/test/checks/navigation/p-as-heading.js +++ b/test/checks/navigation/p-as-heading.js @@ -36,7 +36,7 @@ describe('p-as-heading', function() { it('returns false if the font-weight is heavier', function() { var params = checkSetup( - '

elm 1

' + '

elm 2

', + '

elm 1

' + '

elm 2elm 2

', testOptions ); assert.isFalse( @@ -46,7 +46,7 @@ describe('p-as-heading', function() { it('returns false if the font-size is bigger', function() { var params = checkSetup( - '

elm 1

elm 2

', + '

elm 1

elm 2elm 2

', testOptions ); assert.isFalse( @@ -56,7 +56,7 @@ describe('p-as-heading', function() { it('returns false if the fake heading is italic and the text is not', function() { var params = checkSetup( - '

elm 1

elm 2

', + '

elm 1

elm 2elm 2

', testOptions ); assert.isFalse( @@ -67,7 +67,7 @@ describe('p-as-heading', function() { it('returns true if both texts are bold, italic and larger', function() { var params = checkSetup( '

elm 1

' + - '

elm 2

', + '

elm 2elm 2

', testOptions ); assert.isTrue( @@ -77,7 +77,7 @@ describe('p-as-heading', function() { it('considers styles of elements inside the paragraph', function() { var params = checkSetup( - '

elm 1

elm 2

', + '

elm 1

elm 2elm 2

', testOptions ); assert.isFalse( @@ -87,7 +87,7 @@ describe('p-as-heading', function() { it('ignores empty child element for style', function() { var params = checkSetup( - '

elm 1

elm 2

', + '

elm 1

elm 2elm 2

', testOptions ); assert.isFalse( @@ -97,7 +97,7 @@ describe('p-as-heading', function() { it('considers styles of elements that do not contain all the text', function() { var params = checkSetup( - '

elm 1

elm 2

', + '

elm 1

elm 2elm 2

', testOptions ); assert.isTrue( @@ -108,7 +108,7 @@ describe('p-as-heading', function() { it('returns undefined instead of false if the element is inside a blockquote', function() { var params = checkSetup( '
' + - '

elm 1

elm 2

' + + '

elm 1

elm 2elm 2

' + '
', testOptions ); @@ -120,7 +120,7 @@ describe('p-as-heading', function() { it('returns true over undefined from within a blockquote', function() { var params = checkSetup( '
' + - '

elm 1

elm 2

' + + '

elm 1

elm 2elm 2

' + '
', testOptions ); @@ -141,12 +141,68 @@ describe('p-as-heading', function() { ); }); + it('returns true if the heading is greater than the paragraph', function() { + var params = checkSetup( + '

elm1elm1

' + '

elm2

', + testOptions + ); + assert.isTrue( + axe.testUtils.getCheckEvaluate('p-as-heading').apply(checkContext, params) + ); + }); + + it('returns undefined if the heading is twice as long but not greater than the length of the pararaph', function() { + var params = checkSetup( + '

elm1elm

' + '

elm2elm2

', + testOptions + ); + assert.isUndefined( + axe.testUtils.getCheckEvaluate('p-as-heading').apply(checkContext, params) + ); + }); + + describe('options.passLength and options.failLength', function() { + it('returns true if the heading is greater than the paragraph using options.passLength', function() { + var options = { + margins: [{ weight: 100 }, { italic: true }, { size: 1.2 }], + passLength: 2 + }; + + var params = checkSetup( + '

elm1elm1elm1

' + '

elm2

', + options + ); + assert.isTrue( + axe.testUtils + .getCheckEvaluate('p-as-heading') + .apply(checkContext, params) + ); + }); + + it('returns undefined if the heading is twice as long but not greater than the length of the pararaph using options.failLength ', function() { + var options = { + margins: [{ weight: 100 }, { italic: true }, { size: 1.2 }], + failLength: 0.6 + }; + var params = checkSetup( + '

elm1elm

' + + '

elm2elm2elm2

', + options + ); + assert.isFalse( + axe.testUtils + .getCheckEvaluate('p-as-heading') + .apply(checkContext, params) + ); + }); + }); + describe('option.margin', function() { it('passes if no margins are set', function() { var options = {}; var params = checkSetup( - '

elm 1

elm 2

', + '

elm 1

elm 2elm 2

', options ); assert.isTrue( @@ -162,7 +218,7 @@ describe('p-as-heading', function() { }; var params = checkSetup( - '

elm 1

elm 2

', + '

elm 1

elm 2elm 2

', options ); assert.isTrue( @@ -179,7 +235,7 @@ describe('p-as-heading', function() { var params = checkSetup( '

elm 1

' + - '

elm 2

', + '

elm 2elm 2

', options ); assert.isFalse( @@ -211,7 +267,8 @@ describe('p-as-heading', function() { }; var params = checkSetup( - '

elm 1

' + '

elm 2

', + '

elm 1

' + + '

elm 2elm 2

', options ); assert.isFalse( diff --git a/test/integration/rules/p-as-heading/p-as-heading.html b/test/integration/rules/p-as-heading/p-as-heading.html index 5ffd17d5a0..1be3c19860 100644 --- a/test/integration/rules/p-as-heading/p-as-heading.html +++ b/test/integration/rules/p-as-heading/p-as-heading.html @@ -43,29 +43,34 @@

A paragraph!

+
+

Hello World

+

Hello

+
+

Some text

-

A paragraph!

+

A paragraph, A paragraph!

Some text

-

A paragraph!

+

A paragraph, A paragraph!

Some text

-

A paragraph!

+

A paragraph, A paragraph!

Some text

Some text

-

A paragraph!

+

A paragraph, A paragraph!

diff --git a/test/integration/rules/p-as-heading/p-as-heading.json b/test/integration/rules/p-as-heading/p-as-heading.json index b8c106884e..5a2a189659 100644 --- a/test/integration/rules/p-as-heading/p-as-heading.json +++ b/test/integration/rules/p-as-heading/p-as-heading.json @@ -7,7 +7,8 @@ ["#passed2"], ["#passed3"], ["#passed4"], - ["#passed5"] + ["#passed5"], + ["#passed6"] ], "incomplete": [["#canttell1"], ["#canttell2"]] }