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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +9 -0
  3. data/CONTRIBUTING.md +70 -8
  4. data/GETTINGSTARTED.md +65 -0
  5. data/LICENSE +1 -1
  6. data/README.md +12 -55
  7. data/Rakefile +1 -1
  8. data/a11y.rb +1 -3
  9. data/bbc-a11y.gemspec +0 -1
  10. data/features/check_standards/01_core_purpose.feature +39 -0
  11. data/features/check_standards/02_validation.feature +1 -0
  12. data/features/check_standards/03_progressive_enhancement.feature +3 -0
  13. data/features/check_standards/{language.feature → 04_indicating_language.feature} +2 -2
  14. data/features/check_standards/05_page_titles.feature +1 -0
  15. data/features/check_standards/{main_landmark.feature → 06_main_landmark.feature} +6 -6
  16. data/features/check_standards/07_headings.feature +222 -0
  17. data/features/check_standards/{minimum_text_size.feature → 08_minimum_text_size.feature} +22 -2
  18. data/features/check_standards/09_resizable_text.feature +2 -0
  19. data/features/check_standards/{tab_index.feature → 10_tab_index.feature} +7 -7
  20. data/features/check_standards/{title_attribute.feature → 11_title_attributes.feature} +13 -5
  21. data/features/check_standards/{focusable_controls.feature → 12_focusable_controls.feature} +4 -4
  22. data/features/check_standards/13_visible_on_focus.feature +1 -0
  23. data/features/check_standards/14_control_styles.feature +1 -0
  24. data/features/check_standards/15_focus_styles.feature +1 -0
  25. data/features/check_standards/16_colour_contrast.feature +1 -0
  26. data/features/check_standards/17_colour_and_meaning.feature +1 -0
  27. data/features/check_standards/{image_alt.feature → 18_image_alternatives.feature} +5 -5
  28. data/features/check_standards/{form_labels.feature → 19_form_labels.feature} +7 -7
  29. data/features/check_standards/{form_interactions.feature → 20_form_interactions.feature} +6 -6
  30. data/features/check_standards/21_tables.feature +1 -0
  31. data/features/cli/display_failing_result.feature +14 -1
  32. data/features/cli/display_result_summary.feature +15 -1
  33. data/features/cli/provide_muting_tips.feature +1 -1
  34. data/features/cli/skipping_standards.feature +1 -1
  35. data/features/mute_errors.feature +2 -2
  36. data/features/step_definitions/steps.rb +10 -9
  37. data/features/support/web_server.rb +0 -11
  38. data/features/support/web_server/blank.html +7 -0
  39. data/features/support/web_server/two_headings_failures.html +11 -0
  40. data/karma.conf.js +1 -1
  41. data/lib/bbc/a11y/cli.rb +33 -6
  42. data/lib/bbc/a11y/js/bundle.js +139 -89
  43. data/lib/bbc/a11y/js/standards.js +67 -20
  44. data/lib/bbc/a11y/js/standards/{anchorsMustHaveHrefs.js → focusableControls/anchorsMustHaveHrefs.js} +0 -0
  45. data/lib/bbc/a11y/js/standards/{formsMustHaveSubmitButtons.js → formInteractions/formsMustHaveSubmitButtons.js} +0 -0
  46. data/lib/bbc/a11y/js/standards/{fieldsMustHaveLabelsOrTitles.js → formLabels/fieldsMustHaveLabelsOrTitles.js} +0 -0
  47. data/lib/bbc/a11y/js/standards/headings/contentMustFollowHeadings.js +18 -0
  48. data/lib/bbc/a11y/js/standards/{exactlyOneMainHeading.js → headings/exactlyOneMainHeading.js} +1 -1
  49. data/lib/bbc/a11y/js/standards/{headingsMustBeInAscendingOrder.js → headings/headingsMustBeInAscendingOrder.js} +0 -0
  50. data/lib/bbc/a11y/js/standards/{imagesMustHaveAltAttributes.js → imageAlternatives/imagesMustHaveAltAttributes.js} +0 -0
  51. data/lib/bbc/a11y/js/standards/{htmlMustHaveLangAttribute.js → indicatingLanguage/htmlMustHaveLangAttribute.js} +0 -0
  52. data/lib/bbc/a11y/js/standards/{exactlyOneMainLandmark.js → mainLandmark/exactlyOneMainLandmark.js} +0 -0
  53. data/lib/bbc/a11y/js/standards/{minimumTextSize.js → minimumTextSize/textCannotBeTooSmall.js} +3 -3
  54. data/lib/bbc/a11y/js/standards/{elementsWithZeroTabIndexMustBeFields.js → tabIndex/elementsWithZeroTabIndexMustBeFields.js} +0 -0
  55. data/lib/bbc/a11y/js/standards/{titleAttributesOnlyOnInputs.js → titleAttributes/titleAttributesOnlyOnInputs.js} +1 -1
  56. data/lib/bbc/a11y/linter.rb +16 -4
  57. data/lib/bbc/a11y/runner.rb +0 -1
  58. data/lib/bbc/a11y/string_colours.rb +15 -0
  59. data/lib/bbc/a11y/version +1 -1
  60. data/package.json +3 -0
  61. data/spec/bbc/a11y/js/a11ySpec.js +15 -6
  62. data/spec/bbc/a11y/js/minimumTextSizeStandardSpec.js +25 -0
  63. data/spec/bbc/a11y/js/standardsSpec.js +6 -6
  64. data/spec/bbc/a11y/string_colours_spec.rb +13 -0
  65. metadata +68 -102
  66. data/circle.yml +0 -3
  67. data/examples/bbc-pages/Gemfile +0 -3
  68. data/examples/bbc-pages/Rakefile +0 -3
  69. data/examples/bbc-pages/a11y.rb +0 -2
  70. data/examples/local-web-app/Gemfile +0 -4
  71. data/examples/local-web-app/Rakefile +0 -3
  72. data/examples/local-web-app/a11y.rb +0 -52
  73. data/examples/local-web-app/config.ru +0 -1
  74. data/examples/local-web-app/public/missing_header.html +0 -13
  75. data/examples/local-web-app/public/perfect.html +0 -14
  76. data/examples/local-web-app/readme.md +0 -0
  77. data/features/check_standards/headings.feature +0 -153
  78. data/lib/bbc/a11y/js/standards/contentMustFollowHeadings.js +0 -15
  79. data/lib/bbc/a11y/standards.rb +0 -45
  80. data/lib/bbc/a11y/standards/anchor_hrefs.rb +0 -18
  81. data/lib/bbc/a11y/standards/content_follows_headings.rb +0 -22
  82. data/lib/bbc/a11y/standards/exactly_one_main_heading.rb +0 -25
  83. data/lib/bbc/a11y/standards/exactly_one_main_landmark.rb +0 -20
  84. data/lib/bbc/a11y/standards/form_labels.rb +0 -39
  85. data/lib/bbc/a11y/standards/form_submit_buttons.rb +0 -21
  86. data/lib/bbc/a11y/standards/heading_hierarchy.rb +0 -34
  87. data/lib/bbc/a11y/standards/image_alt.rb +0 -18
  88. data/lib/bbc/a11y/standards/language_attribute.rb +0 -19
  89. data/lib/bbc/a11y/standards/tab_index.rb +0 -22
  90. data/lib/bbc/a11y/standards/title_attribute.rb +0 -31
  91. data/standards/01_core-purpose.md +0 -24
  92. data/standards/02_validation.feature +0 -31
  93. data/standards/03_javascript.feature +0 -40
  94. data/standards/04_language.feature +0 -58
  95. data/standards/05_page_title.feature +0 -45
  96. data/standards/06_main_landmark.feature +0 -24
  97. data/standards/07_headings.feature +0 -65
  98. data/standards/08_title_attribute.feature +0 -71
  99. data/standards/09_tabindex.feature +0 -51
  100. data/standards/10_form_labels.feature +0 -88
  101. data/standards/11_visible-on-focus.md +0 -58
  102. data/standards/13_colour-contrast.md +0 -27
  103. data/standards/14_colour-meaning.md +0 -19
  104. data/standards/15_focusable-controls.md +0 -45
  105. data/standards/16_table.md +0 -109
  106. data/standards/17_control-styles.md +0 -78
  107. data/standards/18_focus-styles.md +0 -36
  108. data/standards/19_form-interactions.md +0 -33
  109. data/standards/20_image-alt.md +0 -34
  110. data/standards/21_min-font-sizes.md +0 -64
  111. data/standards/22_resize-zoom.md +0 -80
  112. data/standards/step_definitions/core_content_steps.rb +0 -3
  113. data/standards/step_definitions/form_steps.rb +0 -6
  114. data/standards/step_definitions/language_steps.rb +0 -21
  115. data/standards/step_definitions/page_steps.rb +0 -50
  116. data/standards/step_definitions/w3c_steps.rb +0 -7
  117. data/standards/support/capybara.rb +0 -53
  118. data/standards/support/skipper.rb +0 -5
  119. data/standards/support/world.rb +0 -3
  120. 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
