mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
141 lines
4.9 KiB
TypeScript
141 lines
4.9 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const revokeObjectURL = vi.fn();
|
|
const createObjectURL = vi.fn((_obj: Blob | MediaSource) => "blob:fake-url");
|
|
|
|
vi.stubGlobal("URL", {
|
|
...globalThis.URL,
|
|
createObjectURL,
|
|
revokeObjectURL,
|
|
});
|
|
|
|
const imagePreviewMock = vi.hoisted(() => ({
|
|
needsServerPreview: vi.fn(() => false),
|
|
fetchDecodedPreview: vi.fn(() => Promise.resolve(null)),
|
|
}));
|
|
|
|
vi.mock("@/lib/image-preview", () => imagePreviewMock);
|
|
|
|
vi.mock("@/lib/analytics", () => ({
|
|
track: vi.fn(),
|
|
}));
|
|
|
|
import { previewKindFor, useFileStore } from "@/stores/file-store";
|
|
|
|
function makeFile(name: string, size = 1024, type = "image/png"): File {
|
|
const buf = new ArrayBuffer(size);
|
|
return new File([buf], name, { type });
|
|
}
|
|
|
|
describe("useFileStore branch coverage", () => {
|
|
beforeEach(() => {
|
|
useFileStore.getState().reset();
|
|
vi.clearAllMocks();
|
|
imagePreviewMock.needsServerPreview.mockReturnValue(false);
|
|
imagePreviewMock.fetchDecodedPreview.mockResolvedValue(null);
|
|
let urlCounter = 0;
|
|
createObjectURL.mockImplementation((_obj: Blob | MediaSource) => `blob:url-${++urlCounter}`);
|
|
});
|
|
|
|
it("maps unknown modalities to no preview", () => {
|
|
expect(previewKindFor("unknown" as never)).toBe("none");
|
|
});
|
|
|
|
it("removeFile is a no-op for missing indexes", () => {
|
|
useFileStore.getState().setFiles([makeFile("a.png")]);
|
|
const before = useFileStore.getState().entries;
|
|
revokeObjectURL.mockClear();
|
|
|
|
useFileStore.getState().removeFile(3);
|
|
|
|
expect(useFileStore.getState().entries).toBe(before);
|
|
expect(revokeObjectURL).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("removeFile revokes processed preview URLs", () => {
|
|
useFileStore.getState().setFiles([makeFile("a.png")]);
|
|
useFileStore.getState().updateEntry(0, {
|
|
processedUrl: "blob:processed",
|
|
processedPreviewUrl: "blob:processed-preview",
|
|
});
|
|
revokeObjectURL.mockClear();
|
|
|
|
useFileStore.getState().removeFile(0);
|
|
|
|
expect(revokeObjectURL).toHaveBeenCalledWith("blob:processed");
|
|
expect(revokeObjectURL).toHaveBeenCalledWith("blob:processed-preview");
|
|
});
|
|
|
|
it("setError stops processing only when an error is present", () => {
|
|
useFileStore.getState().setProcessing(true);
|
|
useFileStore.getState().setError(null);
|
|
expect(useFileStore.getState()).toMatchObject({ error: null, processing: true });
|
|
|
|
useFileStore.getState().setError("failed");
|
|
expect(useFileStore.getState()).toMatchObject({ error: "failed", processing: false });
|
|
});
|
|
|
|
it("setProcessedUrl and setSizes are no-ops without a selected entry", () => {
|
|
expect(() => useFileStore.getState().setProcessedUrl("blob:result")).not.toThrow();
|
|
expect(() => useFileStore.getState().setSizes(1, 2)).not.toThrow();
|
|
expect(useFileStore.getState().entries).toEqual([]);
|
|
expect(useFileStore.getState().processedUrl).toBeNull();
|
|
expect(useFileStore.getState().processedSize).toBeNull();
|
|
});
|
|
|
|
it("stores processed preview URLs on the selected entry", () => {
|
|
useFileStore.getState().setFiles([makeFile("a.png")]);
|
|
|
|
useFileStore.getState().setProcessedUrl("blob:result", "blob:preview");
|
|
|
|
expect(useFileStore.getState().entries[0]).toMatchObject({
|
|
processedUrl: "blob:result",
|
|
processedPreviewUrl: "blob:preview",
|
|
processedFilename: null,
|
|
status: "completed",
|
|
});
|
|
expect(useFileStore.getState().processedPreviewUrl).toBe("blob:preview");
|
|
});
|
|
|
|
it("applies decoded previews only when the entry still contains the same file", async () => {
|
|
imagePreviewMock.needsServerPreview.mockReturnValue(true);
|
|
imagePreviewMock.fetchDecodedPreview.mockImplementation((file: File) =>
|
|
Promise.resolve(
|
|
file.name === "a.heic"
|
|
? { url: "blob:decoded-a", originalWidth: 640, originalHeight: 480 }
|
|
: { url: "blob:decoded-b", originalWidth: 320, originalHeight: 240 },
|
|
),
|
|
);
|
|
|
|
const firstFile = makeFile("a.heic", 100, "image/heic");
|
|
const replacementFile = makeFile("b.heic", 100, "image/heic");
|
|
useFileStore.getState().setFiles([firstFile]);
|
|
useFileStore.getState().setFiles([replacementFile]);
|
|
|
|
await vi.waitFor(() => {
|
|
expect(useFileStore.getState().entries[0].blobUrl).toBe("blob:decoded-b");
|
|
});
|
|
|
|
expect(useFileStore.getState().entries[0]).toMatchObject({
|
|
file: replacementFile,
|
|
originalWidth: 320,
|
|
originalHeight: 240,
|
|
previewLoading: false,
|
|
});
|
|
expect(useFileStore.getState().entries[0].blobUrl).not.toBe("blob:decoded-a");
|
|
});
|
|
|
|
it("clears previewLoading when decoded preview returns null", async () => {
|
|
imagePreviewMock.needsServerPreview.mockReturnValue(true);
|
|
imagePreviewMock.fetchDecodedPreview.mockResolvedValue(null);
|
|
|
|
useFileStore.getState().setFiles([makeFile("a.heic", 100, "image/heic")]);
|
|
|
|
await vi.waitFor(() => {
|
|
expect(useFileStore.getState().entries[0].previewLoading).toBe(false);
|
|
});
|
|
expect(useFileStore.getState().entries[0].blobUrl).toBe("blob:url-1");
|
|
});
|
|
});
|