From 6d0bd2506775fae2bc6618dc7e34144fe45194e5 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 04:55:45 +0000 Subject: [PATCH] Refactor copy logic to remove deprecated execCommand Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- js/ui/components/EmbedVideoModal.js | 16 +--- js/ui/loginModalController.js | 5 +- tests/unit/ui/EmbedVideoModal.test.mjs | 108 +++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 17 deletions(-) create mode 100644 tests/unit/ui/EmbedVideoModal.test.mjs diff --git a/js/ui/components/EmbedVideoModal.js b/js/ui/components/EmbedVideoModal.js index 54e5ab8e..03c895e7 100644 --- a/js/ui/components/EmbedVideoModal.js +++ b/js/ui/components/EmbedVideoModal.js @@ -399,6 +399,7 @@ export class EmbedVideoModal { return; } + // Attempt to copy using the Clipboard API if (navigator?.clipboard?.writeText) { try { await navigator.clipboard.writeText(snippet); @@ -409,24 +410,13 @@ export class EmbedVideoModal { } } + // Fallback: Select the text so the user can copy manually const textarea = this.snippetTextarea; textarea.focus(); textarea.select(); textarea.setSelectionRange(0, textarea.value.length); - let succeeded = false; - try { - succeeded = this.document.execCommand("copy"); - } catch (error) { - logger.user.warn("Embed modal copy fallback failed.", error); - succeeded = false; - } - - if (succeeded) { - this.callbacks.showSuccess("Embed code copied to clipboard!"); - } else { - this.callbacks.showError("Unable to copy embed code. Please copy it manually."); - } + this.callbacks.showError("Unable to copy embed code. Please copy it manually."); } async open({ video, triggerElement } = {}) { diff --git a/js/ui/loginModalController.js b/js/ui/loginModalController.js index b35e473c..10448b1f 100644 --- a/js/ui/loginModalController.js +++ b/js/ui/loginModalController.js @@ -1550,11 +1550,8 @@ export default class LoginModalController { if (!copied && handshakeInput.select) { try { handshakeInput.select(); - if (this.document?.execCommand) { - copied = this.document.execCommand("copy"); - } } catch (error) { - copied = false; + // no-op } } if (copied) { diff --git a/tests/unit/ui/EmbedVideoModal.test.mjs b/tests/unit/ui/EmbedVideoModal.test.mjs new file mode 100644 index 00000000..67874e14 --- /dev/null +++ b/tests/unit/ui/EmbedVideoModal.test.mjs @@ -0,0 +1,108 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +// Mock globals +if (!globalThis.window) { + globalThis.window = { + location: { origin: "http://localhost" }, + NostrTools: { nip19: { naddrEncode: () => "naddr...", neventEncode: () => "nevent..." } } + }; +} +if (!globalThis.document) { + globalThis.document = { + createElement: (tag) => { + return { + tagName: tag.toUpperCase(), + innerHTML: "", + firstChild: null, + querySelector: () => null, + classList: { add: () => {}, remove: () => {}, contains: () => false }, + addEventListener: () => {}, + setAttribute: () => {}, + appendChild: () => {}, + disabled: false, + checked: false, + value: "" + }; + }, + createDocumentFragment: () => ({ appendChild: () => {} }), + getElementById: () => null, + body: { appendChild: () => {} }, + execCommand: () => {} + }; +} +if (!globalThis.navigator) { + globalThis.navigator = { + userAgent: "node", + }; +} +if (!globalThis.navigator.clipboard) { + globalThis.navigator.clipboard = { + writeText: async () => {}, + }; +} + +import { EmbedVideoModal } from "../../../js/ui/components/EmbedVideoModal.js"; + +test("EmbedVideoModal handleCopy uses navigator.clipboard", async (t) => { + let clipboardText = ""; + const originalWriteText = globalThis.navigator.clipboard.writeText; + globalThis.navigator.clipboard.writeText = async (text) => { + clipboardText = text; + }; + + let successMsg = ""; + const callbacks = { + showSuccess: (msg) => { successMsg = msg; }, + showError: () => {} + }; + + const modal = new EmbedVideoModal({ callbacks }); + modal.snippetTextarea = { value: "", select: () => {}, setSelectionRange: () => {}, focus: () => {} }; + + await modal.handleCopy(); + + assert.equal(clipboardText, ""); + assert.equal(successMsg, "Embed code copied to clipboard!"); + + globalThis.navigator.clipboard.writeText = originalWriteText; +}); + +test("EmbedVideoModal handleCopy handles clipboard failure without execCommand", async (t) => { + // Simulate clipboard failure + const originalWriteText = globalThis.navigator.clipboard.writeText; + globalThis.navigator.clipboard.writeText = async () => { throw new Error("Clipboard error"); }; + + let errorMsg = ""; + const callbacks = { + showSuccess: () => {}, + showError: (msg) => { errorMsg = msg; } + }; + + // Mock document.execCommand to track calls + let execCommandCalled = false; + const originalExecCommand = globalThis.document.execCommand; + globalThis.document.execCommand = () => { execCommandCalled = true; return false; }; + + const modal = new EmbedVideoModal({ callbacks }); + // Ensure modal uses our mocked document + modal.document = globalThis.document; + + let selectCalled = false; + modal.snippetTextarea = { + value: "", + select: () => { selectCalled = true; }, + setSelectionRange: () => {}, + focus: () => {} + }; + + await modal.handleCopy(); + + assert.equal(execCommandCalled, false, "execCommand should not be called"); + assert.equal(selectCalled, true, "Text should be selected for manual copy"); + assert.ok(errorMsg.toLowerCase().includes("manual"), "Error message should mention manual copy"); + + // Restore + globalThis.document.execCommand = originalExecCommand; + globalThis.navigator.clipboard.writeText = originalWriteText; +});