Prevent duplicate planning-history rows and keep deletions visible only after the backend confirms persistence. - dedupe Planning Mode history by session id across initial loads, live updates, archive toggles, and draft creation - make deleteAiSession surface backend/content-type errors instead of silently succeeding on failed deletes - keep failed history deletions visible, refresh the list, and show an error toast when persistence fails - add dashboard tests covering session deduplication, delete API failures, and persistence-aware Planning Mode deletion behavior - document the history dedupe/delete semantics and add a patch changeset for @runfusion/fusion Files changed: .changeset/fn-5892-planning-history-fix.md | 5 + docs/dashboard-guide.md | 2 +- packages/dashboard/app/api/__tests__/deleteAiSession.test.ts | 54 +++++ packages/dashboard/app/api/legacy.ts | 41 +++- packages/dashboard/app/components/PlanningModeModal.tsx | 70 ++++--- packages/dashboard/app/components/__tests__/PlanningModeModal.autosize.test.tsx | 11 + packages/dashboard/app/components/__tests__/PlanningModeModal.dedupeSessionsById.test.ts | 44 ++++ packages/dashboard/app/components/__tests__/PlanningModeModal.initial.test.tsx | 11 + packages/dashboard/app/components/__tests__/PlanningModeModal.planning-flow.test.tsx | 226 ++++++++++++++++++++- packages/dashboard/app/components/__tests__/PlanningModeModal.test-helpers.ts | 1 + 10 files changed, 439 insertions(+), 26 deletions(-) Fusion-Task-Id: FN-5892 Fusion-Task-Lineage: 7453bc34-6d57-4d67-ac92-8fb50084fbee
55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { ApiRequestError, deleteAiSession } from "../legacy";
|
|
|
|
describe("deleteAiSession", () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("resolves on 200 responses", async () => {
|
|
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
new Response(JSON.stringify({ ok: true }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
}),
|
|
);
|
|
|
|
await expect(deleteAiSession("session-1")).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("treats 404 responses as idempotent success", async () => {
|
|
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
new Response(JSON.stringify({ error: "Session not found" }), {
|
|
status: 404,
|
|
headers: { "content-type": "application/json" },
|
|
}),
|
|
);
|
|
|
|
await expect(deleteAiSession("missing-session")).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("rejects on non-404 server failures", async () => {
|
|
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
new Response(JSON.stringify({ error: "Delete failed", details: { retryable: false } }), {
|
|
status: 500,
|
|
headers: { "content-type": "application/json" },
|
|
}),
|
|
);
|
|
|
|
await expect(deleteAiSession("session-1")).rejects.toEqual(
|
|
expect.objectContaining<ApiRequestError>({
|
|
name: "ApiRequestError",
|
|
message: "Delete failed",
|
|
status: 500,
|
|
details: { retryable: false },
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("rejects on network failures", async () => {
|
|
vi.spyOn(globalThis, "fetch").mockRejectedValue(new TypeError("Failed to fetch"));
|
|
|
|
await expect(deleteAiSession("session-1")).rejects.toThrow("Failed to fetch");
|
|
});
|
|
});
|