bbc-a11y 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +9 -0
- data/CONTRIBUTING.md +70 -8
- data/GETTINGSTARTED.md +65 -0
- data/LICENSE +1 -1
- data/README.md +12 -55
- data/Rakefile +1 -1
- data/a11y.rb +1 -3
- data/bbc-a11y.gemspec +0 -1
- data/features/check_standards/01_core_purpose.feature +39 -0
- data/features/check_standards/02_validation.feature +1 -0
- data/features/check_standards/03_progressive_enhancement.feature +3 -0
- data/features/check_standards/{language.feature → 04_indicating_language.feature} +2 -2
- data/features/check_standards/05_page_titles.feature +1 -0
- data/features/check_standards/{main_landmark.feature → 06_main_landmark.feature} +6 -6
- data/features/check_standards/07_headings.feature +222 -0
- data/features/check_standards/{minimum_text_size.feature → 08_minimum_text_size.feature} +22 -2
- data/features/check_standards/09_resizable_text.feature +2 -0
- data/features/check_standards/{tab_index.feature → 10_tab_index.feature} +7 -7
- data/features/check_standards/{title_attribute.feature → 11_title_attributes.feature} +13 -5
- data/features/check_standards/{focusable_controls.feature → 12_focusable_controls.feature} +4 -4
- data/features/check_standards/13_visible_on_focus.feature +1 -0
- data/features/check_standards/14_control_styles.feature +1 -0
- data/features/check_standards/15_focus_styles.feature +1 -0
- data/features/check_standards/16_colour_contrast.feature +1 -0
- data/features/check_standards/17_colour_and_meaning.feature +1 -0
- data/features/check_standards/{image_alt.feature → 18_image_alternatives.feature} +5 -5
- data/features/check_standards/{form_labels.feature → 19_form_labels.feature} +7 -7
- data/features/check_standards/{form_interactions.feature → 20_form_interactions.feature} +6 -6
- data/features/check_standards/21_tables.feature +1 -0
- data/features/cli/display_failing_result.feature +14 -1
- data/features/cli/display_result_summary.feature +15 -1
- data/features/cli/provide_muting_tips.feature +1 -1
- data/features/cli/skipping_standards.feature +1 -1
- data/features/mute_errors.feature +2 -2
- data/features/step_definitions/steps.rb +10 -9
- data/features/support/web_server.rb +0 -11
- data/features/support/web_server/blank.html +7 -0
- data/features/support/web_server/two_headings_failures.html +11 -0
- data/karma.conf.js +1 -1
- data/lib/bbc/a11y/cli.rb +33 -6
- data/lib/bbc/a11y/js/bundle.js +139 -89
- data/lib/bbc/a11y/js/standards.js +67 -20
- data/lib/bbc/a11y/js/standards/{anchorsMustHaveHrefs.js → focusableControls/anchorsMustHaveHrefs.js} +0 -0
- data/lib/bbc/a11y/js/standards/{formsMustHaveSubmitButtons.js → formInteractions/formsMustHaveSubmitButtons.js} +0 -0
- data/lib/bbc/a11y/js/standards/{fieldsMustHaveLabelsOrTitles.js → formLabels/fieldsMustHaveLabelsOrTitles.js} +0 -0
- data/lib/bbc/a11y/js/standards/headings/contentMustFollowHeadings.js +18 -0
- data/lib/bbc/a11y/js/standards/{exactlyOneMainHeading.js → headings/exactlyOneMainHeading.js} +1 -1
- data/lib/bbc/a11y/js/standards/{headingsMustBeInAscendingOrder.js → headings/headingsMustBeInAscendingOrder.js} +0 -0
- data/lib/bbc/a11y/js/standards/{imagesMustHaveAltAttributes.js → imageAlternatives/imagesMustHaveAltAttributes.js} +0 -0
- data/lib/bbc/a11y/js/standards/{htmlMustHaveLangAttribute.js → indicatingLanguage/htmlMustHaveLangAttribute.js} +0 -0
- data/lib/bbc/a11y/js/standards/{exactlyOneMainLandmark.js → mainLandmark/exactlyOneMainLandmark.js} +0 -0
- data/lib/bbc/a11y/js/standards/{minimumTextSize.js → minimumTextSize/textCannotBeTooSmall.js} +3 -3
- data/lib/bbc/a11y/js/standards/{elementsWithZeroTabIndexMustBeFields.js → tabIndex/elementsWithZeroTabIndexMustBeFields.js} +0 -0
- data/lib/bbc/a11y/js/standards/{titleAttributesOnlyOnInputs.js → titleAttributes/titleAttributesOnlyOnInputs.js} +1 -1
- data/lib/bbc/a11y/linter.rb +16 -4
- data/lib/bbc/a11y/runner.rb +0 -1
- data/lib/bbc/a11y/string_colours.rb +15 -0
- data/lib/bbc/a11y/version +1 -1
- data/package.json +3 -0
- data/spec/bbc/a11y/js/a11ySpec.js +15 -6
- data/spec/bbc/a11y/js/minimumTextSizeStandardSpec.js +25 -0
- data/spec/bbc/a11y/js/standardsSpec.js +6 -6
- data/spec/bbc/a11y/string_colours_spec.rb +13 -0
- metadata +68 -102
- data/circle.yml +0 -3
- data/examples/bbc-pages/Gemfile +0 -3
- data/examples/bbc-pages/Rakefile +0 -3
- data/examples/bbc-pages/a11y.rb +0 -2
- data/examples/local-web-app/Gemfile +0 -4
- data/examples/local-web-app/Rakefile +0 -3
- data/examples/local-web-app/a11y.rb +0 -52
- data/examples/local-web-app/config.ru +0 -1
- data/examples/local-web-app/public/missing_header.html +0 -13
- data/examples/local-web-app/public/perfect.html +0 -14
- data/examples/local-web-app/readme.md +0 -0
- data/features/check_standards/headings.feature +0 -153
- data/lib/bbc/a11y/js/standards/contentMustFollowHeadings.js +0 -15
- data/lib/bbc/a11y/standards.rb +0 -45
- data/lib/bbc/a11y/standards/anchor_hrefs.rb +0 -18
- data/lib/bbc/a11y/standards/content_follows_headings.rb +0 -22
- data/lib/bbc/a11y/standards/exactly_one_main_heading.rb +0 -25
- data/lib/bbc/a11y/standards/exactly_one_main_landmark.rb +0 -20
- data/lib/bbc/a11y/standards/form_labels.rb +0 -39
- data/lib/bbc/a11y/standards/form_submit_buttons.rb +0 -21
- data/lib/bbc/a11y/standards/heading_hierarchy.rb +0 -34
- data/lib/bbc/a11y/standards/image_alt.rb +0 -18
- data/lib/bbc/a11y/standards/language_attribute.rb +0 -19
- data/lib/bbc/a11y/standards/tab_index.rb +0 -22
- data/lib/bbc/a11y/standards/title_attribute.rb +0 -31
- data/standards/01_core-purpose.md +0 -24
- data/standards/02_validation.feature +0 -31
- data/standards/03_javascript.feature +0 -40
- data/standards/04_language.feature +0 -58
- data/standards/05_page_title.feature +0 -45
- data/standards/06_main_landmark.feature +0 -24
- data/standards/07_headings.feature +0 -65
- data/standards/08_title_attribute.feature +0 -71
- data/standards/09_tabindex.feature +0 -51
- data/standards/10_form_labels.feature +0 -88
- data/standards/11_visible-on-focus.md +0 -58
- data/standards/13_colour-contrast.md +0 -27
- data/standards/14_colour-meaning.md +0 -19
- data/standards/15_focusable-controls.md +0 -45
- data/standards/16_table.md +0 -109
- data/standards/17_control-styles.md +0 -78
- data/standards/18_focus-styles.md +0 -36
- data/standards/19_form-interactions.md +0 -33
- data/standards/20_image-alt.md +0 -34
- data/standards/21_min-font-sizes.md +0 -64
- data/standards/22_resize-zoom.md +0 -80
- data/standards/step_definitions/core_content_steps.rb +0 -3
- data/standards/step_definitions/form_steps.rb +0 -6
- data/standards/step_definitions/language_steps.rb +0 -21
- data/standards/step_definitions/page_steps.rb +0 -50
- data/standards/step_definitions/w3c_steps.rb +0 -7
- data/standards/support/capybara.rb +0 -53
- data/standards/support/skipper.rb +0 -5
- data/standards/support/world.rb +0 -3
- data/standards/support/world_extender.rb +0 -5
@@ -26,16 +26,5 @@ class WebServer
|
|
26
26
|
default
|
27
27
|
end
|
28
28
|
end
|
29
|
-
|
30
|
-
def write_page(path, html)
|
31
|
-
full_path = File.join(DOCUMENT_ROOT, path)
|
32
|
-
File.open(full_path, 'w') { |file| file.write(html) }
|
33
|
-
end
|
34
|
-
|
35
|
-
def delete_page(path)
|
36
|
-
full_path = File.join(DOCUMENT_ROOT, path)
|
37
|
-
File.delete(full_path)
|
38
|
-
rescue Errno::ENOENT
|
39
|
-
end
|
40
29
|
end
|
41
30
|
end
|
data/karma.conf.js
CHANGED
@@ -48,7 +48,7 @@ module.exports = function(config) {
|
|
48
48
|
|
49
49
|
// start these browsers
|
50
50
|
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
51
|
-
browsers: ['
|
51
|
+
browsers: ['PhantomJS'],
|
52
52
|
|
53
53
|
// Continuous Integration mode
|
54
54
|
// if true, Karma captures browsers, runs the tests and exits
|
data/lib/bbc/a11y/cli.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
require 'bbc/a11y/configuration'
|
3
3
|
require 'bbc/a11y/linter'
|
4
4
|
require 'bbc/a11y/runner'
|
5
|
+
require 'bbc/a11y/string_colours'
|
5
6
|
require 'open-uri'
|
6
7
|
require 'capybara'
|
7
|
-
require 'colorize'
|
8
8
|
|
9
9
|
module BBC
|
10
10
|
module A11y
|
@@ -28,7 +28,17 @@ module BBC
|
|
28
28
|
stdout.puts green("✓ #{page_settings.url}")
|
29
29
|
else
|
30
30
|
stdout.puts red("✗ #{page_settings.url}")
|
31
|
-
|
31
|
+
current_section = nil
|
32
|
+
current_name = nil
|
33
|
+
lint_result.errors.each do |error|
|
34
|
+
if error.section != current_section || error.name != current_name
|
35
|
+
humanised_section = humanise(error.section)
|
36
|
+
stdout.puts " * #{humanised_section}: #{error.name}"
|
37
|
+
end
|
38
|
+
stdout.puts " - #{error.message}"
|
39
|
+
current_section = error.section
|
40
|
+
current_name = error.name
|
41
|
+
end
|
32
42
|
end
|
33
43
|
stdout.puts ""
|
34
44
|
end
|
@@ -41,21 +51,28 @@ module BBC
|
|
41
51
|
]
|
42
52
|
stdout.puts(messages.join(', '))
|
43
53
|
@any_errors = summary.fail?
|
54
|
+
unless @any_errors
|
55
|
+
stdout.puts NO_ERRORS_MESSAGE
|
56
|
+
end
|
44
57
|
end
|
45
58
|
|
46
59
|
private
|
47
60
|
|
61
|
+
def humanise(word)
|
62
|
+
word.gsub(/[A-Z]/) { |a| ' ' + a }.gsub(/^\w/) { |w| w.upcase }
|
63
|
+
end
|
64
|
+
|
48
65
|
def red(message)
|
49
|
-
|
66
|
+
colourize_if_tty(message, :red)
|
50
67
|
end
|
51
68
|
|
52
69
|
def green(message)
|
53
|
-
|
70
|
+
colourize_if_tty(message, :green)
|
54
71
|
end
|
55
72
|
|
56
|
-
def
|
73
|
+
def colourize_if_tty(message, colour)
|
57
74
|
if tty?
|
58
|
-
message.
|
75
|
+
message.send(colour)
|
59
76
|
else
|
60
77
|
message
|
61
78
|
end
|
@@ -85,6 +102,16 @@ module BBC
|
|
85
102
|
|
86
103
|
HELP = %{
|
87
104
|
Usage: a11y [url]
|
105
|
+
}
|
106
|
+
|
107
|
+
NO_ERRORS_MESSAGE = %{
|
108
|
+
No errors found. But please remember:
|
109
|
+
|
110
|
+
"Testing shows the presence, not the absence of bugs" -- Edsger W. Dijkstra
|
111
|
+
|
112
|
+
I am only a robot. Always make time to perform manual testing using assistive
|
113
|
+
technologies like VoiceOver, JAWS and NVDA to make sure you're providing a good
|
114
|
+
user experience.
|
88
115
|
}
|
89
116
|
|
90
117
|
end
|
data/lib/bbc/a11y/js/bundle.js
CHANGED
@@ -18,20 +18,61 @@ function Standards(standards, skipped) {
|
|
18
18
|
this.skipped = skipped;
|
19
19
|
}
|
20
20
|
|
21
|
-
Standards.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
21
|
+
Standards.sections = {
|
22
|
+
|
23
|
+
focusableControls: [
|
24
|
+
require('./standards/focusableControls/anchorsMustHaveHrefs')
|
25
|
+
],
|
26
|
+
|
27
|
+
formInteractions: [
|
28
|
+
require('./standards/formInteractions/formsMustHaveSubmitButtons')
|
29
|
+
],
|
30
|
+
|
31
|
+
formLabels: [
|
32
|
+
require('./standards/formLabels/fieldsMustHaveLabelsOrTitles')
|
33
|
+
],
|
34
|
+
|
35
|
+
headings: [
|
36
|
+
require('./standards/headings/contentMustFollowHeadings'),
|
37
|
+
require('./standards/headings/exactlyOneMainHeading'),
|
38
|
+
require('./standards/headings/headingsMustBeInAscendingOrder')
|
39
|
+
],
|
40
|
+
|
41
|
+
imageAlternatives: [
|
42
|
+
require('./standards/imageAlternatives/imagesMustHaveAltAttributes'),
|
43
|
+
],
|
44
|
+
|
45
|
+
indicatingLanguage: [
|
46
|
+
require('./standards/indicatingLanguage/htmlMustHaveLangAttribute')
|
47
|
+
],
|
48
|
+
|
49
|
+
mainLandmark: [
|
50
|
+
require('./standards/mainLandmark/exactlyOneMainLandmark')
|
51
|
+
],
|
52
|
+
|
53
|
+
minimumTextSize: [
|
54
|
+
require('./standards/minimumTextSize/textCannotBeTooSmall')
|
55
|
+
],
|
56
|
+
|
57
|
+
tabIndex: [
|
58
|
+
require('./standards/tabIndex/elementsWithZeroTabIndexMustBeFields')
|
59
|
+
],
|
60
|
+
|
61
|
+
titleAttributes: [
|
62
|
+
require('./standards/titleAttributes/titleAttributesOnlyOnInputs')
|
63
|
+
]
|
64
|
+
|
65
|
+
}
|
66
|
+
|
67
|
+
Standards.all = [];
|
68
|
+
|
69
|
+
for (var section in Standards.sections) {
|
70
|
+
var sectionStandards = Standards.sections[section];
|
71
|
+
for (var i = 0; i < sectionStandards.length; ++i) {
|
72
|
+
sectionStandards[i].section = section;
|
73
|
+
Standards.all.push(sectionStandards[i]);
|
74
|
+
}
|
75
|
+
}
|
35
76
|
|
36
77
|
Standards.prototype.validate = function(jquery) {
|
37
78
|
var results = [];
|
@@ -41,7 +82,13 @@ Standards.prototype.validate = function(jquery) {
|
|
41
82
|
}
|
42
83
|
for (var i = 0; i < this.standards.length; ++i) {
|
43
84
|
standard = this.standards[i];
|
44
|
-
standardResult = {
|
85
|
+
standardResult = {
|
86
|
+
standard: {
|
87
|
+
section: this.standards[i].section,
|
88
|
+
name: this.standards[i].name
|
89
|
+
},
|
90
|
+
errors: []
|
91
|
+
};
|
45
92
|
standard.validate(jquery, fail);
|
46
93
|
results.push(standardResult);
|
47
94
|
}
|
@@ -62,15 +109,15 @@ Standards.matching = function(criteria) {
|
|
62
109
|
function standardsMatching(criteria) {
|
63
110
|
var skips = criteria.skip || [];
|
64
111
|
for (var i = 0; i < skips.length; ++i) {
|
65
|
-
skips[i] =
|
112
|
+
skips[i] = normaliseStandardName(skips[i]);
|
66
113
|
}
|
67
114
|
var isOnly = typeof(criteria.only) == 'string';
|
68
|
-
var only = isOnly ?
|
115
|
+
var only = isOnly ? normaliseStandardName(criteria.only) : null;
|
69
116
|
var matches = [];
|
70
117
|
var skipped = [];
|
71
118
|
for (var i = 0; i < Standards.all.length; ++i) {
|
72
119
|
var standard = Standards.all[i];
|
73
|
-
var name =
|
120
|
+
var name = standard.section.toLowerCase() + normaliseStandardName(standard.name);
|
74
121
|
if (isOnly) {
|
75
122
|
if (only == name) {
|
76
123
|
matches.push(standard);
|
@@ -86,13 +133,13 @@ function standardsMatching(criteria) {
|
|
86
133
|
return { matches: matches, skipped: skipped };
|
87
134
|
}
|
88
135
|
|
89
|
-
function
|
90
|
-
return name.replace(/\
|
136
|
+
function normaliseStandardName(name) {
|
137
|
+
return name.replace(/\W+/g, '').toLowerCase();
|
91
138
|
}
|
92
139
|
|
93
140
|
module.exports = Standards;
|
94
141
|
|
95
|
-
},{"./standards/anchorsMustHaveHrefs":3,"./standards/
|
142
|
+
},{"./standards/focusableControls/anchorsMustHaveHrefs":3,"./standards/formInteractions/formsMustHaveSubmitButtons":4,"./standards/formLabels/fieldsMustHaveLabelsOrTitles":5,"./standards/headings/contentMustFollowHeadings":6,"./standards/headings/exactlyOneMainHeading":7,"./standards/headings/headingsMustBeInAscendingOrder":8,"./standards/imageAlternatives/imagesMustHaveAltAttributes":9,"./standards/indicatingLanguage/htmlMustHaveLangAttribute":10,"./standards/mainLandmark/exactlyOneMainLandmark":11,"./standards/minimumTextSize/textCannotBeTooSmall":12,"./standards/tabIndex/elementsWithZeroTabIndexMustBeFields":13,"./standards/titleAttributes/titleAttributesOnlyOnInputs":14,"./xpath":15}],3:[function(require,module,exports){
|
96
143
|
module.exports = {
|
97
144
|
name: 'Anchors must have hrefs',
|
98
145
|
|
@@ -104,65 +151,20 @@ module.exports = {
|
|
104
151
|
}
|
105
152
|
|
106
153
|
},{}],4:[function(require,module,exports){
|
107
|
-
var headingSelector = 'h1, h2, h3, h4, h5, h6, h7, h8';
|
108
|
-
|
109
154
|
module.exports = {
|
110
|
-
name: '
|
155
|
+
name: 'Forms must have submit buttons',
|
111
156
|
|
112
157
|
validate: function($, fail) {
|
113
|
-
$(
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
fail("No content follows:", heading);
|
158
|
+
$("form").each(function(index, form) {
|
159
|
+
var submits = $(form).find("input[type=submit], button[type=submit]");
|
160
|
+
if (submits.length == 0) {
|
161
|
+
fail('Form has no submit button:', form);
|
118
162
|
}
|
119
163
|
});
|
120
164
|
}
|
121
165
|
}
|
122
166
|
|
123
167
|
},{}],5:[function(require,module,exports){
|
124
|
-
module.exports = {
|
125
|
-
name: 'Elements with zero tab index must be fields',
|
126
|
-
|
127
|
-
validate: function($, fail) {
|
128
|
-
var baddies = $("*[tabindex='0']:not(input, button, select, textarea, a)");
|
129
|
-
baddies.each(function(index, el) {
|
130
|
-
fail('Non-field element with tabindex=0:', el);
|
131
|
-
});
|
132
|
-
}
|
133
|
-
}
|
134
|
-
|
135
|
-
},{}],6:[function(require,module,exports){
|
136
|
-
module.exports = {
|
137
|
-
name: 'Exactly one main heading',
|
138
|
-
|
139
|
-
validate: function($, fail) {
|
140
|
-
var mainHeadings = $('h1:visible');
|
141
|
-
var count = mainHeadings.length;
|
142
|
-
if (count == 0) {
|
143
|
-
fail('Found 0 h1 elements.');
|
144
|
-
} else if (count > 1) {
|
145
|
-
fail('Found ' + count + ' h1 elements:', mainHeadings);
|
146
|
-
}
|
147
|
-
}
|
148
|
-
}
|
149
|
-
|
150
|
-
},{}],7:[function(require,module,exports){
|
151
|
-
module.exports = {
|
152
|
-
name: 'Exactly one main landmark',
|
153
|
-
|
154
|
-
validate: function($, fail) {
|
155
|
-
var mainLandmarks = $("[role='main']");
|
156
|
-
var count = mainLandmarks.length;
|
157
|
-
if (count == 0) {
|
158
|
-
fail('Found 0 elements with role="main".');
|
159
|
-
} else if (count > 1) {
|
160
|
-
fail('Found ' + count + ' elements with role="main":', mainLandmarks);
|
161
|
-
}
|
162
|
-
}
|
163
|
-
}
|
164
|
-
|
165
|
-
},{}],8:[function(require,module,exports){
|
166
168
|
function hasIdOrLabel(field) {
|
167
169
|
return hasId(field) || hasLabel(field);
|
168
170
|
}
|
@@ -190,21 +192,42 @@ module.exports = {
|
|
190
192
|
}
|
191
193
|
}
|
192
194
|
|
193
|
-
},{}],
|
195
|
+
},{}],6:[function(require,module,exports){
|
196
|
+
var headingSelector = 'h1, h2, h3, h4, h5, h6, h7, h8';
|
197
|
+
|
194
198
|
module.exports = {
|
195
|
-
name: '
|
199
|
+
name: 'Content must follow headings',
|
196
200
|
|
197
201
|
validate: function($, fail) {
|
198
|
-
$(
|
199
|
-
var
|
200
|
-
|
201
|
-
|
202
|
+
$(headingSelector).each(function(index, heading) {
|
203
|
+
var nextSibling = heading.nextSibling;
|
204
|
+
while (nextSibling) {
|
205
|
+
if ($(nextSibling).is(heading.tagName)) { break; }
|
206
|
+
if ($(nextSibling).text().trim().length > 0) { return; }
|
207
|
+
nextSibling = nextSibling.nextSibling ?
|
208
|
+
nextSibling.nextSibling : (nextSibling.parentNode && nextSibling.parentNode.nextSibling);
|
202
209
|
}
|
210
|
+
fail("No content follows:", heading);
|
203
211
|
});
|
204
212
|
}
|
205
213
|
}
|
206
214
|
|
207
|
-
},{}],
|
215
|
+
},{}],7:[function(require,module,exports){
|
216
|
+
module.exports = {
|
217
|
+
name: 'Exactly one main heading',
|
218
|
+
|
219
|
+
validate: function($, fail) {
|
220
|
+
var mainHeadings = $('h1:visible');
|
221
|
+
var count = mainHeadings.length;
|
222
|
+
if (count == 0) {
|
223
|
+
fail('Found 0 h1 elements.');
|
224
|
+
} else if (count > 1) {
|
225
|
+
fail('Found ' + count + ' h1 elements:', mainHeadings);
|
226
|
+
}
|
227
|
+
}
|
228
|
+
}
|
229
|
+
|
230
|
+
},{}],8:[function(require,module,exports){
|
208
231
|
module.exports = {
|
209
232
|
name: 'Headings must be in ascending order',
|
210
233
|
|
@@ -237,7 +260,18 @@ function range(a, i, n) {
|
|
237
260
|
return r
|
238
261
|
}
|
239
262
|
|
240
|
-
},{}],
|
263
|
+
},{}],9:[function(require,module,exports){
|
264
|
+
module.exports = {
|
265
|
+
name: 'Images must have alt attributes',
|
266
|
+
|
267
|
+
validate: function($, fail) {
|
268
|
+
$("img:not([alt])").each(function(index, img) {
|
269
|
+
fail('Image has no alt attribute:', img);
|
270
|
+
});
|
271
|
+
}
|
272
|
+
}
|
273
|
+
|
274
|
+
},{}],10:[function(require,module,exports){
|
241
275
|
module.exports = {
|
242
276
|
name: 'Html must have lang attribute',
|
243
277
|
|
@@ -248,24 +282,28 @@ module.exports = {
|
|
248
282
|
}
|
249
283
|
}
|
250
284
|
|
251
|
-
},{}],
|
285
|
+
},{}],11:[function(require,module,exports){
|
252
286
|
module.exports = {
|
253
|
-
name: '
|
287
|
+
name: 'Exactly one main landmark',
|
254
288
|
|
255
289
|
validate: function($, fail) {
|
256
|
-
$("
|
257
|
-
|
258
|
-
|
290
|
+
var mainLandmarks = $("[role='main']");
|
291
|
+
var count = mainLandmarks.length;
|
292
|
+
if (count == 0) {
|
293
|
+
fail('Found 0 elements with role="main".');
|
294
|
+
} else if (count > 1) {
|
295
|
+
fail('Found ' + count + ' elements with role="main":', mainLandmarks);
|
296
|
+
}
|
259
297
|
}
|
260
298
|
}
|
261
299
|
|
262
|
-
},{}],
|
300
|
+
},{}],12:[function(require,module,exports){
|
263
301
|
module.exports = {
|
264
|
-
name: '
|
302
|
+
name: 'Text cannot be too small',
|
265
303
|
|
266
304
|
validate: function($, fail) {
|
267
|
-
var textNodes = $('*:not(head, script, style)').contents().filter(function() {
|
268
|
-
return this.nodeType === 3;
|
305
|
+
var textNodes = $('*:not(head, script, style):visible').contents().filter(function() {
|
306
|
+
return this.nodeType === 3 && this.textContent.trim().length > 0;
|
269
307
|
});
|
270
308
|
|
271
309
|
var parents = [];
|
@@ -286,12 +324,24 @@ module.exports = {
|
|
286
324
|
}
|
287
325
|
}
|
288
326
|
|
327
|
+
},{}],13:[function(require,module,exports){
|
328
|
+
module.exports = {
|
329
|
+
name: 'Elements with zero tab index must be fields',
|
330
|
+
|
331
|
+
validate: function($, fail) {
|
332
|
+
var baddies = $("*[tabindex='0']:not(input, button, select, textarea, a)");
|
333
|
+
baddies.each(function(index, el) {
|
334
|
+
fail('Non-field element with tabindex=0:', el);
|
335
|
+
});
|
336
|
+
}
|
337
|
+
}
|
338
|
+
|
289
339
|
},{}],14:[function(require,module,exports){
|
290
340
|
module.exports = {
|
291
341
|
name: 'Title attributes only on inputs',
|
292
342
|
|
293
343
|
validate: function($, fail) {
|
294
|
-
$("[title]:not(input, button, textarea, select)").each(function(index, element) {
|
344
|
+
$("[title]:not(input, button, textarea, select):visible").each(function(index, element) {
|
295
345
|
fail('Non-input element has title attribute:', element);
|
296
346
|
});
|
297
347
|
}
|