Skip to content

Commit 8597639

Browse files
authored
Merge pull request #1 from jlepinski/feature/wcag-rule-38
Feature/wcag rule 38
2 parents 7576c35 + c401621 commit 8597639

13 files changed

+360
-3
lines changed

lib/messages.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ var errors = {
5959
E055: 'line contains trailing whitespace',
6060
E056: 'expected from <%= expectedMin %> to <%= expectedMax %> levels of indentation. <%= value %> levels instead',
6161
E057: 'tag has missing or empty attributes',
62-
E058: 'rel="noopener" required for links with target="blank"'
62+
E058: 'rel="noopener" required for links with target="blank"',
63+
E059: 'WCAG rule 38: links should have at least 4 chars <%= content %> is of length <%= length %>',
64+
E060: 'WCAG rule 77: Input elements where type=[button|submit|reset] must have a value or title attribute.',
65+
E061: 'WCAG rule 78: Each button element must contain content.',
66+
E062: 'WCAG rule 74: The label element should not encapsulate select and textarea elements.',
67+
E063: 'WCAG rule 73: Each fieldset element should contain a legend element.'
6368
};
6469

6570
module.exports.errors = {};

lib/presets/accessibility.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,10 @@ module.exports = {
66
'page-title': true,
77
'table-req-caption': true,
88
'table-req-header': true,
9-
'tag-name-match':true
9+
'tag-name-match':true,
10+
'link-min-length-4': true,
11+
'input-btn-req-value-or-title': true,
12+
'button-req-content': true,
13+
'label-no-enc-textarea-or-select': true,
14+
'fieldset-contains-legend': true
1015
};

lib/presets/default.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,10 @@ module.exports = {
6464
'input-req-label': false,
6565
'table-req-caption': false,
6666
'table-req-header': false,
67-
'tag-req-attr': false
67+
'tag-req-attr': false,
68+
'link-min-length-4': false,
69+
'input-btn-req-value-or-title': false,
70+
'button-req-content': false,
71+
'label-no-enc-textarea-or-select': false,
72+
'fieldset-contains-legend': false
6873
};

lib/rules/button-req-content.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
var Issue = require('../issue');
2+
3+
module.exports = {
4+
name: 'button-req-content',
5+
on: ['tag'],
6+
filter: ['button'],
7+
desc: [
8+
'a rule from WCAG',
9+
'http://oaa-accessibility.org/wcag20/rule/78/',
10+
'Each button element must contain content'
11+
].join('\n')
12+
};
13+
14+
module.exports.lint = function (element, opts) {
15+
return element.children.length===0
16+
? new Issue('E061', element.openLineCol)
17+
: [];
18+
};