@@ -0,0 +1,7 @@
1
+ <html lang="en-gb">
2
+ <head>
3
+ <title>Blank</title>
4
+ </head>
5
+ <body>
6
+ </body>
7
+ </html>
@@ -0,0 +1,11 @@
1
+ <html lang="en-gb">
2
+ <head>
3
+ <title>Two headings failures</title>
4
+ </head>
5
+
6
+ <body>
7
+ <p role="main">
8
+ <!-- there is no content following the heading below -->
9
+ <h2>This is the main heading, but it's not an H1</h2>
10
+ </body>
11
+ </html>
@@ -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: ['Chrome'],
51
+ browsers: ['PhantomJS'],
52
52
 
53
53
  // Continuous Integration mode
54
54
  // if true, Karma captures browsers, runs the tests and exits
@@ -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
- stdout.puts lint_result.errors.map { |error| " - #{error}" }.join("\n")
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
- colorize_if_tty(message, :red)
66
+ colourize_if_tty(message, :red)
50
67
  end
51
68
 
52
69
  def green(message)
53
- colorize_if_tty(message, :green)
70
+ colourize_if_tty(message, :green)
54
71
  end
55
72
 
56
- def colorize_if_tty(message, colour)
73
+ def colourize_if_tty(message, colour)
57
74
  if tty?
