fix(webui): restore code block copy fallback
This commit is contained in:
@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import { useThemeValue } from "@/hooks/useTheme";
|
import { useThemeValue } from "@/hooks/useTheme";
|
||||||
import { hasAnsi, parseAnsiSegments, stripAnsi } from "@/lib/ansi";
|
import { hasAnsi, parseAnsiSegments, stripAnsi } from "@/lib/ansi";
|
||||||
|
import { copyTextToClipboard } from "@/lib/clipboard";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
interface CodeBlockProps {
|
interface CodeBlockProps {
|
||||||
@@ -192,8 +193,8 @@ export function CodeBlock({
|
|||||||
const renderAnsi = shouldRenderAnsi(language, code);
|
const renderAnsi = shouldRenderAnsi(language, code);
|
||||||
|
|
||||||
const onCopy = useCallback(() => {
|
const onCopy = useCallback(() => {
|
||||||
if (!navigator.clipboard) return;
|
void copyTextToClipboard(renderAnsi ? stripAnsi(code) : code).then((ok) => {
|
||||||
navigator.clipboard.writeText(renderAnsi ? stripAnsi(code) : code).then(() => {
|
if (!ok) return;
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 1_500);
|
setTimeout(() => setCopied(false), 1_500);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { act, render, screen } from "@testing-library/react";
|
import { act, render, screen, waitFor } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
@@ -146,6 +146,35 @@ describe("CodeBlock", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("copies with the textarea fallback when Clipboard API is unavailable", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
Object.defineProperty(navigator, "clipboard", {
|
||||||
|
configurable: true,
|
||||||
|
value: undefined,
|
||||||
|
});
|
||||||
|
const execCommand = vi.fn().mockReturnValue(true);
|
||||||
|
Object.defineProperty(document, "execCommand", {
|
||||||
|
configurable: true,
|
||||||
|
value: execCommand,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
render(
|
||||||
|
<ThemeProvider theme="dark">
|
||||||
|
<CodeBlock language="ts" code="const value = 1;" highlight={false} />
|
||||||
|
</ThemeProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
await user.click(screen.getByRole("button", { name: /copy/i }));
|
||||||
|
|
||||||
|
await waitFor(() => expect(execCommand).toHaveBeenCalledWith("copy"));
|
||||||
|
expect(screen.getByText("Copied")).toBeInTheDocument();
|
||||||
|
} finally {
|
||||||
|
Reflect.deleteProperty(navigator, "clipboard");
|
||||||
|
Reflect.deleteProperty(document, "execCommand");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("reads theme from context without creating per-block observers", async () => {
|
it("reads theme from context without creating per-block observers", async () => {
|
||||||
const originalMutationObserver = globalThis.MutationObserver;
|
const originalMutationObserver = globalThis.MutationObserver;
|
||||||
const observer = vi.fn();
|
const observer = vi.fn();
|
||||||
|
|||||||
Reference in New Issue
Block a user