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;
+});