import { afterEach, describe, expect, it, rs } from "@rstest/core";
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
const mocks = rs.hoisted(() => ({
pathname: "/workspace/chats/a",
user: { id: "account-a" } as { id: string } | null,
}));
rs.mock("next/navigation", () => ({ usePathname: () => mocks.pathname }));
rs.mock("@/components/ui/sidebar", () => ({
useSidebar: () => ({ setOpen: rs.fn() }),
}));
rs.mock("@/core/auth/AuthProvider", () => ({
useAuth: () => ({ user: mocks.user }),
}));
rs.mock("@/env", () => ({
env: { NEXT_PUBLIC_STATIC_WEBSITE_ONLY: "false" },
}));
import {
ArtifactsProvider,
useArtifacts,
} from "@/components/workspace/artifacts/context";
import {
ArtifactDraftsProvider,
useArtifactDraftOwner,
} from "@/core/artifacts/draft-context";
const filepath = "/mnt/user-data/outputs/report.txt";
function Probe() {
const { drafts, setDrafts } = useArtifacts();
const draft = drafts[filepath];
return (
<>
>
);
}
function AccountWorkspace({
show = true,
routeKey = "stable",
}: {
show?: boolean;
routeKey?: string;
}) {
useArtifactDraftOwner(mocks.user?.id ?? null);
return (
<>
{show ? (
) : null}
>
);
}
function Workspace(props: {
show?: boolean;
workspace?: boolean;
routeKey?: string;
}) {
return (
{props.workspace === false ? (
Home
) : (
)}
);
}
afterEach(() => {
cleanup();
window.sessionStorage.clear();
mocks.pathname = "/workspace/chats/a";
mocks.user = { id: "account-a" };
});
describe("artifact draft navigation", () => {
it("keeps drafts and the unload warning across the workspace-to-home exit", () => {
const view = render();
fireEvent.click(screen.getByText("Edit"));
mocks.pathname = "/";
view.rerender();
const event = new Event("beforeunload", { cancelable: true });
window.dispatchEvent(event);
expect(event.defaultPrevented).toBe(true);
mocks.pathname = "/workspace/chats/a";
view.rerender();
expect(screen.getByTestId("draft").textContent).toBe("unsaved edit");
});
it("keeps separate drafts through A-B-A navigation", () => {
const view = render();
fireEvent.click(screen.getByText("Edit"));
mocks.pathname = "/workspace/chats/b";
view.rerender();
expect(screen.getByTestId("draft").textContent).toBe("no draft");
fireEvent.click(screen.getByText("Edit"));
expect(screen.getByTestId("draft").textContent).toBe("B edit");
mocks.pathname = "/workspace/chats/a";
view.rerender();
expect(screen.getByTestId("draft").textContent).toBe("unsaved edit");
mocks.pathname = "/workspace/chats/b";
view.rerender();
expect(screen.getByTestId("draft").textContent).toBe("B edit");
});
it("retains a draft when the route-local provider unmounts", () => {
const view = render();
fireEvent.click(screen.getByText("Edit"));
view.rerender();
view.rerender();
expect(screen.getByTestId("draft").textContent).toBe("unsaved edit");
});
it("does not show the previous account's draft on the same route", () => {
const view = render();
fireEvent.click(screen.getByText("Edit"));
mocks.user = { id: "account-b" };
view.rerender();
expect(screen.getByTestId("draft").textContent).toBe("no draft");
mocks.user = { id: "account-a" };
view.rerender();
expect(screen.getByTestId("draft").textContent).toBe("no draft");
});
it("clears account drafts on logout and removes their unload warning", () => {
const view = render();
fireEvent.click(screen.getByText("Edit"));
mocks.user = null;
view.rerender();
expect(screen.getByTestId("draft").textContent).toBe("no draft");
const event = new Event("beforeunload", { cancelable: true });
window.dispatchEvent(event);
expect(event.defaultPrevented).toBe(false);
mocks.user = { id: "account-a" };
view.rerender();
expect(screen.getByTestId("draft").textContent).toBe("no draft");
});
it("warns about dirty drafts when no artifact page is mounted", () => {
const view = render();
fireEvent.click(screen.getByText("Edit"));
view.rerender();
const event = new Event("beforeunload", { cancelable: true });
window.dispatchEvent(event);
expect(event.defaultPrevented).toBe(true);
});
it("does not keep file contents in browser storage or across root unmount", () => {
const view = render();
fireEvent.click(screen.getByText("Edit"));
const stored = Object.values(window.sessionStorage).join("");
expect(stored).not.toContain("unsaved edit");
view.unmount();
render();
expect(screen.getByTestId("draft").textContent).toBe("no draft");
});
it("clears the unload warning after Save and does not revive discarded drafts", () => {
const view = render();
fireEvent.click(screen.getByText("Edit"));
const dirty = new Event("beforeunload", { cancelable: true });
window.dispatchEvent(dirty);
expect(dirty.defaultPrevented).toBe(true);
fireEvent.click(screen.getByText("Save"));
const saved = new Event("beforeunload", { cancelable: true });
window.dispatchEvent(saved);
expect(saved.defaultPrevented).toBe(false);
fireEvent.click(screen.getByText("Discard"));
view.rerender();
view.rerender();
expect(screen.getByTestId("draft").textContent).toBe("no draft");
});
});