bbc-a11y 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/README.md +9 -11
  4. data/Rakefile +2 -2
  5. data/a11y.rb +5 -0
  6. data/bbc-a11y.gemspec +3 -5
  7. data/bin/a11y +2 -2
  8. data/examples/bbc-pages/a11y.rb +2 -6
  9. data/examples/local-web-app/Gemfile +1 -1
  10. data/examples/local-web-app/a11y.rb +10 -22
  11. data/features/check_standards/focusable_controls.feature +62 -0
  12. data/features/check_standards/form_interactions.feature +45 -0
  13. data/features/check_standards/form_labels.feature +55 -0
  14. data/features/check_standards/headings.feature +154 -0
  15. data/features/check_standards/image_alt.feature +39 -0
  16. data/features/check_standards/language.feature +46 -0
  17. data/features/check_standards/main_landmark.feature +39 -0
  18. data/features/check_standards/tab_index.feature +54 -0
  19. data/features/cli/display_failing_result.feature +10 -0
  20. data/features/{exit_status.feature → cli/exit_status.feature} +1 -2
  21. data/features/cli/provide_muting_tips.feature +25 -0
  22. data/features/cli/report_configuration_errors.feature +43 -0
  23. data/features/cli/skipping_standards.feature +15 -0
  24. data/features/cli/specify_url.feature +9 -0
  25. data/features/cli/specify_url_via_config.feature +13 -0
  26. data/features/mute_errors.feature +118 -0
  27. data/features/step_definitions/steps.rb +25 -1
  28. data/lib/bbc/a11y/cli.rb +32 -44
  29. data/lib/bbc/a11y/configuration.rb +56 -22
  30. data/lib/bbc/a11y/linter.rb +42 -0
  31. data/lib/bbc/a11y/standards.rb +43 -0
  32. data/lib/bbc/a11y/standards/anchor_hrefs.rb +18 -0
  33. data/lib/bbc/a11y/standards/content_follows_headings.rb +22 -0
  34. data/lib/bbc/a11y/standards/exactly_one_main_heading.rb +20 -0
  35. data/lib/bbc/a11y/standards/exactly_one_main_landmark.rb +20 -0
  36. data/lib/bbc/a11y/standards/form_labels.rb +39 -0
  37. data/lib/bbc/a11y/standards/form_submit_buttons.rb +21 -0
  38. data/lib/bbc/a11y/standards/heading_hierarchy.rb +34 -0
  39. data/lib/bbc/a11y/standards/image_alt.rb +18 -0
  40. data/lib/bbc/a11y/standards/language_attribute.rb +19 -0
  41. data/lib/bbc/a11y/standards/tab_index.rb +22 -0
  42. data/lib/bbc/a11y/version +1 -1
  43. data/spec/bbc/a11y/cli_spec.rb +22 -15
  44. data/spec/bbc/a11y/configuration_spec.rb +15 -40
  45. data/standards/support/capybara.rb +1 -2
  46. metadata +62 -81
  47. data/features/specify_url_via_cli.feature +0 -10
  48. data/features/specify_url_via_config.feature +0 -16
  49. data/lib/bbc/a11y.rb +0 -17
  50. data/lib/bbc/a11y/cucumber_runner.rb +0 -208
  51. data/lib/bbc/a11y/cucumber_support.rb +0 -56
  52. data/lib/bbc/a11y/cucumber_support/disabled_w3c.rb +0 -37
  53. data/lib/bbc/a11y/cucumber_support/heading_hierarchy.rb +0 -94
  54. data/lib/bbc/a11y/cucumber_support/language_detector.rb +0 -26
  55. data/lib/bbc/a11y/cucumber_support/matchers.rb +0 -21
  56. data/lib/bbc/a11y/cucumber_support/page.rb +0 -94
  57. data/lib/bbc/a11y/cucumber_support/per_page_checks.rb +0 -28
  58. data/lib/bbc/a11y/cucumber_support/w3c.rb +0 -36
  59. data/spec/bbc/a11y/cucumber_support/heading_hierarchy_spec.rb +0 -162
  60. data/spec/bbc/a11y/cucumber_support/matchers_spec.rb +0 -52
  61. data/spec/bbc/a11y/cucumber_support/page_spec.rb +0 -197
