Let guests keep a published rule via Save & Exit and a post-finalize sign-in prompt.

Email is optional so they can continue without saving; skip from Save & Exit leaves the flow instead of returning to completed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilallo
2026-09-02 16:18:41 -06:00
co-authored by Cursor
parent 84ef943df4
commit db34a1f0df
17 changed files with 521 additions and 60 deletions
+20
View File
@@ -110,6 +110,26 @@ describe("Login", () => {
expect(onClose).toHaveBeenCalledTimes(1);
});
it("closes on backdrop pointerdown, not a leftover click", async () => {
const onClose = vi.fn();
renderWithProviders(
<Login isOpen onClose={onClose} ariaLabelledBy="login-modal-heading">
<p id="login-modal-heading">Body</p>
</Login>,
);
await waitFor(() => {
expect(screen.getByRole("dialog")).toBeInTheDocument();
});
const backdrop = screen.getByRole("dialog").parentElement;
expect(backdrop).not.toBeNull();
fireEvent.click(backdrop!);
expect(onClose).not.toHaveBeenCalled();
fireEvent.pointerDown(screen.getByRole("dialog"));
expect(onClose).not.toHaveBeenCalled();
fireEvent.pointerDown(backdrop!);
expect(onClose).toHaveBeenCalledTimes(1);
});
it("locks body scroll while open", async () => {
renderWithProviders(
<Login isOpen onClose={vi.fn()} ariaLabelledBy="login-modal-heading">
+64
View File
@@ -174,6 +174,70 @@ describe("LoginForm", () => {
expect(setTransferPendingFlag).toHaveBeenCalled();
});
it("keepRule variant claims without attaching a create-flow draft", async () => {
const user = userEvent.setup();
window.localStorage.setItem(
"create-flow-anonymous",
JSON.stringify({ title: "Guest draft" }),
);
vi.mocked(requestMagicLink).mockResolvedValue({ ok: true });
renderWithProviders(
<Suspense fallback={null}>
<LoginForm
variant="keepRule"
magicLinkNextPath="/create/completed"
/>
</Suspense>,
);
expect(
screen.getByRole("heading", {
name: /sign in to keep this rule on your profile/i,
}),
).toBeInTheDocument();
await user.type(
screen.getByRole("textbox", { name: /email address/i }),
"guest@example.com",
);
await user.click(
screen.getByRole("button", { name: /send me a magic link/i }),
);
await waitFor(() => {
expect(requestMagicLink).toHaveBeenCalledWith(
"guest@example.com",
"/create/completed",
undefined,
);
});
expect(setTransferPendingFlag).not.toHaveBeenCalled();
window.localStorage.removeItem("create-flow-anonymous");
});
it("keepRule continue without saving calls onDismiss", async () => {
const user = userEvent.setup();
const onDismiss = vi.fn();
renderWithProviders(
<Suspense fallback={null}>
<LoginForm
variant="keepRule"
magicLinkNextPath="/create/completed"
onDismiss={onDismiss}
/>
</Suspense>,
);
await user.click(
screen.getByRole("button", { name: /continue without saving/i }),
);
expect(onDismiss).toHaveBeenCalledTimes(1);
expect(requestMagicLink).not.toHaveBeenCalled();
});
it("default variant has no continue without saving action", () => {
renderLoginForm();
expect(
screen.queryByRole("button", { name: /continue without saving/i }),
).not.toBeInTheDocument();
});
it("passes safe next path when next query param is set", async () => {
const user = userEvent.setup();
navMock.searchParams = new URLSearchParams("next=/learn");