clipboard.test.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import { afterEach, describe, expect, test, vi } from "vitest";
  2. import { copyTextToClipboard, copyToClipboard, isClipboardSupported } from "@/lib/utils/clipboard";
  3. function stubSecureContext(value: boolean) {
  4. Object.defineProperty(window, "isSecureContext", {
  5. value,
  6. configurable: true,
  7. });
  8. }
  9. function stubClipboard(writeText: (text: string) => Promise<void> | void) {
  10. Object.defineProperty(navigator, "clipboard", {
  11. value: { writeText },
  12. configurable: true,
  13. });
  14. }
  15. function stubExecCommand(impl: (command: string) => boolean) {
  16. Object.defineProperty(document, "execCommand", {
  17. value: impl,
  18. configurable: true,
  19. });
  20. }
  21. afterEach(() => {
  22. vi.unstubAllGlobals();
  23. vi.restoreAllMocks();
  24. });
  25. describe("clipboard utils", () => {
  26. test("SSR 环境:isClipboardSupported/copyTextToClipboard 应返回 false", async () => {
  27. vi.stubGlobal("window", undefined as unknown as Window);
  28. expect(isClipboardSupported()).toBe(false);
  29. await expect(copyTextToClipboard("abc")).resolves.toBe(false);
  30. });
  31. test("isClipboardSupported: 仅在安全上下文且 Clipboard API 可用时为 true", () => {
  32. stubSecureContext(false);
  33. stubClipboard(vi.fn());
  34. expect(isClipboardSupported()).toBe(false);
  35. stubSecureContext(true);
  36. stubClipboard(vi.fn());
  37. expect(isClipboardSupported()).toBe(true);
  38. });
  39. test("copyTextToClipboard: Clipboard API 成功时返回 true", async () => {
  40. stubSecureContext(true);
  41. const writeText = vi.fn().mockResolvedValue(undefined);
  42. stubClipboard(writeText);
  43. const execCommand = vi.fn();
  44. stubExecCommand(execCommand);
  45. const before = document.querySelectorAll("textarea").length;
  46. await expect(copyTextToClipboard("abc")).resolves.toBe(true);
  47. const after = document.querySelectorAll("textarea").length;
  48. expect(writeText).toHaveBeenCalledWith("abc");
  49. expect(execCommand).not.toHaveBeenCalled();
  50. expect(after).toBe(before);
  51. });
  52. test("copyTextToClipboard: Clipboard API 失败时应 fallback 到 execCommand", async () => {
  53. stubSecureContext(true);
  54. const writeText = vi.fn().mockRejectedValue(new Error("fail"));
  55. stubClipboard(writeText);
  56. const execCommand = vi.fn(() => true);
  57. stubExecCommand(execCommand);
  58. const before = document.querySelectorAll("textarea").length;
  59. await expect(copyTextToClipboard("abc")).resolves.toBe(true);
  60. const after = document.querySelectorAll("textarea").length;
  61. expect(writeText).toHaveBeenCalledWith("abc");
  62. expect(execCommand).toHaveBeenCalledWith("copy");
  63. expect(after).toBe(before);
  64. });
  65. test("copyTextToClipboard: 无 Clipboard API 时走 fallback(execCommand 失败则返回 false)", async () => {
  66. stubSecureContext(false);
  67. Object.defineProperty(navigator, "clipboard", { value: undefined, configurable: true });
  68. stubExecCommand(() => false);
  69. await expect(copyTextToClipboard("abc")).resolves.toBe(false);
  70. });
  71. test("copyTextToClipboard: fallback 抛错时返回 false", async () => {
  72. stubSecureContext(false);
  73. Object.defineProperty(navigator, "clipboard", { value: undefined, configurable: true });
  74. stubExecCommand(() => {
  75. throw new Error("boom");
  76. });
  77. await expect(copyTextToClipboard("abc")).resolves.toBe(false);
  78. });
  79. test("copyToClipboard: 兼容旧 API(内部调用 copyTextToClipboard)", async () => {
  80. stubSecureContext(true);
  81. const writeText = vi.fn().mockResolvedValue(undefined);
  82. stubClipboard(writeText);
  83. await expect(copyToClipboard("abc")).resolves.toBe(true);
  84. expect(writeText).toHaveBeenCalledWith("abc");
  85. });
  86. test("copyTextToClipboard: 无 document 时 fallback 直接返回 false", async () => {
  87. stubSecureContext(false);
  88. Object.defineProperty(navigator, "clipboard", { value: undefined, configurable: true });
  89. vi.stubGlobal("document", undefined as unknown as Document);
  90. await expect(copyTextToClipboard("abc")).resolves.toBe(false);
  91. });
  92. });