87 lines
3.1 KiB
TypeScript
87 lines
3.1 KiB
TypeScript
|
|
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);
|
||
|
|
});
|
||
|
|
});
|