import { describe, it, expect } from "vitest"; import { firstFileName, fileNames, hasFile } from "./file-fields"; /** * Regression guard for the "No signed copy on file" bug. * * `delivery_notes.signed_copy` is a PB file field with `maxSelect: 1`. * We WRITE it as an array (`signed_copy: [file]`), but PocketBase * RETURNS a bare STRING when exactly one file is attached. Verified * against the live record on DN26100403 after a real upload: * * typeof : string * Array.isArray : false * value : "image_ar52u2hjfq.jpg" * value[0] : "i" * * Every read site that assumed `string[]` therefore failed, and none * of them threw: `Array.isArray(x) ? x[0] : null` returns null, so the * app reported "No signed copy on file" for a note that had just been * signed, scanned and uploaded. `x[0]` in a tooltip returned the first * LETTER of the filename. One site called `.join(", ")` on the string, * which does throw. * * These tests pin the real shapes PB returns, so the string case can * never be quietly dropped again. */ describe("firstFileName", () => { it("reads a bare string — the shape PB actually returns for one file", () => { expect(firstFileName("image_ar52u2hjfq.jpg")).toBe("image_ar52u2hjfq.jpg"); }); it("reads an array, for the shape we WRITE or for several files", () => { expect(firstFileName(["a.jpg"])).toBe("a.jpg"); expect(firstFileName(["a.jpg", "b.jpg"])).toBe("a.jpg"); }); it("skips empty entries rather than returning ''", () => { expect(firstFileName(["", "b.jpg"])).toBe("b.jpg"); expect(firstFileName("")).toBeNull(); }); it("is null for nothing attached", () => { expect(firstFileName(null)).toBeNull(); expect(firstFileName(undefined)).toBeNull(); expect(firstFileName([])).toBeNull(); }); it("does NOT index into the string — the bug this replaces", () => { // `"image_x.jpg"[0]` is "i". Any read site doing that was showing // the first character of the filename as though it were the file. const value: string = "image_ar52u2hjfq.jpg"; expect(value[0]).toBe("i"); expect(firstFileName(value)).toBe("image_ar52u2hjfq.jpg"); }); }); describe("fileNames", () => { it("normalises both shapes to an array", () => { expect(fileNames("a.jpg")).toEqual(["a.jpg"]); expect(fileNames(["a.jpg", "b.jpg"])).toEqual(["a.jpg", "b.jpg"]); expect(fileNames(null)).toEqual([]); expect(fileNames([])).toEqual([]); }); it("would have thrown on the string form before — .join on a string", () => { // This is the deliveries page crash: `dn.signed_copy.join(", ")`. const value = "image_ar52u2hjfq.jpg" as unknown as string[]; expect(() => value.join(", ")).toThrow(); expect(fileNames("image_ar52u2hjfq.jpg").join(", ")).toBe("image_ar52u2hjfq.jpg"); }); }); describe("hasFile", () => { it("is truthy for either shape", () => { expect(hasFile("a.jpg")).toBe(true); expect(hasFile(["a.jpg"])).toBe(true); }); it("is falsy when empty", () => { expect(hasFile(null)).toBe(false); expect(hasFile("")).toBe(false); expect(hasFile([])).toBe(false); expect(hasFile(undefined)).toBe(false); }); });