Files
fusion/packages/dashboard/app/components/__tests__/StashRecoveryView.test.tsx
Fusion d604d090a8 feat(FN-3863): add stash recovery dashboard surface and API routes
Merged FN-3863 brings stash recovery to the dashboard via three coordinated steps: engine-side orphan stash surfacing API in `merger.ts`, dashboard API routes for stash recovery data, and a new `StashRecoveryView` component with mobile support and inspect-diff row actions, integrated into the header

Fusion-Task-Id: FN-3863
2026-05-09 19:49:02 -07:00

51 lines
2.3 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { StashRecoveryView } from "../StashRecoveryView";
const apiMock = vi.fn();
const confirmMock = vi.fn();
vi.mock("../../api", () => ({ api: (...args: unknown[]) => apiMock(...args) }));
vi.mock("../../hooks/useConfirm", () => ({ useConfirm: () => ({ confirm: confirmMock }) }));
describe("StashRecoveryView", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders empty state", async () => {
apiMock.mockResolvedValueOnce({ records: [] });
render(<StashRecoveryView />);
expect(await screen.findByText(/No orphaned merger autostashes found/i)).toBeInTheDocument();
});
it("renders grouped rows and apply", async () => {
apiMock.mockResolvedValueOnce({ records: [{ sha: "abcdef123", sourceTaskId: "FN-1", createdAt: null, classification: "live", changedPaths: ["a"] }] });
apiMock.mockResolvedValueOnce({ ok: false, reason: "conflict", stderr: "conflict text" });
render(<StashRecoveryView />);
expect(await screen.findByText("FN-1")).toBeInTheDocument();
fireEvent.click(screen.getByText("Apply"));
await waitFor(() => expect(screen.getByText(/conflict text/i)).toBeInTheDocument());
});
it("opens inspect diff modal", async () => {
apiMock.mockResolvedValueOnce({ records: [{ sha: "abcdef123", sourceTaskId: "FN-1", createdAt: null, classification: "live", changedPaths: ["a"] }] });
apiMock.mockResolvedValueOnce({ diff: "patch-content", truncated: false });
render(<StashRecoveryView />);
await screen.findByText("FN-1");
fireEvent.click(screen.getByText("Inspect diff"));
expect(await screen.findByText(/Diff for abcdef1/i)).toBeInTheDocument();
await waitFor(() => expect(screen.getByText("patch-content")).toBeInTheDocument());
});
it("drop requires confirmation", async () => {
apiMock.mockResolvedValueOnce({ records: [{ sha: "abcdef123", sourceTaskId: null, createdAt: null, classification: "live", changedPaths: [] }] });
confirmMock.mockResolvedValueOnce(false);
render(<StashRecoveryView />);
await screen.findByText("Unknown source");
fireEvent.click(screen.getByText("Drop"));
await waitFor(() => expect(confirmMock).toHaveBeenCalled());
expect(apiMock).toHaveBeenCalledTimes(1);
});
});