govuk-rspec-helpers 0.1
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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +7 -0
- data/Rakefile +8 -0
- data/lib/check_govuk_checkbox.rb +167 -0
- data/lib/choose_govuk_radio.rb +104 -0
- data/lib/click_govuk_button.rb +109 -0
- data/lib/click_govuk_link.rb +110 -0
- data/lib/fill_in_govuk_text_field.rb +150 -0
- data/lib/govuk_rspec_helpers.rb +13 -0
- data/lib/summarise_errors_matcher.rb +119 -0
- data/lib/summarise_matcher.rb +71 -0
- data/lib/within_govuk_fieldset.rb +74 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2688889ddc616f1479c43451e7cf7eb79cf2f1ea15cf56eaf2de361dfff89d51
|
4
|
+
data.tar.gz: 6a5b5fe732f6025d57884b152ab0898030b27c8d3fd17ab1145d14fbc8223824
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5061ab29b4267c0b87cdcb5ad862d1c60e94eaf85163c8d036cb4bd98efe8d1f628c95ccd0b6cb37b584ab23d03ae3d32b3bb942e06fbaa107dce7a14e3c3e3e
|
7
|
+
data.tar.gz: b613d4f0f68a69b9876cc8d19ef55ea19bcb2ae8952b1373d78a8c0b14094920f6228906c7c7a383c05d5a44ab7b72596a9c51fdc8e74753b45d5d806eb311e9
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2023 Frankie Roberto
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# GOV.UK RSpec Helpers
|
2
|
+
|
3
|
+
This gem providers a set of helpers to make it easier to test GOV.UK services using the [RSpec](https://rspec.info) framework.
|
4
|
+
|
5
|
+
This is a pre-release. Feedback is welcome.
|
6
|
+
|
7
|
+
See [documentation](https://x-govuk.github.io/govuk-rspec-helpers/).
|
data/Rakefile
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
module GovukRSpecHelpers
|
2
|
+
class GovukCheckbox
|
3
|
+
|
4
|
+
attr_reader :page, :label_text, :hint_text
|
5
|
+
|
6
|
+
def initialize(page:, label_text:, hint_text:)
|
7
|
+
@label_text = label_text
|
8
|
+
@hint_text = hint_text
|
9
|
+
@page = page
|
10
|
+
end
|
11
|
+
|
12
|
+
def check
|
13
|
+
set_checked(true)
|
14
|
+
end
|
15
|
+
|
16
|
+
def uncheck
|
17
|
+
set_checked(false)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def set_checked(checked)
|
23
|
+
labels = page.all('label', text: label_text, exact_text: true, normalize_ws: true)
|
24
|
+
|
25
|
+
if labels.size == 0
|
26
|
+
check_for_input_id_match
|
27
|
+
|
28
|
+
raise "Unable to find label with the text \"#{label_text}\""
|
29
|
+
elsif labels.size > 1
|
30
|
+
raise "Found #{labels.size} labels with the same text. Checkbox labels should be unique within a fieldset."
|
31
|
+
end
|
32
|
+
|
33
|
+
@label = labels.first
|
34
|
+
|
35
|
+
check_label_has_a_for_attribute
|
36
|
+
|
37
|
+
inputs_matching_label = page.all(id: @label[:for])
|
38
|
+
|
39
|
+
if inputs_matching_label.size == 1
|
40
|
+
@input = inputs_matching_label.first
|
41
|
+
elsif inputs_matching_label.size == 0
|
42
|
+
raise "Found the label but it is not associated with an checkbox, did not find a checkbox with the ID \"#{@label[:for]}\"."
|
43
|
+
else
|
44
|
+
raise "Found #{inputs_matching_label.size} elements with id=\"#{@label[:for]}\". IDs must be unique."
|
45
|
+
end
|
46
|
+
|
47
|
+
check_input_type_is_radio
|
48
|
+
|
49
|
+
check_label_classes
|
50
|
+
check_input_class
|
51
|
+
|
52
|
+
hints = @label.ancestor('.govuk-checkboxes__item')
|
53
|
+
.all('.govuk-hint', text: hint_text, exact_text: true, normalize_ws: true)
|
54
|
+
|
55
|
+
@hint = hints.first
|
56
|
+
|
57
|
+
check_for_hint if hint_text
|
58
|
+
check_that_hint_is_associated_with_input if @hint
|
59
|
+
check_that_checkbox_not_checked if checked
|
60
|
+
check_that_checkbox_is_checked if !checked
|
61
|
+
|
62
|
+
@label.click
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_for_input_id_match
|
66
|
+
inputs = page.all('input', id: label_text)
|
67
|
+
|
68
|
+
if inputs.size > 0
|
69
|
+
|
70
|
+
matching_labels = page.all("label[for=#{label_text}]")
|
71
|
+
|
72
|
+
if matching_labels.size > 0
|
73
|
+
raise "Use the full label text \"#{matching_labels.first.text}\" instead of the input ID"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def check_label_has_a_for_attribute
|
79
|
+
if !@label[:for]
|
80
|
+
raise 'Found the label but it is not associated with an checkbox, was missing a for="" attribute'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def check_input_type_is_radio
|
85
|
+
if @input[:type] == 'radio'
|
86
|
+
raise "Found the label, but it is associated with an input with type=\"#{@input[:type]}\" not a checkbox"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def check_label_classes
|
91
|
+
label_classes = @label[:class].to_s.split(/\s+/)
|
92
|
+
|
93
|
+
if !label_classes.include?('govuk-label') && !label_classes.include?('govuk-checkboxes__label')
|
94
|
+
raise "Found label but it is missing the govuk-label and govuk-checkboxes__label classes"
|
95
|
+
elsif !label_classes.include?('govuk-label')
|
96
|
+
raise "Found label but it is missing the govuk-label class"
|
97
|
+
elsif !label_classes.include?('govuk-checkboxes__label')
|
98
|
+
raise "Found label but it is missing the govuk-checkboxes__label class"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def check_input_class
|
103
|
+
input_classes = @input[:class].to_s.split(/\s+/)
|
104
|
+
|
105
|
+
if !input_classes.include?('govuk-checkboxes__input')
|
106
|
+
raise "Found checkbox but it is missing the govuk-checkboxes__input class"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def check_for_hint
|
112
|
+
if @hint.nil? || @hint.text != hint_text
|
113
|
+
other_hints = @label.ancestor('.govuk-checkboxes__item').all('.govuk-hint')
|
114
|
+
|
115
|
+
if other_hints.size > 0 && hint_text
|
116
|
+
raise "Found checkbox but could not find matching hint. Found the hint \"#{other_hints.first.text}\" instead"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def check_that_hint_is_associated_with_input
|
122
|
+
hint_id = @hint[:id]
|
123
|
+
|
124
|
+
if hint_id.to_s.strip == ""
|
125
|
+
if hint_text
|
126
|
+
raise "Found checkbox and hint, but the hint is not associated with the input using aria. And an ID to the hint and Add aria-describedby= to the input with that ID."
|
127
|
+
else
|
128
|
+
raise "Found checkbox, but also found a hint that is not associated with the input using aria. And an ID to the hint and Add aria-describedby= to the input with that ID."
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
if !@input["aria-describedby"].to_s.split(/\s+/).include?(hint_id)
|
133
|
+
if hint_text
|
134
|
+
raise "Found checkbox and hint, but the hint is not associated with the input using aria. Add aria-describedby=\"#{hint_id}\" to the input."
|
135
|
+
else
|
136
|
+
raise "Found checkbox, but also found a hint that is not associated with the input using aria. Add aria-describedby=\"#{hint_id}\" to the input."
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def check_that_checkbox_not_checked
|
142
|
+
if @input[:checked]
|
143
|
+
raise "Found checkbox, but it was already checked"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def check_that_checkbox_is_checked
|
148
|
+
if !@input[:checked]
|
149
|
+
raise "Found checkbox, but it was already unchecked"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
def check_govuk_checkbox(label_text, hint: nil)
|
156
|
+
GovukCheckbox.new(page: page, label_text: label_text, hint_text: hint).check
|
157
|
+
end
|
158
|
+
|
159
|
+
def uncheck_govuk_checkbox(label_text, hint: nil)
|
160
|
+
GovukCheckbox.new(page: page, label_text: label_text, hint_text: hint).uncheck
|
161
|
+
end
|
162
|
+
|
163
|
+
RSpec.configure do |rspec|
|
164
|
+
rspec.include self
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module GovukRSpecHelpers
|
2
|
+
class ChooseGovukRadio
|
3
|
+
|
4
|
+
attr_reader :page, :label_text, :hint_text
|
5
|
+
|
6
|
+
def initialize(page:, label_text:, hint_text:)
|
7
|
+
@label_text = label_text
|
8
|
+
@hint_text = hint_text
|
9
|
+
@page = page
|
10
|
+
end
|
11
|
+
|
12
|
+
def choose
|
13
|
+
labels = page.all('label', text: label_text, exact_text: true, normalize_ws: true)
|
14
|
+
|
15
|
+
if labels.size == 0
|
16
|
+
check_for_input_id_match
|
17
|
+
|
18
|
+
raise "Unable to find label with the text \"#{label_text}\""
|
19
|
+
end
|
20
|
+
|
21
|
+
@label = labels.first
|
22
|
+
|
23
|
+
inputs_matching_label = page.all(id: @label[:for])
|
24
|
+
|
25
|
+
if inputs_matching_label.size == 1
|
26
|
+
@input = inputs_matching_label.first
|
27
|
+
end
|
28
|
+
|
29
|
+
check_that_fieldset_legend_was_specified
|
30
|
+
|
31
|
+
if hint_text
|
32
|
+
check_for_hint
|
33
|
+
check_that_hint_is_associated_with_input
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
@label.click
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def check_for_input_id_match
|
43
|
+
inputs = page.all('input', id: label_text)
|
44
|
+
|
45
|
+
if inputs.size > 0
|
46
|
+
|
47
|
+
matching_labels = page.all("label[for=#{label_text}]")
|
48
|
+
|
49
|
+
if matching_labels.size > 0
|
50
|
+
raise "Use the full label text \"#{matching_labels.first.text}\" instead of the input ID"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def check_that_fieldset_legend_was_specified
|
56
|
+
if page.current_scope.is_a?(Capybara::Node::Document)
|
57
|
+
|
58
|
+
fieldset = @label.ancestor('fieldset')
|
59
|
+
legend = fieldset.find('legend')
|
60
|
+
|
61
|
+
if legend
|
62
|
+
raise "Specify the legend using: within_govuk_fieldset \"#{legend.text}\" do"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def check_for_hint
|
68
|
+
radio_item = @label.ancestor('.govuk-radios__item')
|
69
|
+
|
70
|
+
hints = radio_item.all('.govuk-hint', text: hint_text, exact_text: true, normalize_ws: true)
|
71
|
+
|
72
|
+
if hints.size == 0
|
73
|
+
other_hints = radio_item.all('.govuk-hint')
|
74
|
+
|
75
|
+
if other_hints.size > 0
|
76
|
+
raise "Found radio but could not find matching hint. Found the hint \"#{other_hints.first.text}\" instead"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
@hint = hints.first
|
81
|
+
end
|
82
|
+
|
83
|
+
def check_that_hint_is_associated_with_input
|
84
|
+
hint_id = @hint[:id]
|
85
|
+
|
86
|
+
if hint_id.to_s.strip == ""
|
87
|
+
raise "Found radio and hint, but the hint is not associated with the input using aria. And an ID to the hint and Add aria-describedby= to the input with that ID."
|
88
|
+
end
|
89
|
+
|
90
|
+
if !@input["aria-describedby"].to_s.split(/\s+/).include?(hint_id)
|
91
|
+
raise "Found radio and hint, but the hint is not associated with the input using aria. Add aria-describedby=#{hint_id} to the input."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def choose_govuk_radio(label_text, hint: nil)
|
97
|
+
ChooseGovukRadio.new(page: page, label_text: label_text, hint_text: hint).choose
|
98
|
+
end
|
99
|
+
|
100
|
+
RSpec.configure do |rspec|
|
101
|
+
rspec.include self
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module GovukRSpecHelpers
|
2
|
+
class ClickButton
|
3
|
+
|
4
|
+
attr_reader :page, :button_text, :disabled
|
5
|
+
|
6
|
+
def initialize(page:, button_text:, disabled: false)
|
7
|
+
@page = page
|
8
|
+
@button_text = button_text
|
9
|
+
@disabled = disabled
|
10
|
+
end
|
11
|
+
|
12
|
+
def click
|
13
|
+
@buttons = page.all('button', text: button_text, exact_text: true)
|
14
|
+
|
15
|
+
if @buttons.empty?
|
16
|
+
@buttons = page.all('a.govuk-button', text: button_text, exact_text: true)
|
17
|
+
end
|
18
|
+
|
19
|
+
if @buttons.empty?
|
20
|
+
@buttons = page.all("input[type=submit][value=\"#{Capybara::Selector::CSS.escape(button_text)}\"]")
|
21
|
+
end
|
22
|
+
|
23
|
+
if @buttons.size == 0
|
24
|
+
check_for_inexact_match
|
25
|
+
raise "Unable to find button \"#{button_text}\""
|
26
|
+
end
|
27
|
+
|
28
|
+
check_that_button_text_is_unique_on_the_page
|
29
|
+
|
30
|
+
@button = @buttons.first
|
31
|
+
|
32
|
+
check_data_module_attribute_is_present
|
33
|
+
check_role_is_present_if_button_is_a_link
|
34
|
+
check_button_is_not_draggable_if_button_is_a_link
|
35
|
+
check_if_button_is_disabled
|
36
|
+
check_for_govuk_class
|
37
|
+
|
38
|
+
@button.click unless disabled
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def check_for_inexact_match
|
44
|
+
buttons_without_exact_match = page.all('button', text: button_text)
|
45
|
+
|
46
|
+
if buttons_without_exact_match.size > 0
|
47
|
+
raise "Unable to find button \"#{button_text}\" but did find button with the text \"#{buttons_without_exact_match.first.text}\" - include the full button text including any visually-hidden text"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def check_that_button_text_is_unique_on_the_page
|
52
|
+
if @buttons.size > 1
|
53
|
+
raise "There are #{@buttons.size} buttons with the text \"#{button_text}\" - buttons should be unique within a page"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def check_data_module_attribute_is_present
|
58
|
+
if @button["data-module"] != "govuk-button"
|
59
|
+
raise "Button is missing the data-module=\"govuk-button\" attribute"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def check_role_is_present_if_button_is_a_link
|
64
|
+
if @button.tag_name == 'a' && @button["role"] != "button"
|
65
|
+
raise "Button found, but `role=\"button\"` is missing, this is needed on links styled as buttons"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_button_is_not_draggable_if_button_is_a_link
|
70
|
+
if @button.tag_name == 'a' && @button["draggable"] != "false"
|
71
|
+
raise "Button found, but `draggable=\"false\"` is missing, this is needed on links styled as buttons"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def check_if_button_is_disabled
|
76
|
+
button_classes = @button[:class].to_s.split(/\s/).collect(&:strip)
|
77
|
+
|
78
|
+
if @button["disabled"] == "disabled" && !disabled
|
79
|
+
raise "Button is disabled. Avoid using disabled buttons as they have poor contrast and can confuse users. If this is unavoidable, use click_govuk_button(\"#{button_text}\", disabled: true)"
|
80
|
+
end
|
81
|
+
|
82
|
+
if disabled && !button_classes.include?('govuk-button--disabled')
|
83
|
+
raise "Disabled button is missing the govuk-button--disabled class"
|
84
|
+
end
|
85
|
+
|
86
|
+
if disabled && @button["aria-disabled"].to_s != "true"
|
87
|
+
raise 'Disabled button is missing aria-disabled="true"'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def check_for_govuk_class
|
92
|
+
button_classes = @button[:class].to_s.split(/\s/).collect(&:strip)
|
93
|
+
if button_classes.empty?
|
94
|
+
raise "Button is missing a class, should contain \"govuk-button\""
|
95
|
+
elsif !button_classes.include?('govuk-button')
|
96
|
+
raise "Button is missing the govuk-button class, contains #{button_classes.join(', ')}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
def click_govuk_button(button_text, disabled: false)
|
103
|
+
ClickButton.new(page: page, button_text: button_text, disabled: disabled).click
|
104
|
+
end
|
105
|
+
|
106
|
+
RSpec.configure do |rspec|
|
107
|
+
rspec.include self
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module GovukRSpecHelpers
|
2
|
+
class ClickLink
|
3
|
+
|
4
|
+
attr_reader :link_text, :page
|
5
|
+
|
6
|
+
VALID_LINK_CLASSES = ["govuk-link", "govuk-breadcrumbs__link", "govuk-back-link", "govuk-header__link", "govuk-footer__link", "govuk-notification-banner__link", "govuk-skip-link", "govuk-tabs__tab"]
|
7
|
+
|
8
|
+
AMIBIGOUS_LINK_TEXTS = ["Change", "Add", "Remove"]
|
9
|
+
|
10
|
+
def initialize(page:, link_text:)
|
11
|
+
@page = page
|
12
|
+
@link_text = link_text
|
13
|
+
end
|
14
|
+
|
15
|
+
def click
|
16
|
+
@links = page.all('a', text: link_text, exact_text: true, normalize_ws: true)
|
17
|
+
|
18
|
+
if @links.size == 0
|
19
|
+
check_whether_there_is_an_inexact_match
|
20
|
+
check_whether_there_is_an_href_match
|
21
|
+
check_whether_there_is_an_id_match
|
22
|
+
|
23
|
+
raise "Unable to find link \"#{link_text}\""
|
24
|
+
end
|
25
|
+
|
26
|
+
check_link_text_is_unique
|
27
|
+
check_link_text_is_not_ambiguous
|
28
|
+
|
29
|
+
@link = @links.first
|
30
|
+
@link_classes = @link[:class].to_s.split(/\s/).collect(&:strip)
|
31
|
+
|
32
|
+
check_link_is_not_styled_as_button
|
33
|
+
check_link_warns_if_opening_in_a_new_tab
|
34
|
+
check_link_does_not_contain_a_button
|
35
|
+
check_link_has_a_valid_class
|
36
|
+
|
37
|
+
@link.click
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def check_whether_there_is_an_inexact_match
|
43
|
+
links_without_exact_match = page.all('a', text: link_text)
|
44
|
+
if page.all('a', text: link_text).size > 0
|
45
|
+
raise "Unable to find link \"#{link_text}\" but did find link with the text \"#{links_without_exact_match.first.text}\" - include the full link text including any visually-hidden text"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_whether_there_is_an_href_match
|
50
|
+
links_with_href_match = page.all(:link, href: link_text)
|
51
|
+
if links_with_href_match.size > 0
|
52
|
+
raise "Use the full link text within click_govuk_link() instead of the link href"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_whether_there_is_an_id_match
|
57
|
+
links_with_id_match = page.all(:link, link_text)
|
58
|
+
if links_with_id_match.size > 0
|
59
|
+
raise "Use the full link text within click_govuk_link() instead of the link id"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def check_link_text_is_not_ambiguous
|
64
|
+
if AMIBIGOUS_LINK_TEXTS.include?(link_text.strip)
|
65
|
+
raise "The link was found, but the text \"#{link_text}\" is ambiguous if heard out of context - add some visually-hidden text"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_link_text_is_unique
|
70
|
+
if @links.size > 1
|
71
|
+
raise "There are #{@links.size} links with the link text \"#{link_text}\" - links should be unique within a page"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def check_link_is_not_styled_as_button
|
76
|
+
if @link_classes.include?('govuk-button')
|
77
|
+
raise "The link was found, but is styled as a button. Use `click_govuk_button` instead."
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def check_link_warns_if_opening_in_a_new_tab
|
82
|
+
if @link[:target] == "_blank" && !link_text.include?("opens in new tab")
|
83
|
+
raise "The link was found, but is set to open in a new tab. Either remove this, or add \"(opens in new tab)\" to the link text"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def check_link_does_not_contain_a_button
|
88
|
+
if @link.all('button').any?
|
89
|
+
raise "The link was found, but it contains a button – use either a link or button but not both"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def check_link_has_a_valid_class
|
94
|
+
if @link_classes.empty?
|
95
|
+
raise "\"#{link_text}\" link is missing a class, should contain \"govuk-link\""
|
96
|
+
elsif !@link_classes.any? {|link_class| VALID_LINK_CLASSES.include?(link_class) }
|
97
|
+
raise "\"#{link_text}\" link is missing a govuk-link class, contains #{@link_classes.join(', ')}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def click_govuk_link(link_text)
|
103
|
+
ClickLink.new(page: page, link_text: link_text).click
|
104
|
+
end
|
105
|
+
|
106
|
+
RSpec.configure do |rspec|
|
107
|
+
rspec.include self
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module GovukRSpecHelpers
|
2
|
+
class FillInGovUKTextField
|
3
|
+
|
4
|
+
attr_reader :page, :label, :hint, :with
|
5
|
+
|
6
|
+
def initialize(page:, label:, hint:, with:)
|
7
|
+
@page = page
|
8
|
+
@label = label
|
9
|
+
@hint = hint
|
10
|
+
@with = with
|
11
|
+
end
|
12
|
+
|
13
|
+
def fill_in
|
14
|
+
labels = page.all('label', text: label, exact_text: true, normalize_ws: true)
|
15
|
+
|
16
|
+
if labels.size == 0
|
17
|
+
check_for_inexact_label_match
|
18
|
+
check_for_field_name_match
|
19
|
+
|
20
|
+
raise "Unable to find label with the text #{label}"
|
21
|
+
end
|
22
|
+
|
23
|
+
@label = labels.first
|
24
|
+
|
25
|
+
check_label_has_a_for_attribute
|
26
|
+
|
27
|
+
label_for = @label[:for]
|
28
|
+
@inputs = page.all(id: label_for)
|
29
|
+
|
30
|
+
check_label_is_associated_with_a_field
|
31
|
+
check_there_is_only_1_element_with_the_associated_id
|
32
|
+
|
33
|
+
@input = @inputs.first
|
34
|
+
|
35
|
+
check_associated_element_is_a_form_field
|
36
|
+
check_input_type_is_text
|
37
|
+
|
38
|
+
aria_described_by_ids = @input["aria-describedby"].to_s.strip.split(/\s+/)
|
39
|
+
|
40
|
+
@described_by_elements = []
|
41
|
+
|
42
|
+
if aria_described_by_ids.size > 0
|
43
|
+
aria_described_by_ids.each do |aria_described_by_id|
|
44
|
+
|
45
|
+
check_there_is_only_one_element_with_id(aria_described_by_id)
|
46
|
+
@described_by_elements << page.find(id: aria_described_by_id)
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if hint
|
52
|
+
check_field_is_described_by_a_hint
|
53
|
+
check_hint_matches_text_given
|
54
|
+
end
|
55
|
+
|
56
|
+
@input.set(with)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def check_for_inexact_label_match
|
62
|
+
labels_not_using_exact_match = page.all('label', text: label)
|
63
|
+
if labels_not_using_exact_match.size > 0
|
64
|
+
raise "Unable to find label with the text \"#{label}\" but did find label with the text \"#{labels_not_using_exact_match.first.text}\" - use the full label text"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def check_for_field_name_match
|
69
|
+
inputs_matching_name = page.all("input[name=\"#{label}\"]")
|
70
|
+
|
71
|
+
if inputs_matching_name.size > 0
|
72
|
+
|
73
|
+
input_matching_name = inputs_matching_name.first
|
74
|
+
|
75
|
+
labels = page.all("label[for=\"#{input_matching_name['id']}\"]")
|
76
|
+
|
77
|
+
if labels.size > 0
|
78
|
+
raise "Use the full label text \"#{labels.first.text}\" instead of the field name"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def check_label_has_a_for_attribute
|
84
|
+
if @label[:for].to_s.strip == ""
|
85
|
+
raise "Found the label but it is missing a \"for\" attribute to associate it with an input"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def check_label_is_associated_with_a_field
|
90
|
+
if @inputs.size == 0
|
91
|
+
raise "Found the label but there is no field with the ID \"#{@label[:for]}\" which matches the label‘s \"for\" attribute"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def check_there_is_only_1_element_with_the_associated_id
|
96
|
+
if @inputs.size > 1
|
97
|
+
raise "Found the label but there there are #{@inputs.size} elements with the ID \"#{@label[:for]}\" which matches the label‘s \"for\" attribute"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def check_associated_element_is_a_form_field
|
102
|
+
if !['input', 'textarea', 'select'].include?(@input.tag_name)
|
103
|
+
raise "Found the label but but it is associated with a <#{@input.tag_name}> element instead of a form field"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def check_input_type_is_text
|
108
|
+
raise "Found the field, but it has type=\"#{@input[:type]}\", expected type=\"text\"" unless @input[:type] == "text"
|
109
|
+
end
|
110
|
+
|
111
|
+
def check_field_is_described_by_a_hint
|
112
|
+
if @described_by_elements.size == 0
|
113
|
+
check_if_the_hint_exists_but_is_not_associated_with_field
|
114
|
+
|
115
|
+
raise "Found the field but could not find the hint \"#{hint}\""
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def check_if_the_hint_exists_but_is_not_associated_with_field
|
120
|
+
if page.all('.govuk-hint', text: hint).size > 0
|
121
|
+
raise "Found the field and the hint, but not field is not associated with the hint using aria-describedby"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def check_hint_matches_text_given
|
126
|
+
hint_matching_id = @described_by_elements.find {|element| element[:class].include?("govuk-hint") }
|
127
|
+
if hint_matching_id.text != hint
|
128
|
+
raise "Found the label but the associated hint is \"#{hint_matching_id.text}\" not \"#{hint}\""
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def check_there_is_only_one_element_with_id(aria_described_by_id)
|
133
|
+
elements_matching_id = page.all(id: aria_described_by_id)
|
134
|
+
if elements_matching_id.size == 0
|
135
|
+
raise "Found the field but it has an \"aria-describedby=#{aria_described_by_id}\" attribute and no hint with that ID exists"
|
136
|
+
elsif elements_matching_id.size > 1
|
137
|
+
raise "Found the field but it has an \"aria-describedby=#{aria_described_by_id}\" attribute and 2 elements with that ID exist"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
def fill_in_govuk_text_field(label, hint: nil, with:)
|
144
|
+
FillInGovUKTextField.new(page:, label:, hint:, with:).fill_in
|
145
|
+
end
|
146
|
+
|
147
|
+
RSpec.configure do |rspec|
|
148
|
+
rspec.include self
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
require 'rspec'
|
3
|
+
require 'capybara'
|
4
|
+
|
5
|
+
require_relative 'summarise_errors_matcher'
|
6
|
+
require_relative 'summarise_matcher'
|
7
|
+
|
8
|
+
require_relative 'check_govuk_checkbox'
|
9
|
+
require_relative 'click_govuk_link'
|
10
|
+
require_relative 'click_govuk_button'
|
11
|
+
require_relative 'fill_in_govuk_text_field'
|
12
|
+
require_relative 'choose_govuk_radio'
|
13
|
+
require_relative 'within_govuk_fieldset'
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module GovukRSpecHelpers
|
2
|
+
module SummariseErrorsMatcher
|
3
|
+
|
4
|
+
extend RSpec::Matchers::DSL
|
5
|
+
|
6
|
+
define :summarise_errors do |expected|
|
7
|
+
match do |_actual|
|
8
|
+
title_contains_prefix &&
|
9
|
+
error_summary_title &&
|
10
|
+
expected.all? do |expected_error|
|
11
|
+
error_messages && error_messages[expected.index(expected_error)] == expected_error
|
12
|
+
end && expected.size == error_messages.size &&
|
13
|
+
all_error_messages_contain_links &&
|
14
|
+
all_error_messages_links_are_valid
|
15
|
+
end
|
16
|
+
|
17
|
+
failure_message do |actual|
|
18
|
+
missing_error = expected.find {|expected_error| !error_messages.include?(expected_error) }
|
19
|
+
if !title
|
20
|
+
"Missing <title> tag"
|
21
|
+
elsif !title_contains_prefix
|
22
|
+
"Title tag is missing the error prefix: ‘#{title}’"
|
23
|
+
elsif !error_summary_title
|
24
|
+
"Missing an error summary title"
|
25
|
+
elsif missing_error
|
26
|
+
"Missing error message: ‘#{missing_error}’"
|
27
|
+
elsif !all_error_messages_contain_links
|
28
|
+
"Error message ‘#{error_message_missing_link}’ isn’t linked to anything"
|
29
|
+
elsif !all_error_messages_links_are_valid
|
30
|
+
"Error message ‘#{error_message_with_invalid_link.text(normalize_ws: true)}’ links to ##{error_message_with_invalid_link[:href].split('#').last} but no input field has this ID or name"
|
31
|
+
elsif expected.size == error_messages.size
|
32
|
+
"Error messages appear in a different order"
|
33
|
+
elsif expected.size < error_messages.size
|
34
|
+
"An extra error message is present"
|
35
|
+
else
|
36
|
+
"Unexpected error"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def html
|
41
|
+
@html ||= Capybara::Node::Simple.new(actual.is_a?(String) ? actual : actual.html)
|
42
|
+
end
|
43
|
+
|
44
|
+
def title
|
45
|
+
@title ||= html.title
|
46
|
+
end
|
47
|
+
|
48
|
+
def title_contains_prefix
|
49
|
+
title.to_s.start_with?("Error: ")
|
50
|
+
end
|
51
|
+
|
52
|
+
def all_error_messages_contain_links
|
53
|
+
error_message_items.all? do |error_message_item|
|
54
|
+
error_message_item.all(:link).first
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def all_error_messages_links_are_valid
|
59
|
+
error_message_items.all? do |error_message_item|
|
60
|
+
link = error_message_item.all(:link).first
|
61
|
+
|
62
|
+
if link
|
63
|
+
link_fragment = link[:href].split('#').last
|
64
|
+
|
65
|
+
link_target= html.all(id: link_fragment).first || html.all(:field, name: link_fragment).first
|
66
|
+
|
67
|
+
link_target
|
68
|
+
else
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def error_message_with_invalid_link
|
75
|
+
invalid_link = error_message_links.find do |error_message_link|
|
76
|
+
link_fragment = error_message_link[:href].split('#').last
|
77
|
+
|
78
|
+
link_target= html.all(id: link_fragment).first || html.all(:field, name: link_fragment).first
|
79
|
+
|
80
|
+
!link_target
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def error_message_missing_link
|
85
|
+
error_message_items.find do |error_message_item|
|
86
|
+
!error_message_item.all(:link).first
|
87
|
+
end.text(normalize_ws: true)
|
88
|
+
end
|
89
|
+
|
90
|
+
def error_messages
|
91
|
+
@error_messages ||= (error_summary_list && error_summary_list.all('li').collect {|li| li.text(normalize_ws: true) } || [])
|
92
|
+
end
|
93
|
+
|
94
|
+
def error_message_links
|
95
|
+
@error_message_links ||= error_message_items.collect {|item| item.all(:link).first }
|
96
|
+
end
|
97
|
+
|
98
|
+
def error_message_items
|
99
|
+
@error_message_items ||= (error_summary_list && error_summary_list.all('li') || [])
|
100
|
+
end
|
101
|
+
|
102
|
+
def error_summary_list
|
103
|
+
@error_summary_list ||= (error_summary && error_summary.all('.govuk-error-summary__list').first)
|
104
|
+
end
|
105
|
+
|
106
|
+
def error_summary_title
|
107
|
+
@error_summary_title ||= error_summary.all('h2.govuk-error-summary__title').first
|
108
|
+
end
|
109
|
+
|
110
|
+
def error_summary
|
111
|
+
@error_summary ||= html.all('.govuk-error-summary').first
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
RSpec.configure do |rspec|
|
116
|
+
rspec.include self
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module GovukRSpecHelpers
|
2
|
+
module SummariseMatcher
|
3
|
+
|
4
|
+
extend RSpec::Matchers::DSL
|
5
|
+
|
6
|
+
define :summarise do |expected|
|
7
|
+
match do |_actual|
|
8
|
+
if expected[:action]
|
9
|
+
key_html && value_html && value_match? && action_link && action_link[:href] == expected[:action][:href]
|
10
|
+
else
|
11
|
+
key_html && value_html && value_match?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
failure_message do |actual|
|
16
|
+
if !key_html
|
17
|
+
"Could not find the key ‘#{expected[:key]}’ within\n\n #{actual}"
|
18
|
+
elsif !value_html
|
19
|
+
"Could not find a <dd class=\"govuk-summary-list__value\"> element within HTML: \n#{row_html.native.to_html}"
|
20
|
+
elsif value_text != expected[:value]
|
21
|
+
"Expected ‘#{expected[:key]}’ value to be ‘#{expected[:value]}’ but was ‘#{value_text}’"
|
22
|
+
elsif !action_link
|
23
|
+
"Could not find the link ‘#{expected[:action][:text]}’ within HTML: \n#{actions_html.native.to_html}"
|
24
|
+
else
|
25
|
+
"Expected link href to be #{expected[:action][:href]}, was #{action_link[:href]}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def html
|
30
|
+
@html ||= Capybara::Node::Simple.new(actual.is_a?(String) ? actual : actual.html)
|
31
|
+
end
|
32
|
+
|
33
|
+
def key_html
|
34
|
+
@key_html ||= html.all('dt.govuk-summary-list__key', exact_text: expected[:key], normalize_ws: true).first
|
35
|
+
end
|
36
|
+
|
37
|
+
def row_html
|
38
|
+
@row_html ||= key_html.ancestor('.govuk-summary-list__row')
|
39
|
+
end
|
40
|
+
|
41
|
+
def actions_html
|
42
|
+
@actions_html ||= row_html.all('dd.govuk-summary-list__actions').first
|
43
|
+
end
|
44
|
+
|
45
|
+
def value_html
|
46
|
+
@value_html ||= row_html.all('dd.govuk-summary-list__value').first
|
47
|
+
end
|
48
|
+
|
49
|
+
def value_text
|
50
|
+
@value_text ||= value_html.text(normalize_ws: true)
|
51
|
+
end
|
52
|
+
|
53
|
+
def action_link
|
54
|
+
@action_link ||= actions_html.all(:link, exact_text: expected[:action][:text], normalize_ws: true).first
|
55
|
+
end
|
56
|
+
|
57
|
+
def value_match?
|
58
|
+
if expected[:value].is_a?(Regexp)
|
59
|
+
value_text.match?(expected[:value])
|
60
|
+
else
|
61
|
+
value_text == expected[:value]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
RSpec.configure do |rspec|
|
67
|
+
rspec.include self
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module GovukRSpecHelpers
|
2
|
+
class WithinGovukFieldset
|
3
|
+
|
4
|
+
attr_reader :page, :legend_text, :hint, :block
|
5
|
+
|
6
|
+
def initialize(page:, legend_text:, hint:, block:)
|
7
|
+
@legend_text = legend_text
|
8
|
+
@hint = hint
|
9
|
+
@page = page
|
10
|
+
@block = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def within
|
14
|
+
|
15
|
+
legends = page.all('legend', text: legend_text, exact_text: true, normalize_ws: true)
|
16
|
+
|
17
|
+
if legends.size == 0
|
18
|
+
check_for_fieldset_id_match
|
19
|
+
raise "No fieldset found with matching legend"
|
20
|
+
end
|
21
|
+
|
22
|
+
legend = legends.first
|
23
|
+
@fieldset = legend.ancestor('fieldset')
|
24
|
+
|
25
|
+
check_hint
|
26
|
+
|
27
|
+
@page.within(@fieldset) do
|
28
|
+
block.call
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def check_hint
|
35
|
+
if hint
|
36
|
+
hint_elements = @fieldset.all('.govuk-hint', text: hint, exact_text: true, normalize_ws: true)
|
37
|
+
|
38
|
+
if hint_elements.size == 0
|
39
|
+
raise "Count not find hint with that text"
|
40
|
+
end
|
41
|
+
|
42
|
+
hint_id = hint_elements.first[:id]
|
43
|
+
|
44
|
+
if !@fieldset["aria-describedby"].to_s.split(/\s+/).include?(hint_id)
|
45
|
+
raise "Found hint but it is not associated with the fieldset using aria-describedby"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def check_for_fieldset_id_match
|
52
|
+
matching_fieldsets = page.all("fieldset[id=#{Capybara::Selector::CSS.escape(legend_text)}]")
|
53
|
+
|
54
|
+
if matching_fieldsets.size > 0
|
55
|
+
|
56
|
+
legends = matching_fieldsets.first.all('legend')
|
57
|
+
|
58
|
+
if legends.size > 0
|
59
|
+
raise "Use the full legend text \"#{legends.first.text}\" instead of the fieldset ID"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
def within_govuk_fieldset(legend_text, hint: nil, &block)
|
67
|
+
WithinGovukFieldset.new(page: page, legend_text: legend_text, hint: hint, block: block).within
|
68
|
+
end
|
69
|
+
|
70
|
+
RSpec.configure do |rspec|
|
71
|
+
rspec.include self
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: govuk-rspec-helpers
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Frankie Roberto
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-10-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: capybara
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.24'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.24'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-expectations
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: ''
|
56
|
+
email:
|
57
|
+
- frankie@frankieroberto.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- LICENSE.txt
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- lib/check_govuk_checkbox.rb
|
66
|
+
- lib/choose_govuk_radio.rb
|
67
|
+
- lib/click_govuk_button.rb
|
68
|
+
- lib/click_govuk_link.rb
|
69
|
+
- lib/fill_in_govuk_text_field.rb
|
70
|
+
- lib/govuk_rspec_helpers.rb
|
71
|
+
- lib/summarise_errors_matcher.rb
|
72
|
+
- lib/summarise_matcher.rb
|
73
|
+
- lib/within_govuk_fieldset.rb
|
74
|
+
homepage: https://x-govuk.github.io
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata:
|
78
|
+
homepage_uri: https://x-govuk.github.io
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 3.1.4
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubygems_version: 3.4.10
|
95
|
+
signing_key:
|
96
|
+
specification_version: 4
|
97
|
+
summary: RSpec test helpers for GOV.UK services
|
98
|
+
test_files: []
|