Warn on tab close when create-flow module or wizard edits have not been saved.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
cleanup,
|
||||
within,
|
||||
waitFor,
|
||||
dispatchBeforeUnload,
|
||||
} from "../utils/test-utils";
|
||||
import { fireEvent } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
@@ -576,4 +577,62 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
expect(labels[1]).toMatch(/Code of Conduct/);
|
||||
expect(labels[2]).toMatch(/Core Principle/);
|
||||
});
|
||||
|
||||
it("does not block tab close when the create modal is unchanged", async () => {
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
onState={() => {
|
||||
/* noop */
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||
);
|
||||
await screen.findByRole("dialog");
|
||||
expect(dispatchBeforeUnload()).toBe(false);
|
||||
});
|
||||
|
||||
it("blocks tab close while the create modal has unsaved field edits", async () => {
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
onState={() => {
|
||||
/* noop */
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
const textboxes = within(dialog).getAllByRole(
|
||||
"textbox",
|
||||
) as HTMLTextAreaElement[];
|
||||
fireEvent.change(textboxes[0], { target: { value: "Unsaved principle" } });
|
||||
expect(dispatchBeforeUnload()).toBe(true);
|
||||
});
|
||||
|
||||
it("blocks tab close while the custom-policy wizard has unsaved edits", async () => {
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
onState={() => {
|
||||
/* noop */
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
||||
|
||||
const nameInput = await screen.findByPlaceholderText("Policy name");
|
||||
expect(dispatchBeforeUnload()).toBe(false);
|
||||
fireEvent.change(nameInput, { target: { value: "Renamed in wizard" } });
|
||||
expect(dispatchBeforeUnload()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { screen, fireEvent, waitFor, within } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { renderWithProviders } from "../utils/test-utils";
|
||||
import { renderWithProviders, dispatchBeforeUnload } from "../utils/test-utils";
|
||||
import { CoreValuesSelectScreen } from "../../app/(app)/create/screens/select/CoreValuesSelectScreen";
|
||||
|
||||
describe("CoreValuesSelectScreen", () => {
|
||||
@@ -65,6 +65,20 @@ describe("CoreValuesSelectScreen", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not block tab close when a pending value is unchanged", async () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
fireEvent.click(screen.getByText("Accessibility"));
|
||||
await screen.findByRole("dialog");
|
||||
expect(dispatchBeforeUnload()).toBe(false);
|
||||
});
|
||||
|
||||
it("blocks tab close after editing a pending value", async () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
fireEvent.click(screen.getByText("Accessibility"));
|
||||
await editMeaningInOpenDialog("Changed meaning");
|
||||
expect(dispatchBeforeUnload()).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps the pending value modal open when Keep editing is chosen", async () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
fireEvent.click(screen.getByText("Accessibility"));
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
renderWithProviders as render,
|
||||
screen,
|
||||
fireEvent,
|
||||
dispatchBeforeUnload,
|
||||
} from "../utils/test-utils";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import CustomMethodCardWizard from "../../app/(app)/create/components/CustomMethodCardWizard";
|
||||
|
||||
describe("CustomMethodCardWizard — tab close guard", () => {
|
||||
it("does not block unload when the wizard is open but unchanged", async () => {
|
||||
render(
|
||||
<CustomMethodCardWizard
|
||||
isOpen
|
||||
onClose={() => {
|
||||
/* noop */
|
||||
}}
|
||||
onFinalize={() => {
|
||||
/* noop */
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
await screen.findByPlaceholderText("Policy name");
|
||||
expect(dispatchBeforeUnload()).toBe(false);
|
||||
});
|
||||
|
||||
it("blocks unload after the user types a policy name", async () => {
|
||||
render(
|
||||
<CustomMethodCardWizard
|
||||
isOpen
|
||||
onClose={() => {
|
||||
/* noop */
|
||||
}}
|
||||
onFinalize={() => {
|
||||
/* noop */
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
const name = await screen.findByPlaceholderText("Policy name");
|
||||
fireEvent.change(name, { target: { value: "Garden hours" } });
|
||||
expect(dispatchBeforeUnload()).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
renderWithProviders as render,
|
||||
screen,
|
||||
waitFor,
|
||||
dispatchBeforeUnload,
|
||||
} from "../utils/test-utils";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { FinalReviewScreen } from "../../app/(app)/create/screens/review/FinalReviewScreen";
|
||||
@@ -623,6 +624,28 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
expect(latest.communicationMethodDetailsById).toBeUndefined();
|
||||
});
|
||||
|
||||
it("blocks tab close while chip edits are unsaved", async () => {
|
||||
render(
|
||||
<FinalReviewWithStateProbe
|
||||
onState={() => {
|
||||
/* noop */
|
||||
}}
|
||||
initial={baseSelections}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
expect(dispatchBeforeUnload()).toBe(false);
|
||||
const principleField = within(dialog).getByRole("textbox", {
|
||||
name: /core principle/i,
|
||||
});
|
||||
fireEvent.change(principleField, {
|
||||
target: { value: "Unsaved on tab close" },
|
||||
});
|
||||
expect(dispatchBeforeUnload()).toBe(true);
|
||||
});
|
||||
|
||||
it("shows consolidated placeholder for user-authored communication chips", async () => {
|
||||
const customId = "550e8400-e29b-41d4-a716-446655440000";
|
||||
render(
|
||||
|
||||
Reference in New Issue
Block a user