lib/rules/fieldset-contains-legend.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
var Issue = require('../issue');
2+
var lodash = require('lodash');
3+
4+
module.exports = {
5+
name: 'fieldset-contains-legend',
6+
on: ['tag'],
7+
filter: ['fieldset'],
8+
desc: [
9+
'a rule from WCAG',
10+
'http://oaa-accessibility.org/wcag20/rule/73/',
11+
'Each fieldset element should contain a legend element.'
12+
].join('\n')
13+
};
14+
15+
module.exports.lint = function (element, opts) {
16+
var hasLegend = (element) => {
17+
if(element.name==="legend"){
18+
return true;
19+
}
20+
else {
21+
return false;
22+
}
23+
}
24+
return lodash.some(element.children,hasLegend)
25+
? []
26+
: new Issue('E063', element.openLineCol);
27+
};
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
var Issue = require('../issue');
2+
var lodash = require('lodash');
3+
4+
module.exports = {
5+
name: 'input-btn-req-value-or-title',
6+
on: ['tag'],
7+
filter: ['input'],
8+
desc: [
9+
'a rule from WCAG',
10+
'http://oaa-accessibility.org/wcag20/rule/77/',
11+
'Input elements where type=[button|submit|reset] must have a value or title attribute.'
12+
].join('\n')
13+
};
14+
15+
module.exports.lint = function (element, opts) {
16+
if (!element.attribs.type || lodash.indexOf(["button","submit","reset"],element.attribs.type.value) === -1) {
17+
return [];
18+
}
19+
return !(element.attribs.value || element.attribs.title)
20+
? new Issue('E060', element.openLineCol)
21+
: [];
22+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
var Issue = require('../issue');
2+
var lodash = require('lodash');
3+
4+
module.exports = {
5+
name: 'label-no-enc-textarea-or-select',
6+
on: ['tag'],
7+
filter: ['label'],
8+
desc: [
9+
'a rule from WCAG',
10+
'http://oaa-accessibility.org/wcag20/rule/74/',
11+
'The label element should not encapsulate select and textarea elements.'
12+
].join('\n')
13+
};
14+
15+
module.exports.lint = function (element, opts) {
16+
var itterateTree = (element) => {
17+
if(element.name==="select" || element.name==='textarea'){
18+
return true;
19+
}
20+
else {
21+
return lodash.some(element.children,itterateTree)
22+
}
23+
}
24+
return lodash.some(element.children,itterateTree)
25+
? new Issue('E062', element.openLineCol)
26+
: [];
27+
};

lib/rules/link-min-length-4.js

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
var Issue = require('../issue');
2+
var lodash = require('lodash');
3+
4+
module.exports = {
5+
name: 'link-min-length-4',
6+
on: ['tag'],
7+
filter: ['a'],
8+
desc: [
9+
'a rule from WCAG',
10+
'only triggers if the link has a link; only errors if length is between 4',
11+
'http://oaa-accessibility.org/wcag20/rule/38/',
12+
'Link text should be as least four 4 characters long'
13+
].join('\n')
14+
};
15+
16+
module.exports.lint = function (element, opts) {
17+
//it's not a link if it doesn't have an href
18+
19+
if (!element.attribs.href || !element.attribs.href.value) {
20+
return [];
21+
}
22+
23+
var textElements = lodash.filter(element.children, (node) => { return node.type == 'text' });
24+
var content = textElements.map((textElement) => { return textElement.data }).join('').trim();
25+
length = content.length
26+
return length < 4
27+
? new Issue('E059', element.openLineCol, { 'content': content, 'length': length })
28+
: [];
29+
};

test/functional/button-req-content.js

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
module.exports = [
2+
{
3+
desc: 'should pass when set to false',
4+
input: '<button type="button"></button>',
5+
opts: {
6+
'button-req-content': false
7+
},
8+
output: 0
9+
},
10+
{
11+
desc: 'should fail when set to true with no content',
12+
input: '<button type="button"></button>',
13+
opts: {
14+
'button-req-content': true
15+
},
16+
output: 1
17+
},
18+
{
19+
desc: 'should pass when set to true with content text',
20+
input: '<button type="button">CLICK ME</button>',
21+
opts: {
22+
'button-req-content': true
23+
},
24+
output: 0
25+
},
26+
{
27+
desc: 'should pass when set to true with space content',
28+
input: '<button type="button"> </button>',
29+
opts: {
30+
'button-req-content': true
31+
},
32+
output: 0
33+
},
34+
{
35+
desc: 'should pass when set to true with node content',
36+
input: '<button type="button"> <i class="click"></i> </button>',
37+
opts: {
38+
'button-req-content': true
39+
},
40+
output: 0
41+
}
42+
43+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module.exports = [
2+
{
3+
desc: 'should pass when set to false',
4+
input: '<fieldset><div>fieldcontent</div></fieldset>',
5+
opts: {
6+
'fieldset-contains-legend': false
7+
},
8+
output: 0
9+
},
10+
{
11+
desc: 'should fail when set to true',
12+
input: '<fieldset><div>fieldcontent</div></fieldset>',
13+
opts: {
14+
'fieldset-contains-legend': true
15+
},
16+
output: 1
17+
},
18+
{
19+
desc: 'should pass when set to true and contains a legend',
20+
input: '<fieldset><legend>theLegend</legend><div>fieldcontent</div></fieldset>',
21+
opts: {
22+
'fieldset-contains-legend': true
23+
},
24+
output: 0
25+
}
26+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
module.exports = [
2+
{
3+
desc: 'should pass when set to false',
4+
input: '<input type="button">',
5+
opts: {
6+
'input-btn-req-value-or-title': false
7+
},
8+
output: 0
9+
},
10+
{
11+
desc: 'should fail when set to true with type button',
12+
input: '<input type="button">',
13+
opts: {
14+
'input-btn-req-value-or-title': true
15+
},
16+
output: 1
17+
},
18+
{
19+
desc: 'should fail when set to true with type submit',
20+
input: '<input type="submit">',
21+
opts: {
22+
'input-btn-req-value-or-title': true
23+
},
24+
output: 1
25+
},
26+
{
27+
desc: 'should fail when set to true with type reset',
28+
input: '<input type="reset">',
29+
opts: {
30+
'input-btn-req-value-or-title': true
31+
},
32+
output: 1
33+
},
34+
{
35+
desc: 'should pass when type is not button submit or reset',
36+
input: '<input type="radio">',
37+
opts: {
38+
'input-btn-req-value-or-title': true
39+
},
40+
output: 0
41+
},
42+
{
43+
desc: 'should pass when there is a title',
44+
input: '<input type="button" title="blah">',
45+
opts: {
46+
'input-btn-req-value-or-title': true
47+
},
48+
output: 0
49+
},
50+
{
51+
desc: 'should pass when there is a value',
52+
input: '<input type="button" value="blah">',
53+
opts: {
54+
'input-btn-req-value-or-title': true
55+
},
56+
output: 0
57+
}
58+
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
module.exports = [
2+
{
3+
desc: 'should pass when set to false',
4+
input: '<label><select><option value="v1">V1</option><option value="v2">V2</option><option value="v3">V3</option></select></label>',
5+
opts: {
6+
'label-no-enc-textarea-or-select': false
7+
},
8+
output: 0
9+
},
10+
{
11+
desc: 'should fail when set to true with nested select',
12+
input: '<label><select><option value="v1">V1</option><option value="v2">V2</option><option value="v3">V3</option></select></label>',
13+
opts: {
14+
'label-no-enc-textarea-or-select': true
15+
},
16+
output: 1
17+
},
18+
{
19+
desc: 'should fail when set to true with nested textarea',
20+
input: '<label><textarea></textarea></label>',
21+
opts: {
22+
'label-no-enc-textarea-or-select': true
23+
},
24+
output: 1
25+
},
26+
{
27+
desc: 'should fail when set to true with 2nd level nested textarea',
28+
input: '<label><div><textarea></textarea><div></label>',
29+
opts: {
30+
'label-no-enc-textarea-or-select': true
31+
},
32+
output: 1
33+
},
34+
{
35+
desc: 'should fail when set to true with 2nd level nested select',
36+
input: '<label><div><select><option value="v1">V1</option><option value="v2">V2</option><option value="v3">V3</option></select></div></label>',
37+
opts: {
38+
'label-no-enc-textarea-or-select': true
39+
},
40+
output: 1
41+
},
42+
{
43+
desc: 'should pass when set to true with non nested textarea',
44+
input: '<label></label><textarea></textarea>',
45+
opts: {
46+
'label-no-enc-textarea-or-select': true
47+
},
48+
output: 0
49+
},
50+
{
51+
desc: 'should pass when set to true with non nested select',
52+
input: '<label></label><select><option value="v1">V1</option><option value="v2">V2</option></select>',
53+
opts: {
54+
'label-no-enc-textarea-or-select': true
55+
},
56+
output: 0
57+
}
58+
];

test/functional/link-min-length-4.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module.exports = [
2+
{
3+
desc: 'should pass when set to false',
4+
input: '<a href="/myHref >A</a>',
5+
opts: {
6+
'link-min-length-4': false
7+
},
8+
output: 0
9+
},
10+
{
11+
desc: 'should ignore a tags that don\'t have a href',
12+
input: '<a>A</a>',
13+
opts: {
14+
'link-min-length-4': true
15+
},
16+
output: 0
17+
},
18+
{
19+
desc: 'should fail when set to true',
20+
input: '<a href=\'/myHref\' >A</a>',
21+
opts: {
22+
'link-min-length-4': true
23+
},
24+
output: 1
25+
},
26+
{
27+
desc: 'if content length>=4 and has href there is no issue',
28+
input: '<a href=\'/myHref\' >Four</a>',
29+
opts: {
30+
'link-min-length-4': true
31+
},
32+
output: 0
33+
}
34+
];

0 commit comments

Comments
 (0)