Update test infrastructure to support the dual app type parameter pattern
(app_type enum + app string) introduced in 590be4e:
- Add toast.warning mock for partial-success status handling
- Add syncCurrentProvidersLive mock to test post-import sync behavior
- Update MSW handlers to accept both app_type and app parameters with
fallback resolution (app ?? app_type) across 7 provider endpoints
- Add sync_current_providers_live endpoint mock returning success
This ensures test compatibility during the app_type → app parameter
migration and enables testing of the new partial-success import flow.
123 lines
3.8 KiB
TypeScript
123 lines
3.8 KiB
TypeScript
import { renderHook, act } from "@testing-library/react";
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
import { useImportExport } from "@/hooks/useImportExport";
|
|
|
|
const toastSuccessMock = vi.fn();
|
|
const toastErrorMock = vi.fn();
|
|
const toastWarningMock = vi.fn();
|
|
|
|
vi.mock("sonner", () => ({
|
|
toast: {
|
|
success: (...args: unknown[]) => toastSuccessMock(...args),
|
|
error: (...args: unknown[]) => toastErrorMock(...args),
|
|
warning: (...args: unknown[]) => toastWarningMock(...args),
|
|
},
|
|
}));
|
|
|
|
const openFileDialogMock = vi.fn();
|
|
const importConfigMock = vi.fn();
|
|
const saveFileDialogMock = vi.fn();
|
|
const exportConfigMock = vi.fn();
|
|
const syncCurrentProvidersLiveMock = vi.fn();
|
|
|
|
vi.mock("@/lib/api", () => ({
|
|
settingsApi: {
|
|
openFileDialog: (...args: unknown[]) => openFileDialogMock(...args),
|
|
importConfigFromFile: (...args: unknown[]) => importConfigMock(...args),
|
|
saveFileDialog: (...args: unknown[]) => saveFileDialogMock(...args),
|
|
exportConfigToFile: (...args: unknown[]) => exportConfigMock(...args),
|
|
syncCurrentProvidersLive: (...args: unknown[]) => syncCurrentProvidersLiveMock(...args),
|
|
},
|
|
}));
|
|
|
|
describe("useImportExport Hook (edge cases)", () => {
|
|
beforeEach(() => {
|
|
openFileDialogMock.mockReset();
|
|
importConfigMock.mockReset();
|
|
saveFileDialogMock.mockReset();
|
|
exportConfigMock.mockReset();
|
|
toastSuccessMock.mockReset();
|
|
toastErrorMock.mockReset();
|
|
toastWarningMock.mockReset();
|
|
syncCurrentProvidersLiveMock.mockReset();
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it("keeps state unchanged when file dialog resolves to null", async () => {
|
|
openFileDialogMock.mockResolvedValue(null);
|
|
const { result } = renderHook(() => useImportExport());
|
|
|
|
await act(async () => {
|
|
await result.current.selectImportFile();
|
|
});
|
|
|
|
expect(result.current.selectedFile).toBe("");
|
|
expect(result.current.status).toBe("idle");
|
|
expect(toastErrorMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("resetStatus clears errors but preserves selected file", async () => {
|
|
openFileDialogMock.mockResolvedValue("/config.json");
|
|
importConfigMock.mockResolvedValue({ success: false, message: "broken" });
|
|
const { result } = renderHook(() => useImportExport());
|
|
|
|
await act(async () => {
|
|
await result.current.selectImportFile();
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.importConfig();
|
|
});
|
|
|
|
act(() => {
|
|
result.current.resetStatus();
|
|
});
|
|
|
|
expect(result.current.selectedFile).toBe("/config.json");
|
|
expect(result.current.status).toBe("idle");
|
|
expect(result.current.errorMessage).toBeNull();
|
|
expect(result.current.backupId).toBeNull();
|
|
});
|
|
|
|
it("does not call onImportSuccess when import fails", async () => {
|
|
openFileDialogMock.mockResolvedValue("/config.json");
|
|
importConfigMock.mockResolvedValue({
|
|
success: false,
|
|
message: "invalid",
|
|
});
|
|
const onImportSuccess = vi.fn();
|
|
const { result } = renderHook(() => useImportExport({ onImportSuccess }));
|
|
|
|
await act(async () => {
|
|
await result.current.selectImportFile();
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.importConfig();
|
|
});
|
|
|
|
expect(onImportSuccess).not.toHaveBeenCalled();
|
|
expect(result.current.status).toBe("error");
|
|
});
|
|
|
|
it("propagates export success message to toast with saved path", async () => {
|
|
saveFileDialogMock.mockResolvedValue("/exports/config.json");
|
|
exportConfigMock.mockResolvedValue({ success: true, filePath: "/final/config.json" });
|
|
const { result } = renderHook(() => useImportExport());
|
|
|
|
await act(async () => {
|
|
await result.current.exportConfig();
|
|
});
|
|
|
|
expect(exportConfigMock).toHaveBeenCalledWith("/exports/config.json");
|
|
expect(toastSuccessMock).toHaveBeenCalledWith(
|
|
expect.stringContaining("/final/config.json"),
|
|
);
|
|
});
|
|
|
|
});
|