mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 21:27:02 +01:00
- dufs-client.test.ts: type fetch mocks as vi.fn<typeof fetch>() (fixes the TS2493/TS2352 tuple/undefined build errors that broke `pnpm build`) - dufs-client.ts: explicit method arg drops the unreachable `?? "GET"` branch - gallery/grid: -1 viewer-index sentinel + pass media entry, removing three unreachable defensive guards; onOpenMedia now takes the entry - use-hash-path: extract serverPath() so the SSR snapshot is covered - eslint: strict+stylistic type-checked, react-hooks, reportUnusedDisable Directives; drop eslint-plugin-react (incompatible with ESLint 10) - add grid.test.tsx + use-listing.test.ts; coverage thresholds 100/100/100/100 (62 tests: statements/branches/functions/lines all 100%) - phone_focus_mode/config.sh: whitelist com.kuhy.dufs_client Deployed live via setup_cloud_gallery.sh (render-spa); fixed ffmpeg by renaming a stale /usr/local/bin shadow linking the removed libvpx.so.11; regenerated thumbnails (10 made, 0 failed). APK installed + launch-verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0126QvdMrv2WNeUVShFiLTc3
135 lines
4.8 KiB
TypeScript
135 lines
4.8 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import {
|
|
createDufsClient,
|
|
parsePropfind,
|
|
sortEntries,
|
|
} from "./dufs-client.ts";
|
|
import type { DirEntry } from "./types.ts";
|
|
|
|
const PROPFIND_XML = `<?xml version="1.0" encoding="utf-8"?>
|
|
<D:multistatus xmlns:D="DAV:">
|
|
<D:response>
|
|
<D:href>/Media/2026/07/</D:href>
|
|
<D:propstat><D:prop><D:resourcetype><D:collection/></D:resourcetype></D:prop></D:propstat>
|
|
</D:response>
|
|
<D:response>
|
|
<D:href>/Media/2026/07/sub/</D:href>
|
|
<D:propstat><D:prop>
|
|
<D:resourcetype><D:collection/></D:resourcetype>
|
|
<D:getlastmodified>Sat, 12 Jul 2026 03:00:00 GMT</D:getlastmodified>
|
|
</D:prop></D:propstat>
|
|
</D:response>
|
|
<D:response>
|
|
<D:href>/Media/2026/07/a%20b.jpg</D:href>
|
|
<D:propstat><D:prop>
|
|
<D:resourcetype/>
|
|
<D:getcontentlength>2048</D:getcontentlength>
|
|
<D:getlastmodified>Sat, 12 Jul 2026 03:00:00 GMT</D:getlastmodified>
|
|
</D:prop></D:propstat>
|
|
</D:response>
|
|
<D:response><D:href></D:href></D:response>
|
|
</D:multistatus>`;
|
|
|
|
describe("parsePropfind", () => {
|
|
it("parses entries, skips self, decodes names, reads size/mtime", () => {
|
|
const entries = parsePropfind(PROPFIND_XML, "/Media/2026/07");
|
|
// self ("/Media/2026/07/") and the empty-href entry are dropped
|
|
expect(entries.map((e) => e.name)).toEqual(["sub", "a b.jpg"]);
|
|
const file = entries.find((e) => e.name === "a b.jpg");
|
|
expect(file?.kind).toBe("file");
|
|
expect(file?.size).toBe(2048);
|
|
expect(file?.mtimeMs).toBeGreaterThan(0);
|
|
expect(entries.find((e) => e.name === "sub")?.kind).toBe("dir");
|
|
});
|
|
it("handles missing size/mtime as zero", () => {
|
|
const xml = `<D:multistatus xmlns:D="DAV:"><D:response><D:href>/x.bin</D:href><D:propstat><D:prop><D:resourcetype/></D:prop></D:propstat></D:response></D:multistatus>`;
|
|
const [entry] = parsePropfind(xml, "/");
|
|
expect(entry?.size).toBe(0);
|
|
expect(entry?.mtimeMs).toBe(0);
|
|
});
|
|
it("coerces an unparsable size/mtime to zero", () => {
|
|
const xml = `<D:multistatus xmlns:D="DAV:"><D:response><D:href>/y.bin</D:href><D:propstat><D:prop><D:resourcetype/><D:getcontentlength>not-a-number</D:getcontentlength><D:getlastmodified>never</D:getlastmodified></D:prop></D:propstat></D:response></D:multistatus>`;
|
|
const [entry] = parsePropfind(xml, "/");
|
|
expect(entry?.size).toBe(0);
|
|
expect(entry?.mtimeMs).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe("sortEntries", () => {
|
|
it("dirs first then case-insensitive name", () => {
|
|
const mk = (name: string, kind: "dir" | "file"): DirEntry => ({
|
|
name,
|
|
path: `/${name}`,
|
|
kind,
|
|
size: 0,
|
|
mtimeMs: 0,
|
|
});
|
|
const sorted = sortEntries([
|
|
mk("banana.jpg", "file"),
|
|
mk("Apple", "dir"),
|
|
mk("apple.png", "file"),
|
|
mk("Zeta", "dir"),
|
|
]);
|
|
expect(sorted.map((e) => e.name)).toEqual([
|
|
"Apple",
|
|
"Zeta",
|
|
"apple.png",
|
|
"banana.jpg",
|
|
]);
|
|
});
|
|
});
|
|
|
|
function jsonResponse(body: string, ok = true, status = 200): Response {
|
|
return {
|
|
ok,
|
|
status,
|
|
text: () => Promise.resolve(body),
|
|
} as unknown as Response;
|
|
}
|
|
|
|
describe("createDufsClient", () => {
|
|
it("list() PROPFINDs, sorts, and returns entries", async () => {
|
|
const fetchImpl = vi.fn<typeof fetch>(() =>
|
|
Promise.resolve(jsonResponse(PROPFIND_XML)),
|
|
);
|
|
const client = createDufsClient(fetchImpl);
|
|
const entries = await client.list("/Media/2026/07");
|
|
const firstCall = fetchImpl.mock.calls.at(0);
|
|
expect(firstCall?.[0]).toBe("/Media/2026/07");
|
|
expect(firstCall?.[1]?.method).toBe("PROPFIND");
|
|
expect(entries.length).toBe(2);
|
|
});
|
|
|
|
it("builds file and thumbnail URLs (encoded)", () => {
|
|
const client = createDufsClient(vi.fn<typeof fetch>());
|
|
expect(client.fileUrl("/Media/a b.jpg")).toBe("/Media/a%20b.jpg");
|
|
expect(client.thumbUrl("/Media/a b.jpg")).toBe(
|
|
"/.thumbs/Media/a%20b.jpg.jpg",
|
|
);
|
|
});
|
|
|
|
it("upload PUTs to dir/name, remove DELETEs, writeText PUTs, readText GETs", async () => {
|
|
const fetchImpl = vi.fn<typeof fetch>(() =>
|
|
Promise.resolve(jsonResponse("content")),
|
|
);
|
|
const client = createDufsClient(fetchImpl);
|
|
const file = new File(["x"], "n.txt");
|
|
await client.upload("/dir", file);
|
|
await client.remove("/dir/n.txt");
|
|
await client.writeText("/dir/n.txt", "hi");
|
|
const text = await client.readText("/dir/n.txt");
|
|
expect(text).toBe("content");
|
|
const methods = fetchImpl.mock.calls.map((c) => c[1]?.method);
|
|
expect(methods).toEqual(["PUT", "DELETE", "PUT", "GET"]);
|
|
expect(fetchImpl.mock.calls.at(0)?.[0]).toBe("/dir/n.txt");
|
|
});
|
|
|
|
it("throws on non-ok responses", async () => {
|
|
const fetchImpl = vi.fn<typeof fetch>(() =>
|
|
Promise.resolve(jsonResponse("", false, 404)),
|
|
);
|
|
const client = createDufsClient(fetchImpl);
|
|
await expect(client.remove("/nope")).rejects.toThrow("404");
|
|
});
|
|
});
|