mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-05-26 18:06:00 +00:00
fix(frontend): guard message copy clipboard access (#3211)
* fix(frontend): guard message copy clipboard access * fix(frontend): reuse clipboard guard across copy actions
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { afterEach, expect, test, vi } from "vitest";
|
||||
|
||||
import { writeTextToClipboard } from "@/core/clipboard";
|
||||
|
||||
const originalNavigator = globalThis.navigator;
|
||||
const hadOriginalNavigator = "navigator" in globalThis;
|
||||
const originalDocument = globalThis.document;
|
||||
const hadOriginalDocument = "document" in globalThis;
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
if (!hadOriginalNavigator) {
|
||||
Reflect.deleteProperty(globalThis, "navigator");
|
||||
} else {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: originalNavigator,
|
||||
});
|
||||
}
|
||||
|
||||
if (!hadOriginalDocument) {
|
||||
Reflect.deleteProperty(globalThis, "document");
|
||||
} else {
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: originalDocument,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("writes text with the Clipboard API when available", async () => {
|
||||
const writeText = vi.fn().mockResolvedValue(undefined);
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {
|
||||
clipboard: {
|
||||
writeText,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(true);
|
||||
expect(writeText).toHaveBeenCalledWith("hello");
|
||||
});
|
||||
|
||||
test("returns false when Clipboard API is unavailable", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(false);
|
||||
});
|
||||
|
||||
test("falls back to execCommand when Clipboard API is unavailable", async () => {
|
||||
const textarea = {
|
||||
remove: vi.fn(),
|
||||
select: vi.fn(),
|
||||
setAttribute: vi.fn(),
|
||||
style: {},
|
||||
value: "",
|
||||
};
|
||||
const appendChild = vi.fn();
|
||||
const execCommand = vi.fn().mockReturnValue(true);
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild,
|
||||
},
|
||||
createElement: vi.fn().mockReturnValue(textarea),
|
||||
execCommand,
|
||||
},
|
||||
});
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(true);
|
||||
expect(textarea.value).toBe("hello");
|
||||
expect(appendChild).toHaveBeenCalledWith(textarea);
|
||||
expect(textarea.select).toHaveBeenCalled();
|
||||
expect(execCommand).toHaveBeenCalledWith("copy");
|
||||
expect(textarea.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns false when execCommand fallback fails", async () => {
|
||||
const textarea = {
|
||||
remove: vi.fn(),
|
||||
select: vi.fn(),
|
||||
setAttribute: vi.fn(),
|
||||
style: {},
|
||||
value: "",
|
||||
};
|
||||
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: {
|
||||
body: {
|
||||
appendChild: vi.fn(),
|
||||
},
|
||||
createElement: vi.fn().mockReturnValue(textarea),
|
||||
execCommand: vi.fn().mockReturnValue(false),
|
||||
},
|
||||
});
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(false);
|
||||
expect(textarea.remove).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("returns false when navigator is unavailable", async () => {
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
Object.defineProperty(globalThis, "document", {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(false);
|
||||
});
|
||||
|
||||
test("returns false when Clipboard API rejects", async () => {
|
||||
const writeText = vi.fn().mockRejectedValue(new Error("denied"));
|
||||
Object.defineProperty(globalThis, "navigator", {
|
||||
configurable: true,
|
||||
value: {
|
||||
clipboard: {
|
||||
writeText,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(writeTextToClipboard("hello")).resolves.toBe(false);
|
||||
});
|
||||
Reference in New Issue
Block a user