bbc-a11y 0.0.12 → 0.0.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/README.md +9 -11
- data/Rakefile +2 -2
- data/a11y.rb +5 -0
- data/bbc-a11y.gemspec +3 -5
- data/bin/a11y +2 -2
- data/examples/bbc-pages/a11y.rb +2 -6
- data/examples/local-web-app/Gemfile +1 -1
- data/examples/local-web-app/a11y.rb +10 -22
- data/features/check_standards/focusable_controls.feature +62 -0
- data/features/check_standards/form_interactions.feature +45 -0
- data/features/check_standards/form_labels.feature +55 -0
- data/features/check_standards/headings.feature +154 -0
- data/features/check_standards/image_alt.feature +39 -0
- data/features/check_standards/language.feature +46 -0
- data/features/check_standards/main_landmark.feature +39 -0
- data/features/check_standards/tab_index.feature +54 -0
- data/features/cli/display_failing_result.feature +10 -0
- data/features/{exit_status.feature → cli/exit_status.feature} +1 -2
- data/features/cli/provide_muting_tips.feature +25 -0
- data/features/cli/report_configuration_errors.feature +43 -0
- data/features/cli/skipping_standards.feature +15 -0
- data/features/cli/specify_url.feature +9 -0
- data/features/cli/specify_url_via_config.feature +13 -0
- data/features/mute_errors.feature +118 -0
- data/features/step_definitions/steps.rb +25 -1
- data/lib/bbc/a11y/cli.rb +32 -44
- data/lib/bbc/a11y/configuration.rb +56 -22
- data/lib/bbc/a11y/linter.rb +42 -0
- data/lib/bbc/a11y/standards.rb +43 -0
- data/lib/bbc/a11y/standards/anchor_hrefs.rb +18 -0
- data/lib/bbc/a11y/standards/content_follows_headings.rb +22 -0
- data/lib/bbc/a11y/standards/exactly_one_main_heading.rb +20 -0
- data/lib/bbc/a11y/standards/exactly_one_main_landmark.rb +20 -0
- data/lib/bbc/a11y/standards/form_labels.rb +39 -0
- data/lib/bbc/a11y/standards/form_submit_buttons.rb +21 -0
- data/lib/bbc/a11y/standards/heading_hierarchy.rb +34 -0
- data/lib/bbc/a11y/standards/image_alt.rb +18 -0
- data/lib/bbc/a11y/standards/language_attribute.rb +19 -0
- data/lib/bbc/a11y/standards/tab_index.rb +22 -0
- data/lib/bbc/a11y/version +1 -1
- data/spec/bbc/a11y/cli_spec.rb +22 -15
- data/spec/bbc/a11y/configuration_spec.rb +15 -40
- data/standards/support/capybara.rb +1 -2
- metadata +62 -81
- data/features/specify_url_via_cli.feature +0 -10
- data/features/specify_url_via_config.feature +0 -16
- data/lib/bbc/a11y.rb +0 -17
- data/lib/bbc/a11y/cucumber_runner.rb +0 -208
- data/lib/bbc/a11y/cucumber_support.rb +0 -56
- data/lib/bbc/a11y/cucumber_support/disabled_w3c.rb +0 -37
- data/lib/bbc/a11y/cucumber_support/heading_hierarchy.rb +0 -94
- data/lib/bbc/a11y/cucumber_support/language_detector.rb +0 -26
- data/lib/bbc/a11y/cucumber_support/matchers.rb +0 -21
- data/lib/bbc/a11y/cucumber_support/page.rb +0 -94
- data/lib/bbc/a11y/cucumber_support/per_page_checks.rb +0 -28
- data/lib/bbc/a11y/cucumber_support/w3c.rb +0 -36
- data/spec/bbc/a11y/cucumber_support/heading_hierarchy_spec.rb +0 -162
- data/spec/bbc/a11y/cucumber_support/matchers_spec.rb +0 -52
- data/spec/bbc/a11y/cucumber_support/page_spec.rb +0 -197
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10a369a2db0ae8f7e5b16e92a0f57e12ac3343be
|
4
|
+
data.tar.gz: 1b733f1a53afad2c0d200f801ec95ff6ed2ed7c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79571febea89695f33ae26639f47567dec98e73bed577c5ade2559db0b3235db15c3ec6245c5a6878598eda8f5ef163dd67b8c0df61603fb15c3ffe6779bd8db
|
7
|
+
data.tar.gz: de1ff17432e5fe20a0e67572fdbb0b973ff835d86620a12b7f3e63abe28ecb557ec7e7e824c488832c5cad9f19ea45cc962d23e4f084cc5dcc55c48ddff9c1d7
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.2.3
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[![Circle CI](https://circleci.com/gh/cucumber-ltd/bbc-a11y.svg?style=svg&circle-token=00d656fd091643ad692c78ca60e30ad95df9563a)](https://circleci.com/gh/cucumber-ltd/bbc-a11y)
|
2
|
+
|
1
3
|
# WARNING - PROTOTYPE SOFTWARE
|
2
4
|
|
3
5
|
This project is still at an experimental / proof-of-concept stage. Please set your expectations appropriately, but *do* [give us as much feedback](https://github.com/cucumber-ltd/bbc-a11y/issues) as you can.
|
@@ -32,24 +34,20 @@ Now install the gem:
|
|
32
34
|
You'll need to configure a11y with a set of URLs to run the checks against. Create a file `a11y.rb` in the root of your project that looks something like this:
|
33
35
|
|
34
36
|
```
|
35
|
-
|
36
|
-
|
37
|
-
page "http://bbc.co.uk/news"
|
38
|
-
end
|
37
|
+
page "http://bbc.co.uk"
|
38
|
+
page "http://bbc.co.uk/news"
|
39
39
|
```
|
40
40
|
|
41
41
|
### Skipping scenarios
|
42
42
|
|
43
|
-
Nobody's perfect. Use `
|
43
|
+
Nobody's perfect. Use `skip_standard` in the configuration to opt-out of certain checks.
|
44
44
|
|
45
45
|
```
|
46
|
-
|
47
|
-
|
48
|
-
skip_scenario /W3C/
|
49
|
-
end
|
50
|
-
|
51
|
-
page "http://bbc.co.uk/news"
|
46
|
+
page "http://bbc.co.uk" do
|
47
|
+
skip_standard /W3C/
|
52
48
|
end
|
49
|
+
|
50
|
+
page "http://bbc.co.uk/news"
|
53
51
|
```
|
54
52
|
|
55
53
|
A11y will skip any scenarios from the specifications whose name contains that string.
|
data/Rakefile
CHANGED
data/a11y.rb
ADDED
data/bbc-a11y.gemspec
CHANGED
@@ -13,16 +13,14 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.license = "MIT"
|
14
14
|
s.required_ruby_version = ">= 1.9.3"
|
15
15
|
|
16
|
-
s.add_dependency 'cucumber', '~> 2.0.0.rc'
|
17
|
-
s.add_dependency 'rspec', '~> 3.0'
|
18
16
|
s.add_dependency 'capybara'
|
19
|
-
s.add_dependency 'poltergeist'
|
20
|
-
s.add_dependency 'w3c_validators'
|
21
|
-
s.add_dependency 'cld'
|
22
17
|
s.add_dependency 'colorize'
|
18
|
+
|
19
|
+
s.add_development_dependency 'rspec', '~> 3.0'
|
23
20
|
s.add_development_dependency 'aruba'
|
24
21
|
s.add_development_dependency 'pry'
|
25
22
|
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'cucumber'
|
26
24
|
|
27
25
|
s.rubygems_version = ">= 1.6.1"
|
28
26
|
s.files = `git ls-files`.split("\n").reject {|path| path =~ /\.gitignore$/ }
|
data/bin/a11y
CHANGED
@@ -2,5 +2,5 @@
|
|
2
2
|
$:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
3
3
|
|
4
4
|
require 'bbc/a11y/cli'
|
5
|
-
|
6
|
-
BBC::A11y::CLI.new($stdin, $stdout, $stderr, ARGV.dup).call
|
5
|
+
|
6
|
+
BBC::A11y::CLI.new($stdin, $stdout, $stderr, ARGV.dup).call
|
data/examples/bbc-pages/a11y.rb
CHANGED
@@ -36,29 +36,17 @@ class Server
|
|
36
36
|
end
|
37
37
|
|
38
38
|
server = Server.new
|
39
|
+
server.start
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
server.start
|
44
|
-
end
|
45
|
-
|
46
|
-
after_all do
|
47
|
-
server.stop
|
48
|
-
end
|
49
|
-
|
50
|
-
page "http://localhost:#{server.port}/perfect.html" do
|
51
|
-
skip_scenario "W3C"
|
52
|
-
end
|
41
|
+
at_exit do
|
42
|
+
server.stop
|
43
|
+
end
|
53
44
|
|
54
|
-
|
55
|
-
|
56
|
-
|
45
|
+
page "http://localhost:#{server.port}/perfect.html" do
|
46
|
+
skip_standard "W3C"
|
47
|
+
end
|
57
48
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
49
|
+
page "http://localhost:#{server.port}/missing_header.html" do
|
50
|
+
skip_standard "W3C"
|
51
|
+
skip_standard "Check headings"
|
64
52
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
Feature: Focusable Controls
|
2
|
+
|
3
|
+
Controls for JavaScript enhanced interactions **must** be `<a>`, `<button>`,
|
4
|
+
or `<input>` elements if that is the only mechanism for controlling them.
|
5
|
+
|
6
|
+
`<a>` elements used for controls **must** have an `href` attribute.
|
7
|
+
|
8
|
+
Controls that have no purpose without JavaScript **must not** be added to the
|
9
|
+
page before the associated code is available capable of running.
|
10
|
+
|
11
|
+
Rationale
|
12
|
+
=========
|
13
|
+
|
14
|
+
When creating controls for user interaction with JavaScript enhanced features,
|
15
|
+
for example a carousel with previous and next controls, the controls must be
|
16
|
+
implemented with elements that provide natively focusable elements with click,
|
17
|
+
keydown, and focus events so they are accessible to keyboard as well as
|
18
|
+
pointing device users. If there is an alternative method of controlling the
|
19
|
+
feature, for example a carousel that automatically displays new content when
|
20
|
+
it receives content then controls that are only available to pointing device
|
21
|
+
users can be used.
|
22
|
+
|
23
|
+
In general, use `<a>` elements when there is a URL associated with the
|
24
|
+
interaction when JavaScript is not enabled, and `<button>` elements for
|
25
|
+
interactions that don't have an associated URL.
|
26
|
+
|
27
|
+
`<a>` elements without a `href` attribute are not keyboard accessible, and
|
28
|
+
therefore must not be used for controls.
|
29
|
+
|
30
|
+
When there is no core (non-JavaScript) interaction then the control must not
|
31
|
+
be added to the document before the associated JavaScript is capable of
|
32
|
+
running as this would lead to controls that apparently do nothing, potentially
|
33
|
+
causing confusion to users.
|
34
|
+
|
35
|
+
Scenario: All anchor tags have href attributes
|
36
|
+
Given a page with the HTML:
|
37
|
+
"""
|
38
|
+
<button type="button">Open panel</button>
|
39
|
+
<ul>
|
40
|
+
<li><a href="/news">News</a></li>
|
41
|
+
<li><a href="#sporttab">Sport</a></li>
|
42
|
+
<li><a href="#entertainmenttab">Entertainment</a></li>
|
43
|
+
</ul>
|
44
|
+
"""
|
45
|
+
When I validate the anchor hrefs standards
|
46
|
+
Then it passes
|
47
|
+
|
48
|
+
Scenario: Some anchor tags do not have href attributes
|
49
|
+
Given a page with the HTML:
|
50
|
+
"""
|
51
|
+
<ul>
|
52
|
+
<li><a>News</a></li>
|
53
|
+
<li><a href="#sport">Sport</a></li>
|
54
|
+
<li><a>Entertainment</a></li>
|
55
|
+
</ul>
|
56
|
+
"""
|
57
|
+
When I validate the anchor hrefs standards
|
58
|
+
Then it fails with the message:
|
59
|
+
"""
|
60
|
+
Anchor has no href attribute: /html/body/ul/li[1]/a
|
61
|
+
Anchor has no href attribute: /html/body/ul/li[3]/a
|
62
|
+
"""
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Feature: Form Interactions
|
2
|
+
|
3
|
+
Forms must have a submit button and forms must not update the location of the
|
4
|
+
page on change, focus, or blur events.
|
5
|
+
|
6
|
+
All `<form>` elements that take user input (i.e. do not consist only of
|
7
|
+
`input[type=hidden]` elements to store state) **must** have a submit button in
|
8
|
+
the form of a `<input>[type=submit,image]` or `<button>[type=submit]` element.
|
9
|
+
|
10
|
+
Changes to the page **must** location must only take place on explicit user
|
11
|
+
action i.e. when a submit button is activated. They must not take place when
|
12
|
+
change (particularly for `select` elements), focus, or blur events are fired.
|
13
|
+
|
14
|
+
Rationale
|
15
|
+
=========
|
16
|
+
|
17
|
+
All forms should have a submit button to provide a clear call to action. This
|
18
|
+
is particularly important to users with cognitive disabilities, but is also
|
19
|
+
beneficial to low vision users as an indication of the end of the form.
|
20
|
+
|
21
|
+
Scenario: Form with a submit button
|
22
|
+
Given a page with the HTML:
|
23
|
+
"""
|
24
|
+
<form action="/search">
|
25
|
+
<label for="q">Search term:</label>
|
26
|
+
<input type="text" name="q" id="q">
|
27
|
+
<input type="submit" value="Search">
|
28
|
+
</form>
|
29
|
+
"""
|
30
|
+
When I validate the form submit buttons standards
|
31
|
+
Then it passes
|
32
|
+
|
33
|
+
Scenario: Form with no submit button
|
34
|
+
Given a page with the HTML:
|
35
|
+
"""
|
36
|
+
<form action="/search">
|
37
|
+
<label for="q">Search term:</label>
|
38
|
+
<input type="text" name="q" id="q">
|
39
|
+
</form>
|
40
|
+
"""
|
41
|
+
When I validate the form submit buttons standards
|
42
|
+
Then it fails with the message:
|
43
|
+
"""
|
44
|
+
Form has no submit button: /html/body/form
|
45
|
+
"""
|
@@ -0,0 +1,55 @@
|
|
1
|
+
Feature: Correctly use form labels
|
2
|
+
|
3
|
+
Form fields that allow input (`select`, and `textarea` elements, and all
|
4
|
+
`input` element types other than image, submit, reset, button, or hidden)
|
5
|
+
**must** have an associated label, either in the form of a `<label>` element
|
6
|
+
or, for simple forms when no visible label is required, a `title` attribute.
|
7
|
+
|
8
|
+
Rationale
|
9
|
+
---------
|
10
|
+
|
11
|
+
Form labels are important for all users in order to understand what the form
|
12
|
+
field is however they are essential for speech output users who cannot easily
|
13
|
+
infer what the form element is by looking at the surrounding content.
|
14
|
+
|
15
|
+
While there are WAI-ARIA attributes that allow for labelling of forms it is
|
16
|
+
not supported in all versions of assistive technologies that BBC users could
|
17
|
+
reasonably expect to be able to use.
|
18
|
+
|
19
|
+
Scenario: All fields have labels or title attributes
|
20
|
+
Given a page with the HTML:
|
21
|
+
"""
|
22
|
+
<label for="search">Search the BBC</label>
|
23
|
+
<input type="text" id="search" name="q" />
|
24
|
+
|
25
|
+
<label for="search2">
|
26
|
+
Search the BBC
|
27
|
+
<input id="search2" type="text" name="q" />
|
28
|
+
</label>
|
29
|
+
|
30
|
+
<input type="text" name="q" title="Search the BBC" />
|
31
|
+
"""
|
32
|
+
When I validate the form labels standards
|
33
|
+
Then it passes
|
34
|
+
|
35
|
+
Scenario: Some fields without labels or title attributes
|
36
|
+
Given a page with the HTML:
|
37
|
+
"""
|
38
|
+
<input type="text" name="name" title="Name" />
|
39
|
+
<input type="email" name="email" title="Email" />
|
40
|
+
|
41
|
+
<textarea name="notes"></textarea>
|
42
|
+
<select name="preference"></select>
|
43
|
+
<input type="text" name="q" value="Search the BBC" />
|
44
|
+
<input type="text" name="q" aria-label="Search the BBC" />
|
45
|
+
<input type="text" name="q" placeholder="Search the BBC" />
|
46
|
+
"""
|
47
|
+
When I validate the form labels standards
|
48
|
+
Then it fails with the message:
|
49
|
+
"""
|
50
|
+
Field has no label or title attribute: /html/body/textarea
|
51
|
+
Field has no label or title attribute: /html/body/select
|
52
|
+
Field has no label or title attribute: /html/body/input[3]
|
53
|
+
Field has no label or title attribute: /html/body/input[4]
|
54
|
+
Field has no label or title attribute: /html/body/input[5]
|
55
|
+
"""
|
@@ -0,0 +1,154 @@
|
|
1
|
+
Feature: Headings
|
2
|
+
|
3
|
+
A document **must** have exactly one `<h1>` element.
|
4
|
+
|
5
|
+
Heading levels after the document `<h1>` element **must** be sequential and **must not** skip heading levels.
|
6
|
+
|
7
|
+
Heading elements **must** be followed by content.
|
8
|
+
|
9
|
+
Rationale
|
10
|
+
=========
|
11
|
+
|
12
|
+
A logical heading structure is invaluable for users of screen readers and similar assistive technologies to help navigate content.
|
13
|
+
|
14
|
+
Users should be able to use the document's `<h1>` identify its main content. Documents should have one main subject.
|
15
|
+
|
16
|
+
Heading levels should not be skipped as a predictable document outline is an important factor for understandability.
|
17
|
+
|
18
|
+
Headings should not be used for non-heading purposes, i.e. to identify blocks of content. A heading should always
|
19
|
+
be followed either by non-heading content or by a heading of one level deeper.
|
20
|
+
|
21
|
+
Scenario: No main heading
|
22
|
+
Given a page with the HTML:
|
23
|
+
"""
|
24
|
+
<h2>Heading 2</h2>
|
25
|
+
"""
|
26
|
+
When I validate the heading standards
|
27
|
+
Then it fails with the message:
|
28
|
+
"""
|
29
|
+
A document must have exactly one heading. Found 0 h1 elements.
|
30
|
+
"""
|
31
|
+
|
32
|
+
Scenario: More than one main heading
|
33
|
+
Given a page with the HTML:
|
34
|
+
"""
|
35
|
+
<h1>Heading 1</h1>
|
36
|
+
<h2>Heading 2</h2>
|
37
|
+
<h1>Heading 1</h1>
|
38
|
+
"""
|
39
|
+
When I validate the heading standards
|
40
|
+
Then it fails with the message:
|
41
|
+
"""
|
42
|
+
A document must have exactly one heading. Found 2 h1 elements:
|
43
|
+
/html/body/h1[1]
|
44
|
+
/html/body/h1[2]
|
45
|
+
"""
|
46
|
+
|
47
|
+
Scenario: Headings in ascending order
|
48
|
+
Given a page with the HTML:
|
49
|
+
"""
|
50
|
+
<h1>Heading 1</h1>
|
51
|
+
<h2>Heading 2</h2>
|
52
|
+
<h3>Heading 3</h3>
|
53
|
+
<h4>Heading 4</h4>
|
54
|
+
<h5>Heading 5</h5>
|
55
|
+
<h6>Heading 6</h6>
|
56
|
+
"""
|
57
|
+
When I validate the heading standards
|
58
|
+
Then it passes
|
59
|
+
|
60
|
+
Scenario: Headings in invalid order
|
61
|
+
Given a page with the HTML:
|
62
|
+
"""
|
63
|
+
<h1>Heading 1</h1>
|
64
|
+
<h3>Heading 3</h3>
|
65
|
+
<h2>Heading 2</h2>
|
66
|
+
"""
|
67
|
+
When I validate the heading standards
|
68
|
+
Then it fails with the message:
|
69
|
+
"""
|
70
|
+
Headings are not in order: h1 is followed by h3
|
71
|
+
"""
|
72
|
+
|
73
|
+
Scenario: Headings jump back up more than one level
|
74
|
+
Given a page with the HTML:
|
75
|
+
"""
|
76
|
+
<h1>Heading 1</h1>
|
77
|
+
<h2>Heading 2</h2>
|
78
|
+
<h3>Heading 3</h3>
|
79
|
+
<h4>Heading 4</h4>
|
80
|
+
<h2>Heading 2b</h2>
|
81
|
+
<h3>Heading 3b</h3>
|
82
|
+
"""
|
83
|
+
When I validate the heading standards
|
84
|
+
Then it passes
|
85
|
+
|
86
|
+
Scenario: Heading is hidden
|
87
|
+
Given a page with the HTML:
|
88
|
+
"""
|
89
|
+
<h1>Heading 1</h1>
|
90
|
+
<h3 style="display:none">Heading 3</h3>
|
91
|
+
<h2>Heading 2</h2>
|
92
|
+
"""
|
93
|
+
When I validate the heading standards
|
94
|
+
Then it fails with the message:
|
95
|
+
"""
|
96
|
+
Headings are not in order: h1 is followed by h3
|
97
|
+
"""
|
98
|
+
|
99
|
+
Scenario: Heading in a script tag
|
100
|
+
Given a page with the HTML:
|
101
|
+
"""
|
102
|
+
<h1>Heading 1</h1>
|
103
|
+
<script>
|
104
|
+
var stuff = "<h3>Heading 3</h3>";
|
105
|
+
</script>
|
106
|
+
<h2>Heading 2</h2>
|
107
|
+
"""
|
108
|
+
When I validate the heading standards
|
109
|
+
Then it passes
|
110
|
+
|
111
|
+
Scenario: Subheading before the first main heading
|
112
|
+
Given a page with the HTML:
|
113
|
+
"""
|
114
|
+
<h3>Ignore me</h3>
|
115
|
+
<h1>Heading 1</h1>
|
116
|
+
<h2>Heading 2</h2>
|
117
|
+
"""
|
118
|
+
When I validate the heading standards
|
119
|
+
Then it passes
|
120
|
+
|
121
|
+
Scenario: Content between headings
|
122
|
+
Given a page with the HTML:
|
123
|
+
"""
|
124
|
+
<div role="main">
|
125
|
+
<h1>Main heading</h1>
|
126
|
+
<p>non-heading content</p>
|
127
|
+
<h2>Another heading</h2>
|
128
|
+
<p>non-heading content</p>
|
129
|
+
<h3>Main content</h3>
|
130
|
+
<h2>Secondary content</h2>
|
131
|
+
<p>non-heading content</p>
|
132
|
+
<h2>Tertiary content</h2>
|
133
|
+
<p>Lorem ipsum…</p>
|
134
|
+
</div>
|
135
|
+
"""
|
136
|
+
When I validate the heading standards
|
137
|
+
Then it passes
|
138
|
+
|
139
|
+
Scenario: No content between headings
|
140
|
+
Given a page with the HTML:
|
141
|
+
"""
|
142
|
+
<div role="main">
|
143
|
+
<h1>Main heading</h1>
|
144
|
+
<p>non-heading content</p>
|
145
|
+
<h2>Secondary content</h2>
|
146
|
+
<h2>Tertiary content</h2>
|
147
|
+
<p>non-heading content</p>
|
148
|
+
</div>
|
149
|
+
"""
|
150
|
+
When I validate the heading standards
|
151
|
+
Then it fails with the message:
|
152
|
+
"""
|
153
|
+
Heading elements must be followed by content. No content follows a h2.
|
154
|
+
"""
|