- Tags and custom fields update the cached entry optimistically and build from the latest cache; one entry's PATCHes run in order (mutation scope). - The review textarea follows server changes (Restore) unless edited. - friendlyError() maps validation reasons and 413 to plain copy everywhere. - Rating scale from me/preferences; /login and /register use auth/status; Attachments lists from the API. - Gelasio vendored (official OFL variable TTFs); minors from the review. - Docs: DESIGN (AA on orange, faint labels), DECISIONS, TODO, ARCHITECTURE. Co-Authored-By: Claude Opus 5.5 <[email protected]>
45 lines
2.1 KiB
TypeScript
45 lines
2.1 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { addTag, applyEntryPatch, orderEngagements, removeTag, setCustomValue } from "../src/edits.ts";
|
|
|
|
describe("entry edit builders", () => {
|
|
test("two quick tag adds built from the latest cache keep both", () => {
|
|
// Each edit reads the cache as left by the previous (optimistic) edit, not the render's props.
|
|
let cache = { tags: [] as string[], rating: null as number | null };
|
|
const edit = (build: (cur: typeof cache) => Partial<typeof cache>) => (cache = applyEntryPatch(cache, build(cache)));
|
|
edit((cur) => ({ tags: addTag(cur.tags, "cosmic horror") }));
|
|
edit((cur) => ({ tags: addTag(cur.tags, "re-read") }));
|
|
expect(cache.tags).toEqual(["cosmic horror", "re-read"]);
|
|
});
|
|
|
|
test("addTag trims and ignores case duplicates; removeTag removes one", () => {
|
|
expect(addTag(["Horror"], " horror ")).toEqual(["Horror"]);
|
|
expect(addTag(["a"], " ")).toEqual(["a"]);
|
|
expect(removeTag(["a", "b"], "a")).toEqual(["b"]);
|
|
});
|
|
|
|
test("setCustomValue sets, and removes empty values", () => {
|
|
expect(setCustomValue({ a: 1 }, "b", "x")).toEqual({ a: 1, b: "x" });
|
|
expect(setCustomValue({ a: 1, b: "x" }, "b", "")).toEqual({ a: 1 });
|
|
expect(setCustomValue({ a: [1] }, "a", [])).toEqual({});
|
|
expect(setCustomValue({ a: 1 }, "a", null)).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe("orderEngagements", () => {
|
|
const g = (id: string, createdAt: string, finished?: string, started?: string) => ({
|
|
id,
|
|
createdAt,
|
|
finishedAt: finished ? { value: finished, precision: "day" as const } : null,
|
|
startedAt: started ? { value: started, precision: "day" as const } : null,
|
|
});
|
|
test("sorts by finishedAt ?? startedAt, then createdAt; undated first", () => {
|
|
const list = [
|
|
g("late", "2026-01-01T00:00:00Z", "2026-09-14"),
|
|
g("early-logged-later", "2026-10-01T00:00:00Z", "2025-03"),
|
|
g("started-only", "2026-02-01T00:00:00Z", undefined, "2025-06-01"),
|
|
g("undated", "2026-03-01T00:00:00Z"),
|
|
];
|
|
expect(orderEngagements(list).map((x) => x.id)).toEqual(["undated", "early-logged-later", "started-only", "late"]);
|
|
});
|
|
});
|