@@ -1,21 +0,0 @@
1
- require 'rspec/matchers'
2
- module BBC
3
- module A11y
4
- module CucumberSupport
5
- RSpec::Matchers.define :have_title_attribute_or_associated_label_tag do
6
- match do |element|
7
- if !element['title'].nil?
8
- return true
9
- end
10
- if id = element['id']
11
- return true if element.find_xpath("//*[for=#{id}]")
12
- end
13
- if element.find_xpath("..")[0].name == "label"
14
- return true
15
- end
16
- false
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,94 +0,0 @@
1
- require 'rspec/expectations'
2
- require 'capybara/rspec/matchers'
3
- require 'bbc/a11y/cucumber_support/heading_hierarchy'
4
-
5
- module BBC
6
- module A11y
7
- module CucumberSupport
8
-
9
- class Page
10
-
11
- include RSpec::Matchers
12
- include Capybara::RSpecMatchers
13
-
14
- def initialize(browser)
15
- @browser = browser
16
- end
17
-
18
- def title
19
- browser.title
20
- end
21
-
22
- def all_elements_matching(*selectors)
23
- results = selectors.map { |selector| browser.all(selector) }
24
- # A Capybara::Result looks like an array, but it's not. Turn them into plain
25
- # old nodes
26
- results.map { |result| result.map { |element| element } }.flatten
27
- end
28
-
29
- def must_have_lang_attribute
30
- expect(browser).to have_css('html[lang]')
31
- end
32
-
33
- def must_have_lang_attribute_of(expected_code)
34
- expect(browser.find('html')['lang'].split('-')[0]).to eq expected_code
35
- end
36
-
37
- def must_have_title
38
- expect(browser.title).not_to be_empty
39
- end
40
-
41
- def must_have_title_that_contains_h1_text
42
- expect(browser.title).to include(browser.find('h1').text)
43
- end
44
-
45
- def must_have_one_main_element
46
- expect(browser.all('[role="main"]').length).to eq 1
47
- end
48
-
49
- def must_have_one_h1
50
- expect(browser.all('h1', visible: false).length).to eq 1
51
- end
52
-
53
- def must_have_correct_heading_hierarchy
54
- heading_hierarchy.validate
55
- end
56
-
57
- def must_have_no_elements_with_title_attribute_content_repeated_within
58
- bad_nodes = browser.all('[title]').select { |node| node.text.include? node['title'] }
59
- expect(bad_nodes).to be_empty
60
- end
61
-
62
- def must_have_no_form_fields_with_label_and_title
63
- bad_nodes = browser.all('form *[id][title]').select { |node| browser.has_css?("label[for='#{node['id']}']") }
64
- expect(bad_nodes).to be_empty
65
- end
66
-
67
- def must_not_have_any_elements_with_tabindex_greater_than(max)
68
- bad_nodes = browser.all('[tabindex]').select { |node| node['tabindex'].to_i > max }
69
- expect(bad_nodes).to be_empty
70
- end
71
-
72
- def must_not_have_elements_with_tabindex(tabindex, except: [])
73
- nodes_with_tabindex = browser.all('[tabindex]').select { |node| node['tabindex'].to_i == tabindex }
74
- bad_nodes = nodes_with_tabindex.reject { |node| except.include? node.tag_name }
75
- expect(bad_nodes).to be_empty
76
- end
77
-
78
- def to_s
79
- browser.text
80
- end
81
-
82
- def heading_hierarchy
83
- HeadingHierarchy.new(browser)
84
- end
85
-
86
- private
87
-
88
- attr_reader :browser
89
- private :browser
90
- end
91
-
92
- end
93
- end
94
- end
@@ -1,28 +0,0 @@
1
- module BBC
2
- module A11y
3
- module CucumberSupport
4
- module PerPageChecks
5
- def assert_title_describes_primary_content_of_document(title, page)
6
- pending <<-ERROR
7
- Because the title did not contain the header text, you need to write a custom
8
- method to define how to make this check.
9
-
10
- In your .a11y.rb file, add the following code:
11
-
12
- BBC::A11y.configure do
13
- page "my_page.html" do
14
-
15
- customize_world do
16
- def assert_title_describes_primary_content_of_document(title, page)
17
- # TODO: add your custom code here to make the check
18
- end
19
- end
20
-
21
- end
22
- end
23
- ERROR
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,36 +0,0 @@
1
- require 'w3c_validators'
2
-
3
- module BBC
4
- module A11y
5
- module CucumberSupport
6
-
7
- class W3C
8
- include W3CValidators
9
-
10
- def initialize
11
- @validator = MarkupValidator.new(options)
12
- end
13
-
14
- def validate(url)
15
- @results = @validator.validate_uri(url)
16
- end
17
-
18
- def errors
19
- @results.errors
20
- end
21
-
22
- private
23
-
24
- def options
25
- return {} unless proxy
26
- { proxy_server: proxy, proxy_port: 80 }
27
- end
28
-
29
- def proxy
30
- @proxy ||= ['http_proxy', 'https_proxy', 'HTTP_PROXY', 'HTTPS_PROXY'].map { |key| ENV[key] }.compact.first
31
- end
32
- end
33
-
34
- end
35
- end
36
- end
@@ -1,162 +0,0 @@
1
- require 'bbc/a11y/cucumber_support/heading_hierarchy'
2
- require 'capybara'
3
-
4
- module BBC::A11y::CucumberSupport
5
- describe HeadingHierarchy do
6
-
7
- let(:hierarchy) do
8
- HeadingHierarchy.new(page)
9
- end
10
-
11
- let(:page) do
12
- # patch Capybara::String to look just enough like a real Capybara::Session to work
13
- page = Capybara.string(html)
14
- source = html
15
- page.define_singleton_method(:source) { source }
16
- page
17
- end
18
-
19
- describe '#validate' do
20
-
21
- context 'a simple, valid h1-h6 hierachy' do
22
- let(:html) { <<-HTML }
23
- <html>
24
- <body>
25
- <h1>Heading 1</h1>
26
- <h2>Heading 2</h2>
27
- <h3>Heading 3</h3>
28
- <h4>Heading 4</h4>
29
- <h5>Heading 5</h5>
30
- <h6>Heading 6</h6>
31
- </body>
32
- </html>
33
- HTML
34
-
35
- it 'passes' do
36
- expect { hierarchy.validate }.not_to raise_error
37
- end
38
- end
39
-
40
- context 'an invalid h1-h3 hierarchy' do
41
- let(:html) { <<-HTML }
42
- <html>
43
- <body>
44
- <h1>Heading 1</h1>
45
- <h3>Heading 3</h3>
46
- <h2>Heading 2</h2>
47
- </body>
48
- </html>
49
- HTML
50
-
51
- it 'fails' do
52
- expect { hierarchy.validate }.to raise_error("Headings were not in order: h1, **h3**, h2")
53
- end
54
- end
55
-
56
- context 'an hierarchy that skips back up from h4 to h2' do
57
- let(:html) { <<-HTML }
58
- <html>
59
- <body>
60
- <h1>Heading 1</h1>
61
- <h2>Heading 2</h2>
62
- <h3>Heading 3</h3>
63
- <h4>Heading 4</h4>
64
- <h2>Heading 2b</h2>
65
- <h3>Heading 3b</h3>
66
- </body>
67
- </html>
68
- HTML
69
-
70
- it 'passes' do
71
- expect { hierarchy.validate }.not_to raise_error
72
- end
73
- end
74
-
75
-
76
- end
77
-
78
- describe "#to_s" do
79
- context 'an hierarchy that skips back up from h4 to h2' do
80
- let(:html) { <<-HTML }
81
- <html>
82
- <body>
83
- <h1>Heading 1</h1>
84
- <h2>Heading 2</h2>
85
- <h3>Heading 3</h3>
86
- <h4>Heading 4</h4>
87
- <h2>Heading 2b</h2>
88
- <h3>Heading 3b</h3>
89
- </body>
90
- </html>
91
- HTML
92
-
93
- it 'renders a nested hierarchy' do
94
- expected = <<-TEXT
95
- h1
96
- h2
97
- h3
98
- h4
99
- h2
100
- h3
101
- TEXT
102
- expect( hierarchy.to_s ).to eq expected.strip
103
- end
104
- end
105
- end
106
-
107
- context "hidden headings" do
108
- let(:html) { <<-HTML }
109
- <html>
110
- <body>
111
- <h1 style="display:none">Heading 1</h1>
112
- </body>
113
- </html>
114
- HTML
115
-
116
- it "sees them" do
117
- expect( hierarchy.to_s ).to eq "h1"
118
- end
119
-
120
- end
121
-
122
- context "headings within a script tag" do
123
- # e.g. template elements too
124
- let(:html) { <<-HTML }
125
- <html>
126
- <body>
127
- <h1>Heading 1</h1>
128
- <script>
129
- var stuff = "<h2>Heading 2</h2>";
130
- </script>
131
- </body>
132
- </html>
133
- HTML
134
-
135
- it "ignores them" do
136
- expect( hierarchy.to_s ).to eq "h1"
137
- end
138
- end
139
-
140
- context "with headings before the first h1" do
141
- # rules only apply to headings after the main h1
142
- let(:html) { <<-HTML }
143
- <html>
144
- <body>
145
- <h3>Ignore me</h3>
146
- <h1>Heading 1</h1>
147
- <h2>Heading 2</h2>
148
- </body>
149
- </html>
150
- HTML
151
-
152
- it "ignores them" do
153
- expected = <<-TEXT
154
- h1
155
- h2
156
- TEXT
157
- expect( hierarchy.to_s ).to eq expected.strip
158
- end
159
- end
160
-
161
- end
162
- end
@@ -1,52 +0,0 @@
1
- require 'bbc/a11y/cucumber_support/matchers'
2
- require 'capybara'
3
-
4
- module BBC::A11y::CucumberSupport
5
- describe 'matchers' do
6
- let(:page) do
7
- Capybara.string(html)
8
- end
9
-
10
- describe '#have_title_attribute_or_associated_label_tag' do
11
- context 'a form field with a title attribute' do
12
- let(:element) { Capybara.string(<<-HTML).find('input') }
13
- <input type="text" name="q" title="Search the BBC" />
14
- HTML
15
- it 'passes' do
16
- expect(element).to have_title_attribute_or_associated_label_tag
17
- end
18
- end
19
-
20
- context 'a form field with a label, associated by `for` attribute' do
21
- let(:element) { Capybara.string(<<-HTML).find('input') }
22
- <label for="search">Search the BBC</label>
23
- <input type="text" id="search" name="q" />
24
- HTML
25
- it 'passes' do
26
- expect(element).to have_title_attribute_or_associated_label_tag
27
- end
28
- end
29
-
30
- context 'a form field within a label' do
31
- let(:element) { Capybara.string(<<-HTML).find('input') }
32
- <label for="search">
33
- Search the BBC
34
- <input type="text" name="q" />
35
- </label>
36
- HTML
37
- it 'passes' do
38
- expect(element).to have_title_attribute_or_associated_label_tag
39
- end
40
- end
41
-
42
- context "a form field without associated label tag or title attribute" do
43
- let(:element) { Capybara.string(<<-HTML).find('input') }
44
- <input type="text" name="name" />
45
- HTML
46
- it 'fails' do
47
- expect(element).not_to have_title_attribute_or_associated_label_tag
48
- end
49
- end
50
- end
51
- end
52
- end
@@ -1,197 +0,0 @@
1
- require 'bbc/a11y/cucumber_support/page'
2
- require 'capybara'
3
-
4
- module BBC::A11y::CucumberSupport
5
- describe Page do
6
- let(:page) do
7
- Page.new(Capybara.string(html))
8
- end
9
-
10
- describe '#all_elements_matching' do
11
- context 'with multiple matches' do
12
- let(:html) { <<-HTML }
13
- <html>
14
- <ul>
15
- <li><p></li>
16
- <li></li>
17
- </ul>
18
- </html>
19
- HTML
20
- it 'returns each element that matches' do
21
- expect(page.all_elements_matching('li', 'p').length).to eq 3
22
- end
23
- end
24
- end
25
-
26
- describe '#must_have_lang_attribute' do
27
- context 'with no lang attribute' do
28
- let(:html) { <<-HTML }
29
- <html>
30
- </html>
31
- HTML
32
-
33
- it 'fails' do
34
- expect { page.must_have_lang_attribute }.to raise_error(
35
- RSpec::Expectations::ExpectationNotMetError,
36
- %{expected to find css "html[lang]" but there were no matches})
37
- end
38
- end
39
-
40
- context 'with a lang attribute' do
41
- let(:html) { <<-HTML }
42
- <html lang="en-GB">
43
- </html>
44
- HTML
45
-
46
- it 'passes' do
47
- expect { page.must_have_lang_attribute }.not_to raise_error
48
- end
49
- end
50
- end
51
-
52
- describe '#must_have_lang_attribute_of' do
53
- let(:html) { <<-HTML }
54
- <html lang="en-GB">
55
- </html>
56
- HTML
57
-
58
- it 'passes for a matching lang attribute' do
59
- expect { page.must_have_lang_attribute_of('en') }.not_to raise_error
60
- end
61
-
62
- it 'fails for the wrong language code' do
63
- expect { page.must_have_lang_attribute_of('fr') }.to raise_error(RSpec::Expectations::ExpectationNotMetError)
64
- end
65
- end
66
-
67
- describe "#must_have_no_elements_with_title_attribute_content_repeated_within" do
68
- context "when an element has the same title attribute as its contents" do
69
- let(:html) { <<-HTML }
70
- <html>
71
- <body>
72
- <a title="News">News</a>
73
- </body>
74
- </html>
75
- HTML
76
-
77
- it "fails" do
78
- expect { page.must_have_no_elements_with_title_attribute_content_repeated_within }.
79
- to raise_error(RSpec::Expectations::ExpectationNotMetError)
80
- end
81
- end
82
-
83
- context "when an element has a valid title" do
84
- let(:html) { <<-HTML }
85
- <html>
86
- <body>
87
- <img title="A picture of a cat">
88
- </body>
89
- </html>
90
- HTML
91
-
92
- it "passes" do
93
- expect { page.must_have_no_elements_with_title_attribute_content_repeated_within }.
94
- not_to raise_error
95
- end
96
- end
97
-
98
- end
99
-
100
- describe "#must_have_no_form_fields_with_label_and_title" do
101
- context "a form field with both label and title" do
102
- let(:html) { <<-HTML }
103
- <html>
104
- <body>
105
- <form>
106
- <label for="name">Your name</label>
107
- <input id="name" title="Your name please">
108
- </form>
109
- </body>
110
- </html>
111
- HTML
112
-
113
- it "fails" do
114
- expect { page.must_have_no_form_fields_with_label_and_title }.
115
- to raise_error(RSpec::Expectations::ExpectationNotMetError)
116
- end
117
- end
118
- end
119
-
120
- describe "#must_not_have_any_elements_with_tabindex_greater_than" do
121
- context "an element with a tabindex value of 1" do
122
- let(:html) { <<-HTML }
123
- <html>
124
- <body>
125
- <a tabindex="1">Important</a>
126
- </body>
127
- </html>
128
- HTML
129
-
130
- it "fails for zero" do
131
- expect { page.must_not_have_any_elements_with_tabindex_greater_than 0 }.
132
- to raise_error(RSpec::Expectations::ExpectationNotMetError)
133
- end
134
-
135
- it "passes for 1" do
136
- expect { page.must_not_have_any_elements_with_tabindex_greater_than 0 }.
137
- to raise_error(RSpec::Expectations::ExpectationNotMetError)
138
- end
139
- end
140
- end
141
-
142
- describe "#must_not_have_elements_with_tabindex" do
143
-
144
- context "an element that's in the list of exceptions" do
145
- let(:html) { <<-HTML }
146
- <html>
147
- <body>
148
- <a tabindex="0">Important</a>
149
- </body>
150
- </html>
151
- HTML
152
-
153
- it "passes" do
154
- expect { page.must_not_have_elements_with_tabindex(0, except: 'a') }.not_to raise_error
155
- end
156
- end
157
-
158
- context "an element not in the list of exceptions" do
159
- let(:html) { <<-HTML }
160
- <html>
161
- <body>
162
- <p tabindex="0">Important</a>
163
- </body>
164
- </html>
165
- HTML
166
-
167
- it "fails" do
168
- expect { page.must_not_have_elements_with_tabindex(0, except: 'a') }.
169
- to raise_error(RSpec::Expectations::ExpectationNotMetError)
170
- end
171
- end
172
-
173
- context "a different tabindex value" do
174
- let(:html) { <<-HTML }
175
- <html>
176
- <body>
177
- <p tabindex="-1">Important</a>
178
- </body>
179
- </html>
180
- HTML
181
-
182
- it "passes" do
183
- expect { page.must_not_have_elements_with_tabindex(0, except: 'a') }.not_to raise_error
184
- end
185
- end
186
- end
187
-
188
- describe '#to_s' do
189
- let(:html) { "<html><body><h1>Header</h1><p>More stuff</p></body></html>" }
190
-
191
- it 'renders the visible text on the page' do
192
- expect(page.to_s).to eq "HeaderMore stuff"
193
- end
194
- end
195
-
196
- end
197
- end