// useImportWizard — the folder read at mount marks itself done (#384, #398). // // No renderer in this project, and the reducer alone cannot reach the mount // read: it is an effect of the hook. React's hooks used by `useImportWizard` // are stubbed (technique of useImportWizard.executeImport.test.ts): the hook // runs as a plain function, its effect is captured and run by hand, and the // services it calls are mocked. What is pinned here is the `finally`: the // folder counts as read once the read and its scan are over, success or not. import { describe, it, expect, vi, beforeEach } from "vitest"; const harness = vi.hoisted(() => ({ state: null as unknown, dispatch: vi.fn(), effects: [] as Array<() => void>, })); vi.mock("react", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, useReducer: () => [harness.state, harness.dispatch], useCallback: (fn: unknown) => fn, useEffect: (fn: () => void) => { harness.effects.push(fn); }, }; }); vi.mock("@tauri-apps/api/core", () => ({ invoke: vi.fn() })); vi.mock("../services/db", () => ({ getDb: vi.fn(), withTransaction: vi.fn() })); vi.mock("../services/userPreferenceService", () => ({ getImportFolder: vi.fn(), setImportFolder: vi.fn(), })); vi.mock("../services/importSourceService", () => ({ getAllSources: vi.fn(async () => []), getSourceByName: vi.fn(async () => null), createSource: vi.fn(), updateSource: vi.fn(), })); vi.mock("../services/importedFileService", () => ({ existsByHash: vi.fn(), createImportedFile: vi.fn(), updateFileStatus: vi.fn(), getFilesBySourceId: vi.fn(async () => []), })); vi.mock("../services/importConfigTemplateService", () => ({ getAllTemplates: vi.fn(async () => []), createTemplate: vi.fn(), updateTemplate: vi.fn(), deleteTemplate: vi.fn(), })); import { invoke } from "@tauri-apps/api/core"; import { useImportWizard, initialWizardState } from "./useImportWizard"; import { getImportFolder } from "../services/userPreferenceService"; import type { ScannedSource } from "../shared/types"; const SOURCES: ScannedSource[] = [ { folder_name: "Desjardins", folder_path: "/releves/Desjardins", files: [] }, ]; /** Lets the mount read's promises settle. */ const flush = async () => { for (let i = 0; i < 20; i++) await Promise.resolve(); }; /** Mounts the hook and runs its mount effect, the first one it registers. */ async function mount() { useImportWizard(); const [readFolder] = harness.effects; readFolder(); await flush(); } const types = () => harness.dispatch.mock.calls.map((c) => (c[0] as { type: string }).type); beforeEach(() => { harness.state = initialWizardState; harness.effects = []; harness.dispatch.mockReset(); vi.mocked(getImportFolder).mockReset(); vi.mocked(invoke).mockReset(); }); describe("useImportWizard — the mount read marks the folder read (#384, #398)", () => { it("with a folder: after the folder and its first scan, as the last dispatch", async () => { vi.mocked(getImportFolder).mockResolvedValue("/releves"); vi.mocked(invoke).mockResolvedValue(SOURCES); await mount(); expect(invoke).toHaveBeenCalledWith("scan_import_folder", { folderPath: "/releves" }); const order = types(); expect(order[0]).toBe("SET_IMPORT_FOLDER"); expect(order.indexOf("SET_SCANNED_SOURCES")).toBeGreaterThan(0); expect(order[order.length - 1]).toBe("SET_IMPORT_FOLDER_LOADED"); expect(order.filter((t) => t === "SET_IMPORT_FOLDER_LOADED")).toHaveLength(1); }); it("without a folder: marked read, nothing scanned", async () => { vi.mocked(getImportFolder).mockResolvedValue(null); await mount(); expect(invoke).not.toHaveBeenCalled(); expect(harness.dispatch.mock.calls.map((c) => c[0])).toEqual([ { type: "SET_IMPORT_FOLDER", payload: null }, { type: "SET_IMPORT_FOLDER_LOADED" }, ]); }); it("when the preference cannot be read: still marked read (the empty state then offers a folder)", async () => { vi.mocked(getImportFolder).mockRejectedValue(new Error("database is locked")); await mount(); expect(types()).toEqual(["SET_IMPORT_FOLDER_LOADED"]); }); it("when the scan fails: marked read after the error", async () => { vi.mocked(getImportFolder).mockResolvedValue("/releves"); vi.mocked(invoke).mockRejectedValue(new Error("folder not found")); await mount(); const order = types(); expect(order).toContain("SET_ERROR"); expect(order[order.length - 1]).toBe("SET_IMPORT_FOLDER_LOADED"); }); });