govspeak 6.5.11 → 6.7.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a45a14e5a726d918753171dc957cc1ed29e50d4505685ff19271da6cad41945d
4
- data.tar.gz: 27d874bc05f80a2c37edae5010a326b426cbcf1663d5bfcc1bedc9d6852e1e02
3
+ metadata.gz: 3edb8b047b41d9c15581ec77b6b4ba829dae69f64729265deaf5cad25b91744a
4
+ data.tar.gz: 2e899be9ea70576afb234804213e8697278483a77118dc5f03857bdf7ca0e49d
5
5
  SHA512:
6
- metadata.gz: 5a01955112fab442c130f71c93e4c2a743c1c9d7a16797d5a2ef406bc7ad591f8380b90f03574d373cf8a519d40e4828b880a9a45d1e36b8c5128f423ac1e20f
7
- data.tar.gz: 7ae23223d912b5753eefc4568190fd74e36968a6697e6ef5eeefeff65d88d63f0ec763122b716a4a6179b1c188121c349bb8f9fb6c74ccd346906084cdf7e184
6
+ metadata.gz: 8779eb8e734e660d3ce9b7e020f18c14792ba1488fb92f59d23455e0cf5474f2ded6d376bd3c25e0b09c7e27362a5e56a271bf1f3084ce60a8cc355d07a97b34
7
+ data.tar.gz: 569414d843883df41a9bd9abbc286b94667e50b0156321468d01008980fd28863ef65e127a422a7e55c4793b649901000a6a474e0450903a801287880fa75af6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## Unreleased
2
+
3
+ * Minimum Ruby version specified at 2.6 [215](https://github.com/alphagov/govspeak/pull/215)
4
+
5
+ ## 6.7.2
6
+
7
+ * Fix footnotes in legislative lists [216](https://github.com/alphagov/govspeak/pull/216)
8
+
9
+ ## 6.7.1
10
+
11
+ * Update failing test [212](https://github.com/alphagov/govspeak/pull/212)
12
+ * Fix stats headline HTML semantics [213](https://github.com/alphagov/govspeak/pull/213)
13
+
14
+ ## 6.7.0
15
+
16
+ * Update heading & docs [#206](https://github.com/alphagov/govspeak/pull/206)
17
+
18
+ ## 6.6.0
19
+
20
+ * Allow passed elements to be relaxed from sanitization [#203](https://github.com/alphagov/govspeak/pull/203)
21
+
1
22
  ## 6.5.11
2
23
 
3
24
  * Fix issue rendering $CTA blocks before $C (PR#202)
data/README.md CHANGED
@@ -18,6 +18,23 @@ then create a new document
18
18
  doc = Govspeak::Document.new "^Test^"
19
19
  puts doc.to_html
20
20
 
21
+ ## Changes appearing across GOV.UK
22
+
23
+ Some additional steps or considerations are needed to ensure changes to govspeak cascade across GOV.UK in a holistic way.
24
+
25
+ Once govspeak has been updated and version incremented then:
26
+ - [`govuk_publishing_components` govspeak](https://components.publishing.service.gov.uk/component-guide/govspeak) will also need updating to reflect your most recent change.
27
+ - [Publishing apps](https://docs.publishing.service.gov.uk/apps.html) (including but not limited to [content-publisher](https://github.com/alphagov/content-publisher) & [whitehall](https://github.com/alphagov/whitehall)) also use govspeak, these apps will need to be released with the new govspeak version present.
28
+
29
+ Also, consider if:
30
+ - [whitehall](https://github.com/alphagov/whitehall) needs updating (as custom govspeak changes are present)
31
+ - [govuk-content-schema](https://github.com/alphagov/govuk-content-schemas) needs updating
32
+ - [govpspeak-preview](https://github.com/alphagov/govspeak-preview) is worth updating
33
+
34
+ Any pages that use govspeak to generate Content will need to *republished* in order for the new changes to be reflected.
35
+
36
+ - Data Labs can help identify which pages need updating by [submitting a request](https://gov-uk.atlassian.net/wiki/spaces/GOVUK/pages/1860075525/GOV.UK+Data+Labs#Submitting-a-data-science-request) and [#govuk-2ndline](https://docs.publishing.service.gov.uk/manual/2nd-line.html) can help with republishing
37
+
21
38
  # Extensions
22
39
 
23
40
  In addition to the [standard Markdown syntax](http://daringfireball.net/projects/markdown/syntax "Markdown syntax"), we have added our own extensions.
@@ -106,9 +123,9 @@ Statistic headlines highlight important numbers in content. Displays a statistic
106
123
  Creates the following:
107
124
 
108
125
  ```html
109
- <aside class="stat-headline">
126
+ <div class="stat-headline">
110
127
  <p><em>13.8bn</em> years since the big bang</p>
111
- </aside>
128
+ </div>
112
129
  ```
113
130
 
114
131
  ## Points of Contact
@@ -598,8 +615,8 @@ will output
598
615
  ```html
599
616
  <div id="contact_123" class="contact">
600
617
  <div class="content">
601
- <h3>Government Digital Service</h3>
602
618
  <div class="vcard contact-inner">
619
+ <p>Government Digital Service</p>
603
620
  <div class="email-url-number">
604
621
  <p class="email">
605
622
  <span class="type">Email</span>
@@ -676,4 +693,4 @@ which outputs
676
693
  Start Now
677
694
  <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg>
678
695
  </a>
679
- ```
696
+ ```
@@ -30,9 +30,10 @@ module Govspeak
30
30
  def find_headers(parent)
31
31
  headers = []
32
32
 
33
- if parent.type == :header
33
+ case parent.type
34
+ when :header
34
35
  headers << build_header(parent)
35
- elsif parent.type == :html_element
36
+ when :html_element
36
37
  parent.children.each do |child|
37
38
  if child.type == :header
38
39
  headers << build_header(child)
@@ -40,18 +40,19 @@ class Govspeak::HtmlSanitizer
40
40
  @allowed_image_hosts = options[:allowed_image_hosts]
41
41
  end
42
42
 
43
- def sanitize
43
+ def sanitize(allowed_elements: [])
44
44
  transformers = [TableCellTextAlignWhitelister.new]
45
45
  if @allowed_image_hosts && @allowed_image_hosts.any?
46
46
  transformers << ImageSourceWhitelister.new(@allowed_image_hosts)
47
47
  end
48
- Sanitize.clean(@dirty_html, Sanitize::Config.merge(sanitize_config, transformers: transformers))
48
+
49
+ Sanitize.clean(@dirty_html, Sanitize::Config.merge(sanitize_config(allowed_elements: allowed_elements), transformers: transformers))
49
50
  end
50
51
 
51
- def sanitize_config
52
+ def sanitize_config(allowed_elements: [])
52
53
  Sanitize::Config.merge(
53
54
  Sanitize::Config::RELAXED,
54
- elements: Sanitize::Config::RELAXED[:elements] + %w[govspeak-embed-attachment govspeak-embed-attachment-link svg path],
55
+ elements: Sanitize::Config::RELAXED[:elements] + %w[govspeak-embed-attachment govspeak-embed-attachment-link svg path].concat(allowed_elements),
55
56
  attributes: {
56
57
  :all => Sanitize::Config::RELAXED[:attributes][:all] + %w[role aria-label],
57
58
  "a" => Sanitize::Config::RELAXED[:attributes]["a"] + [:data],
@@ -126,7 +126,7 @@ module Govspeak
126
126
  el.content = "[footnote #{footnote_number}]"
127
127
  end
128
128
  document.css("[role='doc-backlink']").map do |el|
129
- backlink_number = " " + el.css("sup")[0].content if el.css("sup")[0].present?
129
+ backlink_number = " #{el.css('sup')[0].content}" if el.css("sup")[0].present?
130
130
  el["aria-label"] = "go to where this is referenced#{backlink_number}"
131
131
  end
132
132
  end
@@ -4,6 +4,7 @@ require "htmlentities"
4
4
  module Govspeak
5
5
  class AttachmentPresenter
6
6
  attr_reader :attachment
7
+
7
8
  include ActionView::Helpers::TagHelper
8
9
  include ActionView::Helpers::NumberHelper
9
10
  include ActionView::Helpers::TextHelper
@@ -25,7 +26,7 @@ module Govspeak
25
26
  end
26
27
 
27
28
  def file_extension
28
- # Note: this is a separate parameter rather than being calculated from the
29
+ # NOTE: this is a separate parameter rather than being calculated from the
29
30
  # filename because at the time of writing not all apps were using the effects
30
31
  # of this field.
31
32
  attachment[:file_extension]
@@ -23,30 +23,30 @@ module Govspeak
23
23
 
24
24
  def post_addresses
25
25
  @post_addresses ||= begin
26
- addresses = contact.dig(:details, :post_addresses) || []
27
- filter_post_addresses(addresses)
28
- end
26
+ addresses = contact.dig(:details, :post_addresses) || []
27
+ filter_post_addresses(addresses)
28
+ end
29
29
  end
30
30
 
31
31
  def email_addresses
32
32
  @email_addresses ||= begin
33
- emails = contact.dig(:details, :email_addresses) || []
34
- emails.select { |e| e[:email].present? }
35
- end
33
+ emails = contact.dig(:details, :email_addresses) || []
34
+ emails.select { |e| e[:email].present? }
35
+ end
36
36
  end
37
37
 
38
38
  def phone_numbers
39
39
  @phone_numbers ||= begin
40
- phone_numbers = contact.dig(:details, :phone_numbers) || []
41
- phone_numbers.select { |p| p[:number].present? }
42
- end
40
+ phone_numbers = contact.dig(:details, :phone_numbers) || []
41
+ phone_numbers.select { |p| p[:number].present? }
42
+ end
43
43
  end
44
44
 
45
45
  def contact_form_links
46
46
  @contact_form_links ||= begin
47
- contact_form_links = contact.dig(:details, :contact_form_links) || []
48
- contact_form_links.select { |c| c[:link].present? }
49
- end
47
+ contact_form_links = contact.dig(:details, :contact_form_links) || []
48
+ contact_form_links.select { |c| c[:link].present? }
49
+ end
50
50
  end
51
51
 
52
52
  private
@@ -1,3 +1,3 @@
1
1
  module Govspeak
2
- VERSION = "6.5.11".freeze
2
+ VERSION = "6.7.2".freeze
3
3
  end
data/lib/govspeak.rb CHANGED
@@ -53,6 +53,7 @@ module Govspeak
53
53
  @source = source ? source.dup : ""
54
54
 
55
55
  @images = options.delete(:images) || []
56
+ @allowed_elements = options.delete(:allowed_elements) || []
56
57
  @attachments = Array.wrap(options.delete(:attachments))
57
58
  @links = Array.wrap(options.delete(:links))
58
59
  @contacts = Array.wrap(options.delete(:contacts))
@@ -61,18 +62,30 @@ module Govspeak
61
62
  sanitize: true,
62
63
  syntax_highlighter: nil }.merge(options)
63
64
  @options[:entity_output] = :symbolic
65
+ @footnote_definition_html = nil
66
+ @acronyms = []
64
67
  end
65
68
 
66
69
  def to_html
67
70
  @to_html ||= begin
68
- html = if @options[:sanitize]
69
- HtmlSanitizer.new(kramdown_doc.to_html).sanitize
70
- else
71
- kramdown_doc.to_html
72
- end
71
+ html = if @options[:sanitize]
72
+ HtmlSanitizer.new(kramdown_doc.to_html).sanitize(allowed_elements: @allowed_elements)
73
+ else
74
+ kramdown_doc.to_html
75
+ end
76
+
77
+ unless @footnote_definition_html.nil?
78
+ regex = /<div class="footnotes".*[<\/div>]/m
79
+
80
+ if html.scan(regex).empty?
81
+ html << @footnote_definition_html
82
+ else
83
+ html.gsub!(regex, @footnote_definition_html)
84
+ end
85
+ end
73
86
 
74
- Govspeak::PostProcessor.process(html, self)
75
- end
87
+ Govspeak::PostProcessor.process(html, self)
88
+ end
76
89
  end
77
90
 
78
91
  def to_liquid
@@ -109,6 +122,9 @@ module Govspeak
109
122
  def preprocess(source)
110
123
  source = Govspeak::BlockquoteExtraQuoteRemover.remove(source)
111
124
  source = remove_forbidden_characters(source)
125
+
126
+ legislative_list_footnote_definitions(source)
127
+
112
128
  self.class.extensions.each do |_, regexp, block|
113
129
  source.gsub!(regexp) do
114
130
  instance_exec(*Regexp.last_match.captures, &block)
@@ -117,6 +133,40 @@ module Govspeak
117
133
  source
118
134
  end
119
135
 
136
+ def legislative_list_footnote_definitions(source)
137
+ is_legislative_list = source.scan(/\$LegislativeList.*?\[\^\d\]*.*?\$EndLegislativeList/m).size.positive?
138
+ footnotes = source.scan(/\[\^(\d)\]:(.*)/)
139
+ @acronyms = source.scan(/(?<=\*)\[(.*)\]:(.*)/)
140
+
141
+ if is_legislative_list && footnotes.size.positive?
142
+ list_items = footnotes.map do |footnote|
143
+ number = footnote[0]
144
+ text = footnote[1].strip
145
+ footnote_definition = Govspeak::Document.new(text).to_html[/(?<=<p>).*(?=<\/p>)/]
146
+
147
+ <<~HTML_SNIPPET
148
+ <li id="fn:#{number}" role="doc-endnote">
149
+ <p>
150
+ #{footnote_definition}<a href="#fnref:#{number}" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
151
+ </p>
152
+ </li>
153
+ HTML_SNIPPET
154
+ end
155
+
156
+ @footnote_definition_html = <<~HTML_CONTAINER
157
+ <div class="footnotes" role="doc-endnotes">
158
+ <ol>
159
+ #{list_items.join.strip}
160
+ </ol>
161
+ </div>
162
+ HTML_CONTAINER
163
+ end
164
+
165
+ unless @footnote_definition_html.nil? && @acronyms.size.positive?
166
+ add_acronym_alt_text(@footnote_definition_html)
167
+ end
168
+ end
169
+
120
170
  def remove_forbidden_characters(source)
121
171
  # These are characters that are not deemed not suitable for
122
172
  # markup: https://www.w3.org/TR/unicode-xml/#Charlist
@@ -160,7 +210,7 @@ module Govspeak
160
210
  ([^)]+) # capture inside of link text markdown
161
211
  \) # match end of link text markdown
162
212
  \s* # any whitespace between opening bracket and link
163
- {\/button} # match ending bracket
213
+ {/button} # match ending bracket
164
214
  (?:\r|\n|$) # non-capturing match to make sure end of line and linebreak
165
215
  }x) do |attributes, text, href|
166
216
  button_classes = "govuk-button"
@@ -184,8 +234,8 @@ module Govspeak
184
234
  end
185
235
 
186
236
  extension("stat-headline", %r${stat-headline}(.*?){/stat-headline}$m) do |body|
187
- %(\n\n<aside class="stat-headline">
188
- #{Govspeak::Document.new(body.strip).to_html}</aside>\n)
237
+ %(\n\n<div class="stat-headline">
238
+ #{Govspeak::Document.new(body.strip).to_html}</div>\n)
189
239
  end
190
240
 
191
241
  # FIXME: these surrounded_by arguments look dodgy
@@ -294,6 +344,18 @@ module Govspeak
294
344
  doc.gsub!("<ul>", "<ol>")
295
345
  doc.gsub!("</ul>", "</ol>")
296
346
  doc.sub!("<ol>", '<ol class="legislative-list">')
347
+
348
+ footnotes = body.scan(/\[\^(\d+)\]/).flatten
349
+
350
+ footnotes.each do |footnote|
351
+ html = "<sup id=\"fnref:#{footnote}\" role=\"doc-noteref\">" \
352
+ "<a href=\"#fn:#{footnote}\" class=\"footnote\" rel=\"footnote\">" \
353
+ "[footnote #{footnote}]</a></sup>"
354
+
355
+ doc.sub!(/(\[\^#{footnote}\])/, html)
356
+ end
357
+
358
+ add_acronym_alt_text(doc) if @acronyms.size.positive?
297
359
  end
298
360
  end
299
361
  end
@@ -385,6 +447,12 @@ module Govspeak
385
447
  def encode(text)
386
448
  HTMLEntities.new.encode(text)
387
449
  end
450
+
451
+ def add_acronym_alt_text(html)
452
+ @acronyms.each do |acronym|
453
+ html.gsub!(acronym[0], "<abbr title=\"#{acronym[1].strip}\">#{acronym[0]}</abbr>")
454
+ end
455
+ end
388
456
  end
389
457
  end
390
458
 
@@ -4,8 +4,8 @@
4
4
  %>
5
5
  <div id="contact_<%= contact.content_id %>" class="<%= ['contact', extra_class].flatten.join(' ') %>">
6
6
  <div class="content">
7
- <h3><%= contact.title %></h3>
8
7
  <div class="vcard contact-inner">
8
+ <p><%= contact.title %></p>
9
9
  <% contact.post_addresses.each do |address| %>
10
10
  <%= Govspeak::HCardPresenter.new(address).render %>
11
11
  <% end %>
@@ -17,7 +17,7 @@ class GovspeakAttachmentTest < Minitest::Test
17
17
  }
18
18
 
19
19
  rendered = render_govspeak("[Attachment:attachment.pdf]", [attachment])
20
- assert_match(/<section class="gem-c-attachment">/, rendered)
20
+ assert_match(/<section class="gem-c-attachment/, rendered)
21
21
  assert_match(/Attachment Title/, rendered)
22
22
  end
23
23
 
@@ -32,7 +32,7 @@ class GovspeakAttachmentTest < Minitest::Test
32
32
  assert_equal("<p>some text [Attachment:attachment.pdf]</p>\n", rendered)
33
33
 
34
34
  rendered = render_govspeak("[Attachment:attachment.pdf] some text", [attachment])
35
- assert_match(/<section class="gem-c-attachment">/, rendered)
35
+ assert_match(/<section class="gem-c-attachment/, rendered)
36
36
  assert_match(/<p>some text<\/p>/, rendered)
37
37
  end
38
38
  end
@@ -15,7 +15,7 @@ class GovspeakAttachmentsImageTest < Minitest::Test
15
15
  end
16
16
 
17
17
  def compress_html(html)
18
- html.gsub(/[\n\r]+[\s]*/, "")
18
+ html.gsub(/[\n\r]+\s*/, "")
19
19
  end
20
20
 
21
21
  test "renders an empty string for an image attachment not found" do
@@ -7,34 +7,34 @@ class GovspeakTest < Minitest::Test
7
7
  include GovspeakTestHelper
8
8
 
9
9
  test_given_govspeak "{button start cross-domain-tracking:UA-23066786-5}[Start now](https://www.registertovote.service.gov.uk/register-to-vote/start){/button}" do
10
- assert_html_output '<p><a class="gem-c-button govuk-button govuk-button--start" role="button" data-module="cross-domain-tracking" data-tracking-code="UA-23066786-5" data-tracking-name="govspeakButtonTracker" href="https://www.registertovote.service.gov.uk/register-to-vote/start"> Start now <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewbox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg></a></p>'
10
+ assert_html_selector 'a.gem-c-button.govuk-button--start[data-module="cross-domain-tracking"][data-tracking-code="UA-23066786-5"][href="https://www.registertovote.service.gov.uk/register-to-vote/start"]'
11
11
  assert_text_output "Start now"
12
12
  end
13
13
 
14
14
  # The same as above but with line breaks
15
15
  test_given_govspeak "{button start cross-domain-tracking:UA-23066786-5}\n\n\n[Start now](https://www.registertovote.service.gov.uk/register-to-vote/start)\n\n\n{/button}" do
16
- assert_html_output '<p><a class="gem-c-button govuk-button govuk-button--start" role="button" data-module="cross-domain-tracking" data-tracking-code="UA-23066786-5" data-tracking-name="govspeakButtonTracker" href="https://www.registertovote.service.gov.uk/register-to-vote/start"> Start now <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewbox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg></a></p>'
16
+ assert_html_selector 'a.gem-c-button.govuk-button--start[data-module="cross-domain-tracking"][data-tracking-code="UA-23066786-5"][href="https://www.registertovote.service.gov.uk/register-to-vote/start"]'
17
17
  assert_text_output "Start now"
18
18
  end
19
19
 
20
20
  test_given_govspeak "{button cross-domain-tracking:UA-23066786-5}[Start now](https://www.registertovote.service.gov.uk/register-to-vote/start){/button}" do
21
- assert_html_output '<p><a class="gem-c-button govuk-button" role="button" data-module="cross-domain-tracking" data-tracking-code="UA-23066786-5" data-tracking-name="govspeakButtonTracker" href="https://www.registertovote.service.gov.uk/register-to-vote/start">Start now</a></p>'
21
+ assert_html_selector 'a.gem-c-button:not(.govuk-button--start)[data-module="cross-domain-tracking"][data-tracking-code="UA-23066786-5"][href="https://www.registertovote.service.gov.uk/register-to-vote/start"]'
22
22
  assert_text_output "Start now"
23
23
  end
24
24
 
25
25
  test_given_govspeak "{button start}[Start now](https://www.registertovote.service.gov.uk/register-to-vote/start){/button}" do
26
- assert_html_output '<p><a class="gem-c-button govuk-button govuk-button--start" role="button" href="https://www.registertovote.service.gov.uk/register-to-vote/start"> Start now <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewbox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg></a></p>'
26
+ assert_html_selector 'a.gem-c-button.govuk-button--start[href="https://www.registertovote.service.gov.uk/register-to-vote/start"]'
27
27
  assert_text_output "Start now"
28
28
  end
29
29
 
30
30
  test_given_govspeak "{button}[Start now](https://www.registertovote.service.gov.uk/register-to-vote/start){/button}" do
31
- assert_html_output '<p><a class="gem-c-button govuk-button" role="button" href="https://www.registertovote.service.gov.uk/register-to-vote/start">Start now</a></p>'
31
+ assert_html_selector 'a.gem-c-button:not(.govuk-button--start)[href="https://www.registertovote.service.gov.uk/register-to-vote/start"]'
32
32
  assert_text_output "Start now"
33
33
  end
34
34
 
35
35
  # Test other text outputs
36
36
  test_given_govspeak "{button}[Something else](https://www.registertovote.service.gov.uk/register-to-vote/start){/button}" do
37
- assert_html_output '<p><a class="gem-c-button govuk-button" role="button" href="https://www.registertovote.service.gov.uk/register-to-vote/start">Something else</a></p>'
37
+ assert_html_selector 'a.gem-c-button[href="https://www.registertovote.service.gov.uk/register-to-vote/start"]'
38
38
  assert_text_output "Something else"
39
39
  end
40
40
 
@@ -54,22 +54,6 @@ class GovspeakTest < Minitest::Test
54
54
  assert_text_output "Text before the button with line breaks Start Now test after the button"
55
55
  end
56
56
 
57
- # Test README examples
58
- test_given_govspeak "{button}[Continue](https://gov.uk/random){/button}" do
59
- assert_html_output '<p><a class="gem-c-button govuk-button" role="button" href="https://gov.uk/random">Continue</a></p>'
60
- assert_text_output "Continue"
61
- end
62
-
63
- test_given_govspeak "{button start}[Start Now](https://gov.uk/random){/button}" do
64
- assert_html_output '<p><a class="gem-c-button govuk-button govuk-button--start" role="button" href="https://gov.uk/random"> Start Now <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewbox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg></a></p>'
65
- assert_text_output "Start Now"
66
- end
67
-
68
- test_given_govspeak "{button start cross-domain-tracking:UA-XXXXXX-Y}[Start Now](https://example.com/external-service/start-now){/button}" do
69
- assert_html_output '<p><a class="gem-c-button govuk-button govuk-button--start" role="button" data-module="cross-domain-tracking" data-tracking-code="UA-XXXXXX-Y" data-tracking-name="govspeakButtonTracker" href="https://example.com/external-service/start-now"> Start Now <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewbox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg></a></p>'
70
- assert_text_output "Start Now"
71
- end
72
-
73
57
  # Test indenting button govspeak results in no render, useful in guides
74
58
  test_given_govspeak " {button start cross-domain-tracking:UA-XXXXXX-Y}[Example](https://example.com/external-service/start-now){/button}" do
75
59
  assert_html_output %{
@@ -107,7 +91,7 @@ class GovspeakTest < Minitest::Test
107
91
  lorem lorem lorem
108
92
  lorem lorem lorem
109
93
 
110
- {button start}[Start Now](https://gov.uk/random){/button}
94
+ {button}[Random page](https://gov.uk/random){/button}
111
95
 
112
96
  lorem lorem lorem
113
97
  lorem lorem lorem
@@ -120,7 +104,7 @@ class GovspeakTest < Minitest::Test
120
104
  <p>lorem lorem lorem
121
105
  lorem lorem lorem</p>
122
106
 
123
- <p><a class="gem-c-button govuk-button govuk-button--start" role="button" href="https://gov.uk/random"> Start Now <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewbox="0 0 33 40" role="presentation" focusable="false"><path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path></svg></a></p>
107
+ <p><a class="gem-c-button govuk-button" role="button" href="https://gov.uk/random">Random page</a></p>
124
108
 
125
109
  <p>lorem lorem lorem
126
110
  lorem lorem lorem</p>
@@ -51,7 +51,7 @@ class GovspeakContactsTest < Minitest::Test
51
51
  end
52
52
 
53
53
  def compress_html(html)
54
- html.gsub(/[\n\r]+[\s]*/, "")
54
+ html.gsub(/[\n\r]+\s*/, "")
55
55
  end
56
56
 
57
57
  test "contact is rendered when present in options[:contacts]" do
@@ -62,8 +62,8 @@ class GovspeakContactsTest < Minitest::Test
62
62
  expected_html_output = %(
63
63
  <div id="contact_4f3383e4-48a2-4461-a41d-f85ea8b89ba0" class="contact postal-address">
64
64
  <div class="content">
65
- <h3>Government Digital Service</h3>
66
65
  <div class="vcard contact-inner">
66
+ <p>Government Digital Service</p>
67
67
  <p class="adr">
68
68
  <span class="street-address">125 Kingsway</span><br>
69
69
  <span class="locality">Holborn</span><br>
@@ -106,8 +106,8 @@ class GovspeakContactsTest < Minitest::Test
106
106
  expected_html_output = %(
107
107
  <div id="contact_4f3383e4-48a2-4461-a41d-f85ea8b89ba0" class="contact">
108
108
  <div class="content">
109
- <h3>Government Digital Service</h3>
110
109
  <div class="vcard contact-inner">
110
+ <p>Government Digital Service</p>
111
111
  <div class="email-url-number">
112
112
  <p class="email">
113
113
  <span class="type">Email</span>
@@ -20,13 +20,13 @@ class GovspeakFootnoteTest < Minitest::Test
20
20
  [^3]: And then they both point here." do
21
21
  assert_html_output(
22
22
  %(
23
- <p>Footnotes can be added<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote">[footnote 1]</a></sup>.</p>
23
+ <p>Footnotes can be added<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">[footnote 1]</a></sup>.</p>
24
24
 
25
- <p>Footnotes can be added too<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote">[footnote 2]</a></sup>.</p>
25
+ <p>Footnotes can be added too<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">[footnote 2]</a></sup>.</p>
26
26
 
27
- <p>This footnote has a reference number<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote">[footnote 3]</a></sup>.</p>
27
+ <p>This footnote has a reference number<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">[footnote 3]</a></sup>.</p>
28
28
 
29
- <p>And this footnote has the same reference number<sup id="fnref:3:1" role="doc-noteref"><a href="#fn:3" class="footnote">[footnote 3]</a></sup>.</p>
29
+ <p>And this footnote has the same reference number<sup id="fnref:3:1" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">[footnote 3]</a></sup>.</p>
30
30
 
31
31
  <div class="footnotes" role="doc-endnotes">
32
32
  <ol>
@@ -18,9 +18,7 @@ class GovspeakImagesBangTest < Minitest::Test
18
18
  test "!!n syntax renders an image in options[:images]" do
19
19
  given_govspeak "!!1", images: [Image.new] do
20
20
  assert_html_output(
21
- %(<figure class="image embedded">) +
22
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>) +
23
- %(</figure>),
21
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>",
24
22
  )
25
23
  end
26
24
  end
@@ -28,9 +26,7 @@ class GovspeakImagesBangTest < Minitest::Test
28
26
  test "!!n syntax escapes alt text" do
29
27
  given_govspeak "!!1", images: [Image.new(alt_text: %(my alt '&"<>))] do
30
28
  assert_html_output(
31
- %(<figure class="image embedded">) +
32
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt '&amp;&quot;&lt;&gt;"></div>) +
33
- %(</figure>),
29
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt '&amp;&quot;&lt;&gt;\"></div></figure>",
34
30
  )
35
31
  end
36
32
  end
@@ -48,10 +44,7 @@ class GovspeakImagesBangTest < Minitest::Test
48
44
  test "!!n syntax adds image caption if given" do
49
45
  given_govspeak "!!1", images: [Image.new(caption: "My Caption & so on")] do
50
46
  assert_html_output(
51
- %(<figure class="image embedded">) +
52
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>\n) +
53
- %(<figcaption><p>My Caption &amp; so on</p></figcaption>) +
54
- %(</figure>),
47
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div>\n<figcaption><p>My Caption &amp; so on</p></figcaption></figure>",
55
48
  )
56
49
  end
57
50
  end
@@ -59,9 +52,7 @@ class GovspeakImagesBangTest < Minitest::Test
59
52
  test "!!n syntax ignores a blank caption" do
60
53
  given_govspeak "!!1", images: [Image.new(caption: " ")] do
61
54
  assert_html_output(
62
- %(<figure class="image embedded">) +
63
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>) +
64
- %(</figure>),
55
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>",
65
56
  )
66
57
  end
67
58
  end
@@ -69,10 +60,7 @@ class GovspeakImagesBangTest < Minitest::Test
69
60
  test "¡¡n syntax adds image credit if given" do
70
61
  given_govspeak "!!1", images: [Image.new(credit: "My Credit & so on")] do
71
62
  assert_html_output(
72
- %(<figure class="image embedded">) +
73
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>\n) +
74
- %(<figcaption><p>Image credit: My Credit &amp; so on</p></figcaption>) +
75
- %(</figure>),
63
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div>\n<figcaption><p>Image credit: My Credit &amp; so on</p></figcaption></figure>",
76
64
  )
77
65
  end
78
66
  end
@@ -80,9 +68,7 @@ class GovspeakImagesBangTest < Minitest::Test
80
68
  test "!!n syntax ignores a blank credit" do
81
69
  given_govspeak "!!1", images: [Image.new(credit: " ")] do
82
70
  assert_html_output(
83
- %(<figure class="image embedded">) +
84
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>) +
85
- %(</figure>),
71
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>",
86
72
  )
87
73
  end
88
74
  end
@@ -90,13 +76,7 @@ class GovspeakImagesBangTest < Minitest::Test
90
76
  test "!!n syntax adds image caption and credit if given" do
91
77
  given_govspeak "!!1", images: [Image.new(caption: "My Caption & so on", credit: "My Credit & so on")] do
92
78
  assert_html_output(
93
- %(<figure class="image embedded">) +
94
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>\n) +
95
- %(<figcaption>) +
96
- %(<p>My Caption &amp; so on</p>\n) +
97
- %(<p>Image credit: My Credit &amp; so on</p>) +
98
- %(</figcaption>) +
99
- %(</figure>),
79
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div>\n<figcaption><p>My Caption &amp; so on</p>\n<p>Image credit: My Credit &amp; so on</p></figcaption></figure>",
100
80
  )
101
81
  end
102
82
  end
@@ -14,9 +14,7 @@ class GovspeakImagesTest < Minitest::Test
14
14
  test "Image:image-id syntax renders an image in options[:images]" do
15
15
  given_govspeak "[Image:image-id]", images: [build_image] do
16
16
  assert_html_output(
17
- %(<figure class="image embedded">) +
18
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>) +
19
- %(</figure>),
17
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>",
20
18
  )
21
19
  end
22
20
  end
@@ -24,9 +22,7 @@ class GovspeakImagesTest < Minitest::Test
24
22
  test "Image:image-id syntax escapes alt text" do
25
23
  given_govspeak "[Image:image-id]", images: [build_image(alt_text: %(my alt '&"<>))] do
26
24
  assert_html_output(
27
- %(<figure class="image embedded">) +
28
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt '&amp;&quot;&lt;&gt;"></div>) +
29
- %(</figure>),
25
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt '&amp;&quot;&lt;&gt;\"></div></figure>",
30
26
  )
31
27
  end
32
28
  end
@@ -39,10 +35,7 @@ class GovspeakImagesTest < Minitest::Test
39
35
  test "Image:image-id syntax adds image caption if given" do
40
36
  given_govspeak "[Image:image-id]", images: [build_image(caption: "My Caption & so on")] do
41
37
  assert_html_output(
42
- %(<figure class="image embedded">) +
43
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>\n) +
44
- %(<figcaption><p>My Caption &amp; so on</p></figcaption>) +
45
- %(</figure>),
38
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div>\n<figcaption><p>My Caption &amp; so on</p></figcaption></figure>",
46
39
  )
47
40
  end
48
41
  end
@@ -50,9 +43,7 @@ class GovspeakImagesTest < Minitest::Test
50
43
  test "Image:image-id syntax ignores a blank caption" do
51
44
  given_govspeak "[Image:image-id]", images: [build_image(caption: " ")] do
52
45
  assert_html_output(
53
- %(<figure class="image embedded">) +
54
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>) +
55
- %(</figure>),
46
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>",
56
47
  )
57
48
  end
58
49
  end
@@ -60,10 +51,7 @@ class GovspeakImagesTest < Minitest::Test
60
51
  test "Image:image-id syntax adds image credit if given" do
61
52
  given_govspeak "[Image:image-id]", images: [build_image(credit: "My Credit & so on")] do
62
53
  assert_html_output(
63
- %(<figure class="image embedded">) +
64
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>\n) +
65
- %(<figcaption><p>Image credit: My Credit &amp; so on</p></figcaption>) +
66
- %(</figure>),
54
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div>\n<figcaption><p>Image credit: My Credit &amp; so on</p></figcaption></figure>",
67
55
  )
68
56
  end
69
57
  end
@@ -71,9 +59,7 @@ class GovspeakImagesTest < Minitest::Test
71
59
  test "Image:image-id syntax ignores a blank credit" do
72
60
  given_govspeak "[Image:image-id]", images: [build_image(credit: " ")] do
73
61
  assert_html_output(
74
- %(<figure class="image embedded">) +
75
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>) +
76
- %(</figure>),
62
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>",
77
63
  )
78
64
  end
79
65
  end
@@ -81,13 +67,7 @@ class GovspeakImagesTest < Minitest::Test
81
67
  test "Image:image-id syntax adds image caption and credit if given" do
82
68
  given_govspeak "[Image:image-id]", images: [build_image(caption: "My Caption & so on", credit: "My Credit & so on")] do
83
69
  assert_html_output(
84
- %(<figure class="image embedded">) +
85
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>\n) +
86
- %(<figcaption>) +
87
- %(<p>My Caption &amp; so on</p>\n) +
88
- %(<p>Image credit: My Credit &amp; so on</p>) +
89
- %(</figcaption>) +
90
- %(</figure>),
70
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div>\n<figcaption><p>My Caption &amp; so on</p>\n<p>Image credit: My Credit &amp; so on</p></figcaption></figure>",
91
71
  )
92
72
  end
93
73
  end
@@ -99,18 +79,13 @@ class GovspeakImagesTest < Minitest::Test
99
79
 
100
80
  given_govspeak "[Image:image-id]", images: [build_image] do
101
81
  assert_html_output(
102
- %(<figure class="image embedded">) +
103
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>) +
104
- %(</figure>),
82
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>",
105
83
  )
106
84
  end
107
85
 
108
86
  given_govspeak "[Image:image-id] some text", images: [build_image] do
109
87
  assert_html_output(
110
- %(<figure class="image embedded">) +
111
- %(<div class="img"><img src="http://example.com/image.jpg" alt="my alt"></div>) +
112
- %(</figure>\n) +
113
- %(<p>some text</p>),
88
+ "<figure class=\"image embedded\"><div class=\"img\"><img src=\"http://example.com/image.jpg\" alt=\"my alt\"></div></figure>\n<p>some text</p>",
114
89
  )
115
90
  end
116
91
  end
@@ -30,7 +30,7 @@ class GovspeakTest < Minitest::Test
30
30
 
31
31
  test "stat-headline block extension" do
32
32
  rendered = Govspeak::Document.new("this \n{stat-headline}*13.8bn* Age of the universe in years{/stat-headline}").to_html
33
- assert_equal %(<p>this</p>\n\n<aside class="stat-headline">\n<p><em>13.8bn</em> Age of the universe in years</p>\n</aside>\n), rendered
33
+ assert_equal %(<p>this</p>\n\n<div class="stat-headline">\n<p><em>13.8bn</em> Age of the universe in years</p>\n</div>\n), rendered
34
34
  end
35
35
 
36
36
  test "extracts headers with text, level and generated id" do
@@ -591,6 +591,213 @@ Teston
591
591
  }
592
592
  end
593
593
 
594
+ test_given_govspeak "
595
+ $LegislativeList
596
+ * 1. Item 1[^1]
597
+ * 2. Item 2[^2]
598
+ * 3. Item 3
599
+ $EndLegislativeList
600
+
601
+ [^1]: Footnote definition one
602
+ [^2]: Footnote definition two
603
+ " do
604
+ assert_html_output %(
605
+ <ol class="legislative-list">
606
+ <li>1. Item 1<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">[footnote 1]</a></sup>
607
+ </li>
608
+ <li>2. Item 2<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">[footnote 2]</a></sup>
609
+ </li>
610
+ <li>3. Item 3</li>
611
+ </ol>
612
+
613
+ <div class="footnotes" role="doc-endnotes">
614
+ <ol>
615
+ <li id="fn:1" role="doc-endnote">
616
+ <p>
617
+ Footnote definition one<a href="#fnref:1" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
618
+ </p>
619
+ </li>
620
+ <li id="fn:2" role="doc-endnote">
621
+ <p>
622
+ Footnote definition two<a href="#fnref:2" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
623
+ </p>
624
+ </li>
625
+ </ol>
626
+ </div>
627
+ )
628
+ end
629
+
630
+ test_given_govspeak "
631
+ $LegislativeList
632
+ * 1. Item 1[^1]
633
+ * 2. Item 2
634
+ * 3. Item 3
635
+ $EndLegislativeList
636
+
637
+ This is a paragraph with a footnote[^2].
638
+
639
+ $LegislativeList
640
+ * 1. Item 1
641
+ * 2. Item 2[^3]
642
+ * 3. Item 3
643
+ $EndLegislativeList
644
+
645
+ [^1]: Footnote definition one
646
+ [^2]: Footnote definition two
647
+ [^3]: Footnote definition two
648
+ " do
649
+ assert_html_output %(
650
+ <ol class="legislative-list">
651
+ <li>1. Item 1<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">[footnote 1]</a></sup>
652
+ </li>
653
+ <li>2. Item 2</li>
654
+ <li>3. Item 3</li>
655
+ </ol>
656
+
657
+ <p>This is a paragraph with a footnote<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">[footnote 2]</a></sup>.</p>
658
+
659
+ <ol class="legislative-list">
660
+ <li>1. Item 1</li>
661
+ <li>2. Item 2<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">[footnote 3]</a></sup>
662
+ </li>
663
+ <li>3. Item 3</li>
664
+ </ol>
665
+
666
+ <div class="footnotes" role="doc-endnotes">
667
+ <ol>
668
+ <li id="fn:1" role="doc-endnote">
669
+ <p>
670
+ Footnote definition one<a href="#fnref:1" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
671
+ </p>
672
+ </li>
673
+ <li id="fn:2" role="doc-endnote">
674
+ <p>
675
+ Footnote definition two<a href="#fnref:2" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
676
+ </p>
677
+ </li>
678
+ <li id="fn:3" role="doc-endnote">
679
+ <p>
680
+ Footnote definition two<a href="#fnref:3" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
681
+ </p>
682
+ </li>
683
+ </ol>
684
+ </div>
685
+ )
686
+ end
687
+
688
+ test_given_govspeak "
689
+ $LegislativeList
690
+ * 1. Item 1[^1] with a [link](http://www.gov.uk)
691
+ * 2. Item 2
692
+ * 3. Item 3
693
+ $EndLegislativeList
694
+
695
+ This is a paragraph with a footnote[^2]
696
+
697
+ [^1]: Footnote definition one
698
+ [^2]: Footnote definition two
699
+ " do
700
+ assert_html_output %(
701
+ <ol class="legislative-list">
702
+ <li>1. Item 1<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">[footnote 1]</a></sup> with a <a href="http://www.gov.uk">link</a>
703
+ </li>
704
+ <li>2. Item 2</li>
705
+ <li>3. Item 3</li>
706
+ </ol>
707
+
708
+ <p>This is a paragraph with a footnote<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">[footnote 2]</a></sup></p>
709
+
710
+ <div class="footnotes" role="doc-endnotes">
711
+ <ol>
712
+ <li id="fn:1" role="doc-endnote">
713
+ <p>
714
+ Footnote definition one<a href="#fnref:1" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
715
+ </p>
716
+ </li>
717
+ <li id="fn:2" role="doc-endnote">
718
+ <p>
719
+ Footnote definition two<a href="#fnref:2" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
720
+ </p>
721
+ </li>
722
+ </ol>
723
+ </div>
724
+ )
725
+ end
726
+
727
+ test_given_govspeak "
728
+ $LegislativeList
729
+ * 1. Item 1[^1] with a [link](http://www.gov.uk)
730
+ * 2. Item 2
731
+ * 3. Item 3[^2]
732
+ $EndLegislativeList
733
+
734
+ [^1]: Footnote definition one with a [link](http://www.gov.uk) included
735
+ [^2]: Footnote definition two with an external [link](http://www.google.com)
736
+ " do
737
+ assert_html_output %(
738
+ <ol class="legislative-list">
739
+ <li>1. Item 1<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">[footnote 1]</a></sup> with a <a href="http://www.gov.uk">link</a>
740
+ </li>
741
+ <li>2. Item 2</li>
742
+ <li>3. Item 3<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">[footnote 2]</a></sup>
743
+ </li>
744
+ </ol>
745
+
746
+ <div class="footnotes" role="doc-endnotes">
747
+ <ol>
748
+ <li id="fn:1" role="doc-endnote">
749
+ <p>
750
+ Footnote definition one with a <a href="http://www.gov.uk">link</a> included<a href="#fnref:1" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
751
+ </p>
752
+ </li>
753
+ <li id="fn:2" role="doc-endnote">
754
+ <p>
755
+ Footnote definition two with an external <a rel="external" href="http://www.google.com">link</a><a href="#fnref:2" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
756
+ </p>
757
+ </li>
758
+ </ol>
759
+ </div>
760
+ )
761
+ end
762
+
763
+ test_given_govspeak "
764
+ $LegislativeList
765
+ * 1. Item 1[^1] with an ACRONYM
766
+ * 2. Item 2[^2]
767
+ * 3. Item 3
768
+ $EndLegislativeList
769
+
770
+ [^1]: Footnote definition one
771
+ [^2]: Footnote definition two with an ACRONYM
772
+
773
+ *[ACRONYM]: This is the acronym explanation
774
+ " do
775
+ assert_html_output %(
776
+ <ol class="legislative-list">
777
+ <li>1. Item 1<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">[footnote 1]</a></sup> with an <abbr title="This is the acronym explanation">ACRONYM</abbr>
778
+ </li>
779
+ <li>2. Item 2<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">[footnote 2]</a></sup>
780
+ </li>
781
+ <li>3. Item 3</li>
782
+ </ol>
783
+
784
+ <div class="footnotes" role="doc-endnotes">
785
+ <ol>
786
+ <li id="fn:1" role="doc-endnote">
787
+ <p>
788
+ Footnote definition one<a href="#fnref:1" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
789
+ </p>
790
+ </li>
791
+ <li id="fn:2" role="doc-endnote">
792
+ <p>
793
+ Footnote definition two with an <abbr title="This is the acronym explanation">ACRONYM</abbr><a href="#fnref:2" class="reversefootnote" role="doc-backlink" aria-label="go to where this is referenced">↩</a>
794
+ </p>
795
+ </li>
796
+ </ol>
797
+ </div>
798
+ )
799
+ end
800
+
594
801
  test_given_govspeak "
595
802
  The quick brown
596
803
  $LegislativeList
@@ -666,6 +873,11 @@ Teston
666
873
  assert_equal "<script>doGoodThings();</script>", document.to_html.strip
667
874
  end
668
875
 
876
+ test "it can exclude stipulated elements from sanitization" do
877
+ document = Govspeak::Document.new("<uncommon-element>some content</uncommon-element>", allowed_elements: %w[uncommon-element])
878
+ assert_equal "<uncommon-element>some content</uncommon-element>", document.to_html.strip
879
+ end
880
+
669
881
  test "identifies a Govspeak document containing malicious HTML as invalid" do
670
882
  document = Govspeak::Document.new("<script>doBadThings();</script>")
671
883
  refute document.valid?
@@ -26,6 +26,12 @@ module GovspeakTestHelper
26
26
  @testcase.assert expected.strip == actual, describe_error(@govspeak, expected.strip, actual)
27
27
  end
28
28
 
29
+ def assert_html_selector(selector)
30
+ html = document.to_html
31
+ fragment = Nokogiri::HTML.fragment(html)
32
+ @testcase.assert fragment.css(selector).any?, "Expected to find #{selector} within #{html}"
33
+ end
34
+
29
35
  def remove_indentation(raw)
30
36
  lines = raw.split("\n")
31
37
  if lines.first.empty?
@@ -33,7 +39,7 @@ module GovspeakTestHelper
33
39
  nonblanks = lines.reject { |l| l.match(/^ *$/) }
34
40
  indentation = nonblanks.map { |line| line.match(/^ */)[0].size }.min
35
41
  unindented = lines.map do |line|
36
- line[indentation..-1]
42
+ line[indentation..]
37
43
  end
38
44
  unindented.join "\n"
39
45
  else
@@ -96,4 +96,10 @@ class HtmlSanitizerTest < Minitest::Test
96
96
  assert_equal "<table><thead><tr><th>thing</th></tr></thead><tbody><tr><td>thing</td></tr></tbody></table>", Govspeak::HtmlSanitizer.new(html).sanitize
97
97
  end
98
98
  end
99
+
100
+ test "excludes specified elements from sanitization" do
101
+ html = "<custom-allowed-element><p>text</p></custom-allowed-element>"
102
+ assert_equal "<p>text</p>", Govspeak::HtmlSanitizer.new(html).sanitize
103
+ assert_equal html, Govspeak::HtmlSanitizer.new(html).sanitize(allowed_elements: %w[custom-allowed-element])
104
+ end
99
105
  end
data/test/test_helper.rb CHANGED
@@ -17,10 +17,10 @@ class Minitest::Test
17
17
  clean_name = name.gsub(/\s+/, "_")
18
18
  method = "test_#{clean_name.gsub(/\s+/, '_')}".to_sym
19
19
  already_defined = begin
20
- instance_method(method)
21
- rescue StandardError
22
- false
23
- end
20
+ instance_method(method)
21
+ rescue StandardError
22
+ false
23
+ end
24
24
  raise "#{method} exists" if already_defined
25
25
 
26
26
  define_method(method, &block)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: govspeak
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.5.11
4
+ version: 6.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - GOV.UK Dev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-30 00:00:00.000000000 Z
11
+ date: 2021-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: actionview
@@ -216,14 +216,14 @@ dependencies:
216
216
  requirements:
217
217
  - - "~>"
218
218
  - !ruby/object:Gem::Version
219
- version: 3.17.1
219
+ version: 4.0.0
220
220
  type: :development
221
221
  prerelease: false
222
222
  version_requirements: !ruby/object:Gem::Requirement
223
223
  requirements:
224
224
  - - "~>"
225
225
  - !ruby/object:Gem::Version
226
- version: 3.17.1
226
+ version: 4.0.0
227
227
  - !ruby/object:Gem::Dependency
228
228
  name: simplecov
229
229
  requirement: !ruby/object:Gem::Requirement
@@ -360,36 +360,36 @@ required_ruby_version: !ruby/object:Gem::Requirement
360
360
  requirements:
361
361
  - - ">="
362
362
  - !ruby/object:Gem::Version
363
- version: '0'
363
+ version: '2.6'
364
364
  required_rubygems_version: !ruby/object:Gem::Requirement
365
365
  requirements:
366
366
  - - ">="
367
367
  - !ruby/object:Gem::Version
368
368
  version: '0'
369
369
  requirements: []
370
- rubygems_version: 3.1.4
370
+ rubygems_version: 3.0.3
371
371
  signing_key:
372
372
  specification_version: 4
373
373
  summary: Markup language for single domain
374
374
  test_files:
375
- - test/govspeak_images_bang_test.rb
376
- - test/govspeak_attachment_test.rb
377
- - test/govspeak_link_extractor_test.rb
378
- - test/govspeak_attachments_inline_test.rb
379
- - test/govspeak_button_test.rb
380
- - test/govspeak_structured_headers_test.rb
381
375
  - test/govspeak_attachments_image_test.rb
382
- - test/govspeak_attachment_link_test.rb
376
+ - test/govspeak_structured_headers_test.rb
377
+ - test/govspeak_images_bang_test.rb
378
+ - test/govspeak_link_test.rb
383
379
  - test/govspeak_extract_contact_content_ids_test.rb
384
- - test/presenters/h_card_presenter_test.rb
385
- - test/govspeak_test.rb
386
- - test/html_sanitizer_test.rb
387
- - test/govspeak_footnote_test.rb
380
+ - test/govspeak_test_helper.rb
388
381
  - test/blockquote_extra_quote_remover_test.rb
382
+ - test/govspeak_attachments_inline_test.rb
383
+ - test/govspeak_attachment_test.rb
389
384
  - test/test_helper.rb
390
- - test/govspeak_table_with_headers_test.rb
385
+ - test/govspeak_test.rb
386
+ - test/presenters/h_card_presenter_test.rb
387
+ - test/govspeak_footnote_test.rb
388
+ - test/govspeak_attachment_link_test.rb
389
+ - test/govspeak_link_extractor_test.rb
390
+ - test/govspeak_button_test.rb
391
391
  - test/govspeak_images_test.rb
392
- - test/govspeak_link_test.rb
393
392
  - test/html_validator_test.rb
393
+ - test/html_sanitizer_test.rb
394
+ - test/govspeak_table_with_headers_test.rb
394
395
  - test/govspeak_contacts_test.rb
395
- - test/govspeak_test_helper.rb