Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions apps/web/components/dashboard/email-editor-screen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,51 @@ describe("EmailEditorScreen", () => {
).toBe(false);
});

it("doesn't ask to save again after a save made from an action", async () => {
Object.defineProperty(window.history, "length", { configurable: true, value: 2 });
const onSave = mock().mockResolvedValue(undefined);
render(
<EmailEditorScreen
exitFallbackHref="/templates"
purpose="transactional"
initialContent={initialContent}
onSave={onSave}
actions={(saveContent) => (
<button type="button" onClick={() => void saveContent()}>
Send test
</button>
)}
/>,
);

fireEvent.click(screen.getByText("Change document"));
fireEvent.click(screen.getByRole("button", { name: "Send test" }));
await waitFor(() => expect(onSave).toHaveBeenCalledWith(mocks.changedContent));

fireEvent.click(screen.getByRole("button", { name: "Exit editor" }));

expect(screen.queryByText("Save your changes?")).toBeNull();
expect(mocks.back).toHaveBeenCalled();
});

it("gives the unsaved-changes dialog room for its three actions", () => {
render(
<EmailEditorScreen
exitFallbackHref="/templates"
purpose="transactional"
initialContent={initialContent}
onSave={mock()}
/>,
);

fireEvent.click(screen.getByText("Change document"));
fireEvent.click(screen.getByRole("button", { name: "Exit editor" }));

const dialog = screen.getByRole("alertdialog");
expect(dialog.className).toContain("data-[size=default]:sm:max-w-lg");
expect(dialog.className).not.toContain("data-[size=default]:sm:max-w-sm");
});

it("uses the fallback route for a deep-linked editor", () => {
Object.defineProperty(window.history, "length", {
configurable: true,
Expand Down
22 changes: 17 additions & 5 deletions apps/web/components/dashboard/email-editor-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -303,19 +303,30 @@ export function EmailEditorScreen({
}
}

/** Persists the current document, throwing on failure. Every save goes
* through here, so a save from an action (such as a test send) also counts
* as saved for the unsaved-changes check. */
async function persist() {
contentChanged.current = false;
try {
await onSave(contentRef.current);
} catch (err) {
contentChanged.current = true;
throw err;
}
}

/** Saves, returning whether it worked. */
async function save(): Promise<boolean> {
if (saving) return false;
setSaving(true);
setError(null);
try {
contentChanged.current = false;
await onSave(contentRef.current);
await persist();
setSaved(true);
setTimeout(() => setSaved(false), 2000);
return true;
} catch (err) {
contentChanged.current = true;
setError(err instanceof ApiError ? err.message : "Failed to save email");
return false;
} finally {
Expand All @@ -337,7 +348,7 @@ export function EmailEditorScreen({
</Badge>
</div>
<div className="flex items-center gap-2">
{actions?.(() => onSave(contentRef.current))}
{actions?.(persist)}
<Button onClick={() => void save()} disabled={saving}>
{saving ? (
<Loader size={16} />
Expand All @@ -355,7 +366,8 @@ export function EmailEditorScreen({
{error && <Banner className="mx-4 mt-4 shrink-0">{error}</Banner>}

<AlertDialog open={leaving} onOpenChange={setLeaving}>
<AlertDialogContent>
{/* Three actions don't fit the default width of a confirmation. */}
<AlertDialogContent className="data-[size=default]:sm:max-w-lg">
<AlertDialogHeader>
<AlertDialogTitle>Save your changes?</AlertDialogTitle>
<AlertDialogDescription>
Expand Down
Loading