|
|
@@ -0,0 +1,89 @@
|
|
|
+/* @vitest-environment node */
|
|
|
+
|
|
|
+import { describe, it, expect, vi, afterEach } from "vitest";
|
|
|
+import { writeTextToClipboard } from "./clipboard.js";
|
|
|
+
|
|
|
+function createDocumentStub({ copyResult = true } = {}) {
|
|
|
+ const textarea = {
|
|
|
+ value: "",
|
|
|
+ style: {},
|
|
|
+ setAttribute: vi.fn(),
|
|
|
+ focus: vi.fn(),
|
|
|
+ select: vi.fn(),
|
|
|
+ setSelectionRange: vi.fn(),
|
|
|
+ remove: vi.fn(),
|
|
|
+ };
|
|
|
+
|
|
|
+ const documentStub = {
|
|
|
+ createElement: vi.fn().mockReturnValue(textarea),
|
|
|
+ body: {
|
|
|
+ appendChild: vi.fn(),
|
|
|
+ },
|
|
|
+ execCommand: vi.fn().mockReturnValue(copyResult),
|
|
|
+ };
|
|
|
+
|
|
|
+ return { documentStub, textarea };
|
|
|
+}
|
|
|
+
|
|
|
+describe("lib/frontend/ui/clipboard", () => {
|
|
|
+ afterEach(() => {
|
|
|
+ vi.unstubAllGlobals();
|
|
|
+ vi.restoreAllMocks();
|
|
|
+ });
|
|
|
+
|
|
|
+ it("uses navigator.clipboard when available", async () => {
|
|
|
+ const writeText = vi.fn().mockResolvedValue(undefined);
|
|
|
+ vi.stubGlobal("navigator", {
|
|
|
+ clipboard: { writeText },
|
|
|
+ });
|
|
|
+
|
|
|
+ const { documentStub } = createDocumentStub({ copyResult: true });
|
|
|
+ vi.stubGlobal("document", documentStub);
|
|
|
+
|
|
|
+ const result = await writeTextToClipboard("secret");
|
|
|
+
|
|
|
+ expect(writeText).toHaveBeenCalledWith("secret");
|
|
|
+ expect(documentStub.execCommand).not.toHaveBeenCalled();
|
|
|
+ expect(result).toEqual({ ok: true, method: "clipboard" });
|
|
|
+ });
|
|
|
+
|
|
|
+ it("falls back to execCommand when Clipboard API is unavailable", async () => {
|
|
|
+ vi.stubGlobal("navigator", {});
|
|
|
+ const { documentStub, textarea } = createDocumentStub({ copyResult: true });
|
|
|
+ vi.stubGlobal("document", documentStub);
|
|
|
+
|
|
|
+ const result = await writeTextToClipboard("secret");
|
|
|
+
|
|
|
+ expect(documentStub.createElement).toHaveBeenCalledWith("textarea");
|
|
|
+ expect(documentStub.body.appendChild).toHaveBeenCalledWith(textarea);
|
|
|
+ expect(documentStub.execCommand).toHaveBeenCalledWith("copy");
|
|
|
+ expect(textarea.remove).toHaveBeenCalledTimes(1);
|
|
|
+ expect(result).toEqual({ ok: true, method: "execCommand" });
|
|
|
+ });
|
|
|
+
|
|
|
+ it("falls back to execCommand when Clipboard API throws", async () => {
|
|
|
+ const writeText = vi.fn().mockRejectedValue(new Error("not allowed"));
|
|
|
+ vi.stubGlobal("navigator", {
|
|
|
+ clipboard: { writeText },
|
|
|
+ });
|
|
|
+
|
|
|
+ const { documentStub } = createDocumentStub({ copyResult: true });
|
|
|
+ vi.stubGlobal("document", documentStub);
|
|
|
+
|
|
|
+ const result = await writeTextToClipboard("secret");
|
|
|
+
|
|
|
+ expect(writeText).toHaveBeenCalledWith("secret");
|
|
|
+ expect(documentStub.execCommand).toHaveBeenCalledWith("copy");
|
|
|
+ expect(result).toEqual({ ok: true, method: "execCommand" });
|
|
|
+ });
|
|
|
+
|
|
|
+ it("returns ok=false when no copy mechanism is available", async () => {
|
|
|
+ vi.stubGlobal("navigator", {});
|
|
|
+ vi.stubGlobal("document", undefined);
|
|
|
+
|
|
|
+ const result = await writeTextToClipboard("secret");
|
|
|
+
|
|
|
+ expect(result).toEqual({ ok: false, method: null });
|
|
|
+ });
|
|
|
+});
|
|
|
+
|