























1+// Control UI tests cover clipboard copy fallback behavior.
2+import { afterEach, describe, expect, it, vi } from "vitest";
3+import { copyToClipboard } from "./clipboard.ts";
4+5+// jsdom does not implement document.execCommand, so install a controllable mock
6+// per test and remove it afterwards to keep the fallback path observable.
7+function mockExecCommand(result: boolean): ReturnType<typeof vi.fn> {
8+const exec = vi.fn().mockReturnValue(result);
9+(document as unknown as { execCommand: unknown }).execCommand = exec;
10+return exec;
11+}
12+13+afterEach(() => {
14+vi.unstubAllGlobals();
15+delete (document as unknown as { execCommand?: unknown }).execCommand;
16+});
17+18+describe("copyToClipboard", () => {
19+it("returns false without touching the clipboard for empty text", async () => {
20+const writeText = vi.fn().mockResolvedValue(undefined);
21+vi.stubGlobal("navigator", { clipboard: { writeText } });
22+const exec = mockExecCommand(true);
23+24+expect(await copyToClipboard("")).toBe(false);
25+expect(writeText).not.toHaveBeenCalled();
26+expect(exec).not.toHaveBeenCalled();
27+});
28+29+it("uses the async Clipboard API in secure contexts", async () => {
30+const writeText = vi.fn().mockResolvedValue(undefined);
31+vi.stubGlobal("navigator", { clipboard: { writeText } });
32+const exec = mockExecCommand(true);
33+34+expect(await copyToClipboard("hello")).toBe(true);
35+expect(writeText).toHaveBeenCalledWith("hello");
36+expect(exec).not.toHaveBeenCalled();
37+});
38+39+it("falls back to execCommand when the Clipboard API rejects", async () => {
40+const writeText = vi.fn().mockRejectedValue(new Error("denied"));
41+vi.stubGlobal("navigator", { clipboard: { writeText } });
42+const exec = mockExecCommand(true);
43+44+expect(await copyToClipboard("hello")).toBe(true);
45+expect(exec).toHaveBeenCalledWith("copy");
46+expect(document.querySelector("textarea")).toBeNull();
47+});
48+49+it("falls back to execCommand over plain HTTP where navigator.clipboard is undefined", async () => {
50+vi.stubGlobal("navigator", {});
51+const exec = mockExecCommand(true);
52+53+expect(await copyToClipboard("hello")).toBe(true);
54+expect(exec).toHaveBeenCalledWith("copy");
55+expect(document.querySelector("textarea")).toBeNull();
56+});
57+58+it("returns false when both clipboard paths fail", async () => {
59+vi.stubGlobal("navigator", {});
60+const exec = mockExecCommand(false);
61+62+expect(await copyToClipboard("hello")).toBe(false);
63+expect(exec).toHaveBeenCalledWith("copy");
64+expect(document.querySelector("textarea")).toBeNull();
65+});
66+67+it("restores focus after the execCommand fallback", async () => {
68+vi.stubGlobal("navigator", {});
69+mockExecCommand(true);
70+const button = document.createElement("button");
71+document.body.append(button);
72+button.focus();
73+button.disabled = true;
74+75+expect(await copyToClipboard("hello")).toBe(true);
76+button.disabled = false;
77+await new Promise<void>((resolve) => {
78+window.setTimeout(resolve, 0);
79+});
80+expect(document.activeElement).toBe(button);
81+82+button.remove();
83+});
84+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。