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