mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 09:07:46 +02:00
Use the shared animated prompt-copy control across setup, invitations, webhooks, and task handoffs. Preserve first-click copying, clipboard recovery, and logo continuity. Add Storybook coverage and restore mention icon masks for the current Lucide data shape. Co-Authored-By: Paperclip <noreply@paperclip.ing>
83 lines
3.9 KiB
TypeScript
83 lines
3.9 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { act } from "react";
|
|
import { createRoot, type Root } from "react-dom/client";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { copyTextToClipboard } from "@/lib/clipboard";
|
|
import { GitHubSetupPrompt, buildGitHubSetupPrompt, githubSetupPrompt } from "./GitHubSetupPrompt";
|
|
|
|
vi.mock("@/lib/clipboard", () => ({ copyTextToClipboard: vi.fn() }));
|
|
|
|
describe("GitHub setup prompt", () => {
|
|
let container: HTMLDivElement;
|
|
let root: Root;
|
|
|
|
beforeEach(async () => {
|
|
vi.clearAllMocks();
|
|
container = document.createElement("div");
|
|
document.body.append(container);
|
|
root = createRoot(container);
|
|
await act(async () => root.render(<GitHubSetupPrompt />));
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await act(async () => root.unmount());
|
|
container.remove();
|
|
});
|
|
|
|
async function openPrompt() {
|
|
await act(async () => container.querySelector("button")!.click());
|
|
return document.querySelector('[role="dialog"]')!;
|
|
}
|
|
|
|
async function copyPrompt(dialog: Element) {
|
|
await act(async () => dialog.querySelector<HTMLButtonElement>(".agent-setup-copy")!.click());
|
|
}
|
|
|
|
it("copies on the opening click and confirms success in both buttons", async () => {
|
|
vi.mocked(copyTextToClipboard).mockResolvedValue(undefined);
|
|
const dialog = await openPrompt();
|
|
expect(copyTextToClipboard).toHaveBeenCalledTimes(1);
|
|
expect(copyTextToClipboard).toHaveBeenCalledWith(buildGitHubSetupPrompt(window.location.origin));
|
|
expect(vi.mocked(copyTextToClipboard).mock.calls[0][0]).toContain(`Paperclip instance URL: ${window.location.origin}`);
|
|
expect(dialog.querySelector(".agent-setup-copy")?.textContent).toBe("Copied to clipboard");
|
|
expect(container.querySelector('[role="status"]')?.textContent).toContain("Ready to paste into your agent.");
|
|
expect(container.querySelector("button")?.dataset.copied).toBe("true");
|
|
expect(dialog.querySelector("textarea")).toBeNull();
|
|
});
|
|
|
|
it("offers selectable instructions when clipboard access fails and allows retry", async () => {
|
|
vi.mocked(copyTextToClipboard).mockRejectedValueOnce(new Error("Clipboard unavailable"));
|
|
const dialog = await openPrompt();
|
|
const fallback = dialog.querySelector("textarea")!;
|
|
expect(fallback.value).toBe(buildGitHubSetupPrompt(window.location.origin));
|
|
expect(fallback.readOnly).toBe(true);
|
|
fallback.focus();
|
|
expect(fallback.selectionStart).toBe(0);
|
|
expect(fallback.selectionEnd).toBe(fallback.value.length);
|
|
expect(dialog.querySelector('[role="alert"]')?.textContent).toContain("Could not copy");
|
|
vi.mocked(copyTextToClipboard).mockResolvedValueOnce(undefined);
|
|
await copyPrompt(dialog);
|
|
expect(dialog.querySelector('[role="alert"]')).toBeNull();
|
|
expect(dialog.querySelector("textarea")).toBeNull();
|
|
});
|
|
|
|
it("uses the preview's configured instance instead of its Storybook host", async () => {
|
|
await act(async () => root.render(<GitHubSetupPrompt instanceUrl="https://my-company.paperclip.app" />));
|
|
vi.mocked(copyTextToClipboard).mockResolvedValue(undefined);
|
|
const dialog = await openPrompt();
|
|
expect(copyTextToClipboard).toHaveBeenCalledTimes(1);
|
|
expect(copyTextToClipboard).toHaveBeenCalledWith(buildGitHubSetupPrompt("https://my-company.paperclip.app"));
|
|
});
|
|
|
|
it("includes only the instance origin, without URL credentials or callback state", () => {
|
|
const prompt = buildGitHubSetupPrompt("https://user:private-password@my-company.paperclip.app/GIT/apps/chat/connect?code=private-code#private-state");
|
|
expect(prompt).toContain("Paperclip instance URL: https://my-company.paperclip.app\n");
|
|
expect(prompt).not.toMatch(/private-password|private-code|private-state/);
|
|
expect(prompt).toContain(githubSetupPrompt);
|
|
});
|
|
|
|
it.each(["", "not a URL", "javascript:alert(1)"])("asks for the URL when preview configuration is unavailable: %s", (url) => {
|
|
expect(buildGitHubSetupPrompt(url)).toMatch(/^Paperclip instance URL is unavailable\./);
|
|
});
|
|
});
|