58
- message.colorize(colour)
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
@@ -18,20 +18,61 @@ function Standards(standards, skipped) {
18
18
  this.skipped = skipped;
19
19
  }
20
20
 
21
- Standards.all = [
22
- require('./standards/anchorsMustHaveHrefs'),
23
- require('./standards/contentMustFollowHeadings'),
24
- require('./standards/elementsWithZeroTabIndexMustBeFields'),
25
- require('./standards/exactlyOneMainHeading'),
26
- require('./standards/exactlyOneMainLandmark'),
27
- require('./standards/fieldsMustHaveLabelsOrTitles'),
28
- require('./standards/formsMustHaveSubmitButtons'),
29
- require('./standards/headingsMustBeInAscendingOrder'),
30
- require('./standards/htmlMustHaveLangAttribute'),
31
- require('./standards/imagesMustHaveAltAttributes'),
32
- require('./standards/minimumTextSize'),
33
- require('./standards/titleAttributesOnlyOnInputs')
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 = { standard: this.standards[i].name, errors: [] };
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] = normalise(skips[i]);
112
+ skips[i] = normaliseStandardName(skips[i]);
66
113
  }
67
114
  var isOnly = typeof(criteria.only) == 'string';
68
- var only = isOnly ? normalise(criteria.only) : null;
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 = normalise(standard.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 normalise(name) {
90
- return name.replace(/\s+/g, '').toLowerCase();
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/contentMustFollowHeadings":4,"./standards/elementsWithZeroTabIndexMustBeFields":5,"./standards/exactlyOneMainHeading":6,"./standards/exactlyOneMainLandmark":7,"./standards/fieldsMustHaveLabelsOrTitles":8,"./standards/formsMustHaveSubmitButtons":9,"./standards/headingsMustBeInAscendingOrder":10,"./standards/htmlMustHaveLangAttribute":11,"./standards/imagesMustHaveAltAttributes":12,"./standards/minimumTextSize":13,"./standards/titleAttributesOnlyOnInputs":14,"./xpath":15}],3:[function(require,module,exports){
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: 'Content must follow headings',
155
+ name: 'Forms must have submit buttons',
111
156
 
112
157
  validate: function($, fail) {
113
- $(headingSelector).each(function(index, heading) {
114
- if ($(heading.nextSibling).is(headingSelector) ||
115
- ($(heading.nextSibling).text().trim() == '' &&
116
- $(heading).next().is(headingSelector))) {
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
- },{}],9:[function(require,module,exports){
195
+ },{}],6:[function(require,module,exports){
196
+ var headingSelector = 'h1, h2, h3, h4, h5, h6, h7, h8';
197
+
194
198
  module.exports = {
195
- name: 'Forms must have submit buttons',
199
+ name: 'Content must follow headings',
196
200
 
197
201
  validate: function($, fail) {
198
- $("form").each(function(index, form) {
199
- var submits = $(form).find("input[type=submit], button[type=submit]");
200
- if (submits.length == 0) {
201
- fail('Form has no submit button:', form);
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
- },{}],10:[function(require,module,exports){
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
- },{}],11:[function(require,module,exports){
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
- },{}],12:[function(require,module,exports){
285
+ },{}],11:[function(require,module,exports){
252
286
  module.exports = {
253
- name: 'Images must have alt attributes',
287
+ name: 'Exactly one main landmark',
254
288
 
255
289
  validate: function($, fail) {
256
- $("img:not([alt])").each(function(index, img) {
257
- fail('Image has no alt attribute:', img);
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
- },{}],13:[function(require,module,exports){
300
+ },{}],12:[function(require,module,exports){
263
301
  module.exports = {
264
- name: 'Minimum text size',
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
  }