diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 679ed399a..7dd14512c 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -2,5 +2,6 @@
//= require govuk_publishing_components/dependencies
//= require govuk_publishing_components/lib
+//= require govuk_publishing_components/components/add-another
//= require govuk_publishing_components/components/govspeak
//= require govuk_publishing_components/components/table
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index b231a426e..63c94f08f 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -4,6 +4,7 @@ $govuk-page-width: 1140px;
@import 'govuk_publishing_components/govuk_frontend_support';
@import 'govuk_publishing_components/component_support';
@import 'govuk_publishing_components/components/button';
+@import 'govuk_publishing_components/components/add-another';
@import 'govuk_publishing_components/components/checkboxes';
@import 'govuk_publishing_components/components/date-input';
@import 'govuk_publishing_components/components/details';
diff --git a/app/controllers/editions_controller.rb b/app/controllers/editions_controller.rb
index e79487457..50a207e22 100644
--- a/app/controllers/editions_controller.rb
+++ b/app/controllers/editions_controller.rb
@@ -79,10 +79,28 @@ def history
render action: "show"
end
- def linking
+ def related_external_links
render action: "show"
end
+ def update_related_external_links
+ artefact = resource.artefact
+
+ if params.key?("artefact")
+ artefact.assign_attributes(permitted_external_links_params)
+
+ if artefact.save
+ flash[:success] = "Related links updated."
+ else
+ flash[:danger] = artefact.errors.full_messages.join("\n")
+ end
+ else
+ flash[:danger] = "There aren't any external related links yet"
+ end
+
+ redirect_to related_external_links_edition_path(@resource.id)
+ end
+
def confirm_unpublish
if redirect_url.blank? || validate_redirect(redirect_url)
render "secondary_nav_tabs/confirm_unpublish"
@@ -217,6 +235,10 @@ def permitted_update_params
params.require(:edition).permit(%i[title overview in_beta body major_change change_note])
end
+ def permitted_external_links_params
+ params.require(:artefact).permit(external_links_attributes: %i[title url id _destroy])
+ end
+
def require_destroyable
return if @resource.can_destroy?
diff --git a/app/helpers/tabbed_nav_helper.rb b/app/helpers/tabbed_nav_helper.rb
index a02a3d135..646173d69 100644
--- a/app/helpers/tabbed_nav_helper.rb
+++ b/app/helpers/tabbed_nav_helper.rb
@@ -23,6 +23,8 @@ def current_tab_name
"unpublish"
when "admin"
"admin"
+ when "related_external_links"
+ "related_external_links"
else
"temp_nav_text"
end
diff --git a/app/views/editions/secondary_nav_tabs/_add-another_checkbox.html.erb b/app/views/editions/secondary_nav_tabs/_add-another_checkbox.html.erb
new file mode 100644
index 000000000..d3af62e8b
--- /dev/null
+++ b/app/views/editions/secondary_nav_tabs/_add-another_checkbox.html.erb
@@ -0,0 +1,5 @@
+<%= render "govuk_publishing_components/components/checkboxes", {
+ name: "artefact[external_links_attributes][#{index}][_destroy]",
+ id: "artefact_external_links_attributes_#{index}__destroy",
+ items: [{label: "Delete", value: "1" }],
+} %>
diff --git a/app/views/editions/secondary_nav_tabs/_add-another_fieldset.html.erb b/app/views/editions/secondary_nav_tabs/_add-another_fieldset.html.erb
new file mode 100644
index 000000000..b4f51bcd2
--- /dev/null
+++ b/app/views/editions/secondary_nav_tabs/_add-another_fieldset.html.erb
@@ -0,0 +1,24 @@
+<% if id %>
+ <%= hidden_field_tag "artefact[external_links_attributes][#{index}][id]", id %>
+<% end %>
+
+<%= render "govuk_publishing_components/components/input", {
+ label: {
+ text: "Title",
+ bold: true,
+ },
+ name: "artefact[external_links_attributes][#{index}][title]",
+ id: "artefact_external_links_attributes_#{index}_title",
+ value: @edition.artefact.external_links[index].present? ? @edition.artefact.external_links[index].title : "",
+} %>
+
+<%= render "govuk_publishing_components/components/input", {
+ label: {
+ text: "URL",
+ bold: true,
+ },
+ name: "artefact[external_links_attributes][#{index}][url]",
+ id: "artefact_external_links_attributes_#{index}_url",
+ value: @edition.artefact.external_links[index].present? ? @edition.artefact.external_links[index].url : "",
+ hint: "Example: https://gov.uk",
+} %>
diff --git a/app/views/editions/secondary_nav_tabs/_related_external_links.html.erb b/app/views/editions/secondary_nav_tabs/_related_external_links.html.erb
new file mode 100644
index 000000000..a88b452d6
--- /dev/null
+++ b/app/views/editions/secondary_nav_tabs/_related_external_links.html.erb
@@ -0,0 +1,40 @@
+
+
+ <%= header_for("Related external links") %>
+
+ <%= form_for @edition.artefact, url: update_related_external_links_edition_path(@resource) do %>
+ <%
+ if @edition.artefact.external_links.count == 0
+ items = [{
+ fields: render(partial: "secondary_nav_tabs/add-another_fieldset", locals: { index: 0, id: nil }),
+ destroy_checkbox: render(partial: "secondary_nav_tabs/add-another_checkbox", locals: {index: 0}),
+ }]
+ empty = render(partial: "secondary_nav_tabs/add-another_fieldset", locals: { index: 1, id: nil })
+ else
+ items = @edition.artefact.external_links.each_with_index.map do | external_link, index |
+ {
+ fields: render(partial: "secondary_nav_tabs/add-another_fieldset", locals: { index:, id: external_link.id }),
+ destroy_checkbox: render(partial: "secondary_nav_tabs/add-another_checkbox", locals: {index: index}),
+ }
+ end
+ empty = render(partial: "secondary_nav_tabs/add-another_fieldset", locals: { index: @edition.artefact.external_links.count, id: nil })
+ end
+ %>
+
+ <%= render "govuk_publishing_components/components/add_another", {
+ fieldset_legend: "Link",
+ add_button_text: "Add another link",
+ items: items,
+ empty: empty,
+ } %>
+
+ <%= render "govuk_publishing_components/components/inset_text", {
+ text: "After saving, changes to related external links will be visible on the site the next time this publication is published.",
+ } %>
+
+ <%= render "govuk_publishing_components/components/button", {
+ text: "Save",
+ } %>
+ <% end %>
+
+
diff --git a/config/routes.rb b/config/routes.rb
index d667e58d2..9a32c32a9 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -23,7 +23,8 @@
get "history"
get "admin"
post "duplicate"
- get "related_external_links", to: "editions#linking"
+ get "related_external_links"
+ patch "update_related_external_links"
get "tagging", to: "editions#linking"
get "unpublish"
get "unpublish/confirm-unpublish", to: "editions#confirm_unpublish", as: "confirm_unpublish"
diff --git a/test/functional/editions_controller_test.rb b/test/functional/editions_controller_test.rb
index a447ef2b5..4c63a2bd1 100644
--- a/test/functional/editions_controller_test.rb
+++ b/test/functional/editions_controller_test.rb
@@ -46,7 +46,7 @@ class EditionsControllerTest < ActionController::TestCase
end
context "when 'restrict_access_by_org' feature toggle is disabled" do
- %i[show metadata history admin linking unpublish].each do |action|
+ %i[show metadata history admin related_external_links unpublish].each do |action|
context "##{action}" do
setup do
@edition = FactoryBot.create(:edition, owning_org_content_ids: %w[org-two])
@@ -74,7 +74,7 @@ class EditionsControllerTest < ActionController::TestCase
test_strategy.switch!(:restrict_access_by_org, false)
end
- %i[show metadata history admin linking unpublish].each do |action|
+ %i[show metadata history admin related_external_links unpublish].each do |action|
context "##{action}" do
setup do
@edition = FactoryBot.create(:edition, owning_org_content_ids: %w[org-two])
@@ -625,6 +625,62 @@ class EditionsControllerTest < ActionController::TestCase
end
end
+ context "#update_related_external_links" do
+ should "display an error message when the title is blank" do
+ patch :update_related_external_links, params: {
+ id: @edition.id,
+ artefact: {
+ external_links_attributes: [{ title: "", url: "http://foo-bar.com", _destroy: false }],
+ },
+ }
+
+ assert_equal "External links is invalid", flash[:danger]
+ end
+
+ should "display an error message when the url is blank" do
+ patch :update_related_external_links, params: {
+ id: @edition.id,
+ artefact: {
+ external_links_attributes: [{ title: "foo", url: "", _destroy: false }],
+ },
+ }
+
+ assert_equal "External links is invalid", flash[:danger]
+ end
+
+ should "display an error message when the url is invalid" do
+ patch :update_related_external_links, params: {
+ id: @edition.id,
+ artefact: {
+ external_links_attributes: [{ title: "foo", url: "an-invalid-url", _destroy: false }],
+ },
+ }
+
+ assert_equal "External links is invalid", flash[:danger]
+ end
+
+ should "update related external links and display a success message when successfully saved" do
+ patch :update_related_external_links, params: {
+ id: @edition.id,
+ artefact: {
+ external_links_attributes: [{ title: "foo", url: "https://foo-bar.com", _destroy: false }],
+ },
+ }
+
+ assert_equal "Related links updated.", flash[:success]
+ assert_equal "foo", @edition.artefact.external_links[0].title
+ assert_equal "https://foo-bar.com", @edition.artefact.external_links[0].url
+ end
+
+ should "display an error message when there are no external links to save" do
+ patch :update_related_external_links, params: {
+ id: @edition.id,
+ }
+
+ assert_equal "There aren't any external related links yet", flash[:danger]
+ end
+ end
+
private
def description(edition)
diff --git a/test/integration/edition_edit_test.rb b/test/integration/edition_edit_test.rb
index af17f96bb..bff6c3386 100644
--- a/test/integration/edition_edit_test.rb
+++ b/test/integration/edition_edit_test.rb
@@ -585,6 +585,130 @@ class EditionEditTest < IntegrationTest
end
end
+ context "Related external links tab" do
+ setup do
+ visit_draft_edition
+ click_link "Related external links"
+ end
+
+ should "render 'Related external links' header, inset text and save button" do
+ assert page.has_css?("h2", text: "Related external links")
+ assert page.has_css?("div.gem-c-inset-text", text: "After saving, changes to related external links will be visible on the site the next time this publication is published.")
+ assert page.has_css?("button.gem-c-button", text: "Save")
+ end
+
+ context "Document has no external links when page loads" do
+ setup do
+ visit_draft_edition
+ click_link "Related external links"
+ end
+
+ should "render an empty 'Add another' form" do
+ # Link 1
+ assert page.has_css?("legend", text: "Link 1")
+ assert page.has_css?("input[name='artefact[external_links_attributes][0][_destroy]']")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_0_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_0_url']").text
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][0][title]']").value
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][0][url]']").value
+
+ # Link 2 (empty fields)
+ assert page.has_css?("legend", text: "Link 2")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_1_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_1_url']").text
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][1][title]']").value
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][1][url]']").value
+ end
+ end
+
+ context "Document already has external links when page loads" do
+ setup do
+ visit_draft_edition
+ @draft_edition.artefact.external_links = [{ title: "Link One", url: "https://gov.uk" }]
+ click_link "Related external links"
+ end
+
+ should "render a pre-populated 'Add another' form" do
+ # Link 1
+ assert page.has_css?("legend", text: "Link 1")
+ assert page.has_css?("input[name='artefact[external_links_attributes][0][_destroy]']")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_0_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_0_url']").text
+ assert_equal "Link One", page.find("input[name='artefact[external_links_attributes][0][title]']").value
+ assert_equal "https://gov.uk", page.find("input[name='artefact[external_links_attributes][0][url]']").value
+
+ # Link 2 (empty fields)
+ assert page.has_css?("legend", text: "Link 2")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_1_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_1_url']").text
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][1][title]']").value
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][1][url]']").value
+ end
+ end
+
+ context "User adds a new external link and saves" do
+ setup do
+ visit_draft_edition
+ click_link "Related external links"
+ end
+
+ should "render a prepopulated 'Add another' form" do
+ within :css, ".gem-c-add-another .js-add-another__fieldset:first-of-type" do
+ fill_in "Title", with: "A new external link"
+ fill_in "URL", with: "https://foo.com"
+ end
+
+ click_button("Save")
+
+ # Link 1
+ assert page.has_css?("legend", text: "Link 1")
+ assert page.has_css?("input[name='artefact[external_links_attributes][0][_destroy]']")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_0_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_0_url']").text
+ assert_equal "A new external link", page.find("input[name='artefact[external_links_attributes][0][title]']").value
+ assert_equal "https://foo.com", page.find("input[name='artefact[external_links_attributes][0][url]']").value
+
+ # Link 2 (empty fields)
+ assert page.has_css?("legend", text: "Link 2")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_1_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_1_url']").text
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][1][title]']").value
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][1][url]']").value
+ end
+ end
+
+ context "User deletes an external link and saves" do
+ setup do
+ visit_draft_edition
+ @draft_edition.artefact.external_links = [{ title: "Link One", url: "https://gov.uk" }]
+ click_link "Related external links"
+ end
+
+ should "render an empty 'Add another' form" do
+ within :css, ".gem-c-add-another .js-add-another__fieldset:first-of-type" do
+ check("Delete")
+ end
+
+ click_button("Save")
+
+ # Link 1
+ assert page.has_css?("legend", text: "Link 1")
+ assert page.has_css?("input[name='artefact[external_links_attributes][0][_destroy]']")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_0_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_0_url']").text
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][0][title]']").value
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][0][url]']").value
+
+ # Link 2 (empty fields)
+ assert page.has_css?("legend", text: "Link 2")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_1_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_1_url']").text
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][1][title]']").value
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][1][url]']").value
+ end
+ end
+ end
+
private
def visit_draft_edition
diff --git a/test/integration/edition_external_links_test.rb b/test/integration/edition_external_links_test.rb
new file mode 100644
index 000000000..2b467cec7
--- /dev/null
+++ b/test/integration/edition_external_links_test.rb
@@ -0,0 +1,96 @@
+require "integration_test_helper"
+
+class EditionExternalLinksTest < JavascriptIntegrationTest
+ setup do
+ @govuk_editor = FactoryBot.create(:user, :govuk_editor, name: "Stub User")
+ login_as(@govuk_editor)
+ test_strategy = Flipflop::FeatureSet.current.test!
+ test_strategy.switch!(:design_system_edit, true)
+ end
+
+ context "Related external links tab" do
+ setup do
+ visit_draft_edition
+ click_link "Related external links"
+ end
+
+ should "render 'Related external links' header, inset text and save button" do
+ assert page.has_css?("h2", text: "Related external links")
+ assert page.has_css?("div.gem-c-inset-text", text: "After saving, changes to related external links will be visible on the site the next time this publication is published.")
+ assert page.has_css?("button.gem-c-button", text: "Save")
+ end
+
+ should "render an empty 'Add another' form when the page loads" do
+ assert page.has_css?("legend", text: "Link 1")
+ assert page.has_no_css?("input[name='artefact[external_links_attributes][0][_destroy]']")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_0_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_0_url']").text
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][0][title]']").value
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][0][url]']").value
+ assert page.has_css?("button", text: "Add another link")
+ end
+
+ should "render a pre-populated 'Add another' form when the user adds values to the form and saves" do
+ fill_in "Title", with: "Link one"
+ fill_in "URL", with: "https://one.com"
+ click_button("Save")
+
+ assert page.has_css?("legend", text: "Link 1")
+ assert page.has_no_css?("input[name='artefact[external_links_attributes][0][_destroy]']")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_0_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_0_url']").text
+ assert_equal "Link one", page.find("input[name='artefact[external_links_attributes][0][title]']").value
+ assert_equal "https://one.com", page.find("input[name='artefact[external_links_attributes][0][url]']").value
+ assert page.has_css?("button", text: "Add another link")
+ end
+
+ should "display 'Delete' buttons and a second set of inputs when 'Add another link' is clicked" do
+ click_button("Add another link")
+
+ assert page.has_css?("legend", text: "Link 1")
+ assert page.has_no_css?("input[name='artefact[external_links_attributes][0][_destroy]']")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_0_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_0_url']").text
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][0][title]']").value
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][0][url]']").value
+ assert page.has_css?("legend", text: "Link 2")
+ assert page.has_no_css?("input[name='artefact[external_links_attributes][1][_destroy]']")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_1_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_1_url']").text
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][1][title]']").value
+ assert_equal "", page.find("input[name='artefact[external_links_attributes][1][url]']").value
+ assert page.has_css?("button", text: "Add another link")
+ within :css, ".gem-c-add-another .js-add-another__fieldset:nth-of-type(1)" do
+ assert page.has_css?("button", text: "Delete")
+ end
+ within :css, ".gem-c-add-another .js-add-another__fieldset:nth-of-type(2)" do
+ assert page.has_css?("button", text: "Delete")
+ end
+ end
+
+ should "delete the first set of fields when the user clicks the first “Delete” button" do
+ click_button("Add another link")
+
+ within :css, ".gem-c-add-another .js-add-another__fieldset:nth-of-type(1)" do
+ click_button("Delete")
+ end
+
+ assert page.has_css?("legend", text: "Link 1")
+ assert page.has_no_css?("label[for='artefact_external_links_attributes_0_title']")
+ assert page.has_no_css?("label[for='artefact_external_links_attributes_0_url']")
+ assert_equal "Title", page.find("label[for='artefact_external_links_attributes_1_title']").text
+ assert_equal "URL", page.find("label[for='artefact_external_links_attributes_1_url']").text
+ assert page.has_css?("button", text: "Add another link")
+ within :css, ".gem-c-add-another .js-add-another__fieldset:nth-of-type(2)" do
+ assert page.has_css?("button", text: "Delete")
+ end
+ end
+ end
+
+private
+
+ def visit_draft_edition
+ @draft_edition = FactoryBot.create(:edition, title: "Edit page title", state: "draft", overview: "metatags", in_beta: 1, body: "The body")
+ visit edition_path(@draft_edition)
+ end
+end