From 0ac4a7864831bf972d9afcbd0c21b234a4acc74b Mon Sep 17 00:00:00 2001 From: pezholio Date: Mon, 4 Nov 2024 10:30:44 +0000 Subject: [PATCH 1/3] Add engines to the assets load path This will make it easier to reference our CSS and JS --- app/assets/stylesheets/application.scss | 2 +- config/initializers/assets.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 93859945366..068bce7c472 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -49,7 +49,7 @@ $govuk-page-width: 1140px; @import "./admin/views/whats-new"; @import "./admin/views/worldwide-organisations-choose-main-office"; -@import "../../../lib/engines/content_block_manager/app/assets/stylesheets/content_block_manager/application"; +@import "./content_block_manager/application"; .app-js-only { display: none; diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 493e0c0987c..e0242f22572 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -5,3 +5,8 @@ # Add Yarn node_modules folder to the asset load path. Rails.application.config.assets.paths << Rails.root.join("node_modules") + +# Add engines to the assets load path +Dir.glob(Rails.root.join("lib/engines/**/assets/*")).each do |path| + Rails.application.config.assets.paths << path +end From afe1120a583c0051e2d36dd401cd66df21391702 Mon Sep 17 00:00:00 2001 From: pezholio Date: Tue, 5 Nov 2024 10:40:24 +0000 Subject: [PATCH 2/3] Add JS to copy embed code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This finds any row in a summary card/list component with a `data-module=“copy-embed-code”` and adds a `dd` element with a link to copy the code. If clicked, this gets the value of summary list row and copies it to the clipboard. The modern way of copying to the clipboard (via `navigator.clipboard.writeText`) isn’t supported in all browsers and causes issues with testing, so we’re doing it the “old-fashioned way” by creatinf an invisible textarea, populating it with the text, then selecting and coping it it via `execCommand`. I’ve also added some tweaks to the Jasmine setup, so we can keep our Jasmine specs within the engine. --- app/assets/javascripts/application.js | 2 + .../content_block_manager/application.js | 1 + .../modules/copy-embed-code.js | 81 +++++++++++++++++++ .../content_block_manager/copy-code.spec.js | 72 +++++++++++++++++ spec/support/jasmine-browser.json | 5 +- 5 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 lib/engines/content_block_manager/app/assets/javascripts/content_block_manager/application.js create mode 100644 lib/engines/content_block_manager/app/assets/javascripts/content_block_manager/modules/copy-embed-code.js create mode 100644 lib/engines/content_block_manager/spec/content_block_manager/copy-code.spec.js diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 4b97b68b300..b82168bdd78 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -34,6 +34,8 @@ //= require admin/views/organisation-form //= require admin/views/unpublish-display-conditions +//= require content_block_manager/application + 'use strict' window.GOVUK.approveAllCookieTypes() window.GOVUK.cookie('cookies_preferences_set', 'true', { days: 365 }) diff --git a/lib/engines/content_block_manager/app/assets/javascripts/content_block_manager/application.js b/lib/engines/content_block_manager/app/assets/javascripts/content_block_manager/application.js new file mode 100644 index 00000000000..d5222b5e015 --- /dev/null +++ b/lib/engines/content_block_manager/app/assets/javascripts/content_block_manager/application.js @@ -0,0 +1 @@ +//= require ./modules/copy-embed-code diff --git a/lib/engines/content_block_manager/app/assets/javascripts/content_block_manager/modules/copy-embed-code.js b/lib/engines/content_block_manager/app/assets/javascripts/content_block_manager/modules/copy-embed-code.js new file mode 100644 index 00000000000..bcd9bde1d26 --- /dev/null +++ b/lib/engines/content_block_manager/app/assets/javascripts/content_block_manager/modules/copy-embed-code.js @@ -0,0 +1,81 @@ +'use strict' +window.GOVUK = window.GOVUK || {} +window.GOVUK.Modules = window.GOVUK.Modules || {} +;(function (Modules) { + function CopyEmbedCode(module) { + this.module = module + this.copyLink = this.createLink.bind(this)() + } + + CopyEmbedCode.prototype.init = function () { + const dd = document.createElement('dd') + dd.classList.add('govuk-summary-list__actions') + dd.append(this.copyLink) + + this.module.append(dd) + } + + CopyEmbedCode.prototype.createLink = function () { + const copyLink = document.createElement('a') + copyLink.classList.add('govuk-link') + copyLink.classList.add('govuk-link__copy-link') + copyLink.setAttribute('href', '#') + copyLink.setAttribute('role', 'button') + copyLink.textContent = 'Copy code' + copyLink.addEventListener('click', this.copyCode.bind(this)) + // Handle when a keyboard user highlights the link and clicks return + copyLink.addEventListener( + 'keydown', + function (e) { + if (e.keyCode === 13) { + this.copyCode.bind(this) + } + }.bind(this) + ) + + return copyLink + } + + CopyEmbedCode.prototype.copyCode = function (e) { + e.preventDefault() + + const embedCode = this.module.dataset.embedCode + this.writeToClipboard(embedCode).then(this.copySuccess.bind(this)) + } + + CopyEmbedCode.prototype.copySuccess = function () { + const originalText = this.copyLink.textContent + this.copyLink.textContent = 'Code copied' + this.copyLink.focus() + + setTimeout(this.restoreText.bind(this, originalText), 2000) + } + + CopyEmbedCode.prototype.restoreText = function (originalText) { + this.copyLink.textContent = originalText + } + + // This is a fallback for browsers that do not support the async clipboard API + CopyEmbedCode.prototype.writeToClipboard = function (embedCode) { + return new Promise(function (resolve) { + // Create a textarea element with the embed code + const textArea = document.createElement('textarea') + textArea.value = embedCode + + document.body.appendChild(textArea) + + // Select the text in the textarea + textArea.select() + + // Copy the selected text + document.execCommand('copy') + + // Remove our textarea + document.body.removeChild(textArea) + + resolve() + }) + } + + Modules.CopyEmbedCode = CopyEmbedCode +})(window.GOVUK.Modules) diff --git a/lib/engines/content_block_manager/spec/content_block_manager/copy-code.spec.js b/lib/engines/content_block_manager/spec/content_block_manager/copy-code.spec.js new file mode 100644 index 00000000000..a09c27a8df9 --- /dev/null +++ b/lib/engines/content_block_manager/spec/content_block_manager/copy-code.spec.js @@ -0,0 +1,72 @@ +describe('GOVUK.Modules.CopyEmbedCode', function () { + let fixture, embedCode, copyEmbedCode, copyLink, fakeTextarea + + beforeEach(function () { + embedCode = 'something' + fixture = document.createElement('div') + fixture.setAttribute('data-embed-code', embedCode) + fixture.innerHTML = ` +
Embed code
+
${embedCode}
+ ` + document.body.append(fixture) + + copyEmbedCode = new GOVUK.Modules.CopyEmbedCode(fixture) + copyEmbedCode.init() + + copyLink = document.querySelector('.govuk-link__copy-link') + + fakeTextarea = document.createElement('textarea') + spyOn(document, 'createElement').and.returnValue(fakeTextarea) + }) + + afterEach(function () { + fixture.innerHTML = '' + }) + + it('should add a link to copy the embed code', function () { + expect(copyLink).toBeTruthy() + expect(copyLink.textContent).toBe('Copy code') + }) + + it('should create and populate a textarea', function () { + window.GOVUK.triggerEvent(copyLink, 'click') + + expect(fakeTextarea.value).toEqual(embedCode) + }) + + it('should select the text in the textarea and run the copy command', function () { + const copySpy = spyOn(document, 'execCommand') + const selectSpy = spyOn(fakeTextarea, 'select') + + window.GOVUK.triggerEvent(copyLink, 'click') + + expect(selectSpy).toHaveBeenCalled() + expect(copySpy).toHaveBeenCalled() + }) + + it('should add and remove the textarea', function () { + const appendSpy = spyOn(document.body, 'appendChild') + const removeSpy = spyOn(document.body, 'removeChild') + + window.GOVUK.triggerEvent(copyLink, 'click') + + expect(appendSpy).toHaveBeenCalled() + expect(removeSpy).toHaveBeenCalled() + }) + + it('changes and restores the link text', async function () { + jasmine.clock().install() + + await window.GOVUK.triggerEvent(copyLink, 'click') + + copyLink = document.querySelector('.govuk-link__copy-link') + + expect(copyLink.textContent).toEqual('Code copied') + jasmine.clock().tick(2000) + + expect(copyLink.textContent).toEqual('Copy code') + + jasmine.clock().uninstall() + }) +}) diff --git a/spec/support/jasmine-browser.json b/spec/support/jasmine-browser.json index f337113dcde..d91b8e20dbe 100644 --- a/spec/support/jasmine-browser.json +++ b/spec/support/jasmine-browser.json @@ -4,9 +4,10 @@ "application-*.js", "all-*.js" ], - "specDir": "spec/javascripts", + "specDir": ".", "specFiles": [ - "**/*[sS]pec.js" + "spec/javascripts/**/*[sS]pec.js", + "lib/engines/**/spec/**/*[sS]pec.js" ], "helpers": [ "https://unpkg.com/jasmine-ajax@4.0.0/lib/mock-ajax.js", From 50a32f9dbba35ebdb7b3e8b3a7d899dc8be641eb Mon Sep 17 00:00:00 2001 From: pezholio Date: Tue, 5 Nov 2024 08:34:07 +0000 Subject: [PATCH 3/3] Add data attributes to summary cards Also adds cucumber tests --- .../index/summary_card_component.html.erb | 2 +- .../document/index/summary_card_component.rb | 1 + .../document/show/summary_list_component.rb | 4 ++++ .../features/search_for_object.feature | 23 +++++++++++++++---- .../content_block_manager_steps.rb | 21 +++++++++++++++++ .../features/view_object.feature | 8 +++++++ .../index/summary_card_component_test.rb | 2 ++ .../show/summary_list_component_test.rb | 2 ++ 8 files changed, 58 insertions(+), 5 deletions(-) diff --git a/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/index/summary_card_component.html.erb b/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/index/summary_card_component.html.erb index e9043bf0229..ac8f8efc41f 100644 --- a/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/index/summary_card_component.html.erb +++ b/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/index/summary_card_component.html.erb @@ -1,4 +1,4 @@ -<%= render "components/summary_card", { +<%= render "govuk_publishing_components/components/summary_card", { title:, rows:, summary_card_actions:, diff --git a/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/index/summary_card_component.rb b/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/index/summary_card_component.rb index 73e465872b5..4ffc0ed9361 100644 --- a/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/index/summary_card_component.rb +++ b/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/index/summary_card_component.rb @@ -6,6 +6,7 @@ def rows { key: item[:field], value: item[:value], + data: item[:data], } end end diff --git a/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/show/summary_list_component.rb b/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/show/summary_list_component.rb index 76cd37676e0..c0b05d52f7e 100644 --- a/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/show/summary_list_component.rb +++ b/lib/engines/content_block_manager/app/components/content_block_manager/content_block/document/show/summary_list_component.rb @@ -23,6 +23,10 @@ def embed_code_item { field: "Embed code", value: content_block_document.embed_code, + data: { + module: "copy-embed-code", + "embed-code": content_block_document.embed_code, + }, } end diff --git a/lib/engines/content_block_manager/features/search_for_object.feature b/lib/engines/content_block_manager/features/search_for_object.feature index 44d12c9ece1..009ab5c60b6 100644 --- a/lib/engines/content_block_manager/features/search_for_object.feature +++ b/lib/engines/content_block_manager/features/search_for_object.feature @@ -20,6 +20,8 @@ Feature: Search for a content object | title | ministry address | | email_address | ministry@example.com | | organisation | Ministry of Example | + + Scenario: GDS Editor can filter by organisation When I visit the Content Block Manager home page Then my organisation is already selected as a filter And I should see the details for all documents from my organisation @@ -28,23 +30,36 @@ Feature: Search for a content object Then "3" content blocks are returned Scenario: GDS Editor searches for a content object by keyword in title - When I enter the keyword "example search" + When I visit the Content Block Manager home page + And I enter the keyword "example search" And I click to view results Then I should see the content block with title "example search title" returned And "1" content blocks are returned Scenario: GDS Editor searches for a content object by keyword in details - When I enter the keyword "ABC123" + When I visit the Content Block Manager home page + And I enter the keyword "ABC123" And I click to view results Then I should see the content block with title "an address" returned And "1" content blocks are returned Scenario: GDS Editor searches for a content object by block type - When I check the block type "Email address" + When I visit the Content Block Manager home page + And I select the lead organisation "All organisations" + And I check the block type "Email address" And I click to view results And "2" content blocks are returned Scenario: GDS Editor searches for a content object by lead organisation - When I select the lead organisation "Ministry of Example" + When I visit the Content Block Manager home page + And I select the lead organisation "Ministry of Example" And I click to view results And "1" content blocks are returned + + @javascript + Scenario: GDS Editor can copy embed code + When I visit the Content Block Manager home page + And I select the lead organisation "Ministry of Example" + And I click to view results + And I click to copy the embed code for the content block "ministry address" + Then the embed code should be copied to my clipboard diff --git a/lib/engines/content_block_manager/features/step_definitions/content_block_manager_steps.rb b/lib/engines/content_block_manager/features/step_definitions/content_block_manager_steps.rb index 1994a40dabd..172ac9c42ec 100644 --- a/lib/engines/content_block_manager/features/step_definitions/content_block_manager_steps.rb +++ b/lib/engines/content_block_manager/features/step_definitions/content_block_manager_steps.rb @@ -595,3 +595,24 @@ def click_save_and_continue Then("I should see the content block manager home page") do expect(page).to have_content("All content blocks") end + +When("I click to copy the embed code") do + find("a", text: "Copy code").click + has_text?("Code copied") + @embed_code = @content_block.document.embed_code +end + +When("I click to copy the embed code for the content block {string}") do |content_block_name| + within(".govuk-summary-card", text: content_block_name) do + find("a", text: "Copy code").click + has_text?("Code copied") + document = ContentBlockManager::ContentBlock::Document.find_by(title: content_block_name) + @embed_code = document.embed_code + end +end + +Then("the embed code should be copied to my clipboard") do + page.driver.browser.execute_cdp("Browser.grantPermissions", origin: page.server_url, permissions: %w[clipboardReadWrite]) + clip_text = page.evaluate_async_script("navigator.clipboard.readText().then(arguments[0])") + expect(clip_text).to eq(@embed_code) +end diff --git a/lib/engines/content_block_manager/features/view_object.feature b/lib/engines/content_block_manager/features/view_object.feature index 951335aebae..a8dc0739a5e 100644 --- a/lib/engines/content_block_manager/features/view_object.feature +++ b/lib/engines/content_block_manager/features/view_object.feature @@ -20,3 +20,11 @@ Feature: View a content object And I click to view the document Then I should see the dependent content listed + @javascript + Scenario: GDS Editor can copy embed code + When I visit the Content Block Manager home page + Then I should see the details for all documents + When I click to view the document + And I click to copy the embed code + Then the embed code should be copied to my clipboard + diff --git a/lib/engines/content_block_manager/test/components/content_block/document/index/summary_card_component_test.rb b/lib/engines/content_block_manager/test/components/content_block/document/index/summary_card_component_test.rb index 32a4cdf421a..bcaf774b8ae 100644 --- a/lib/engines/content_block_manager/test/components/content_block/document/index/summary_card_component_test.rb +++ b/lib/engines/content_block_manager/test/components/content_block/document/index/summary_card_component_test.rb @@ -44,6 +44,8 @@ class ContentBlockManager::ContentBlock::Document::Index::SummaryCardComponentTe assert_selector ".govuk-summary-list__key", text: "Creator" assert_selector ".govuk-summary-list__value", text: content_block_edition.creator.name + assert_selector ".govuk-summary-list__row[data-module='copy-embed-code']", text: "Embed code" + assert_selector ".govuk-summary-list__row[data-embed-code='#{content_block_document.embed_code}']", text: "Embed code" assert_selector ".govuk-summary-list__key", text: "Embed code" assert_selector ".govuk-summary-list__value", text: content_block_document.embed_code diff --git a/lib/engines/content_block_manager/test/components/content_block/document/show/summary_list_component_test.rb b/lib/engines/content_block_manager/test/components/content_block/document/show/summary_list_component_test.rb index 739b2a4c63f..1ea8e19f4f5 100644 --- a/lib/engines/content_block_manager/test/components/content_block/document/show/summary_list_component_test.rb +++ b/lib/engines/content_block_manager/test/components/content_block/document/show/summary_list_component_test.rb @@ -39,6 +39,8 @@ class ContentBlockManager::ContentBlock::Document::Show::SummaryListComponentTes assert_selector ".govuk-summary-list__key", text: "Creator" assert_selector ".govuk-summary-list__value", text: content_block_edition.creator.name + assert_selector ".govuk-summary-list__row[data-module='copy-embed-code']", text: "Embed code" + assert_selector ".govuk-summary-list__row[data-embed-code='#{content_block_document.embed_code}']", text: "Embed code" assert_selector ".govuk-summary-list__key", text: "Embed code" assert_selector ".govuk-summary-list__value", text: content_block_document.embed_code