Files

129 lines
5.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import type { ChangeEvent } from "../src/api.ts";
import { changeValue, describeChange, groupByRun, logMonth, scheduleLabel, SCHEDULES } from "../src/labels.ts";
const base: ChangeEvent = {
id: "ev1",
type: "changed",
status: "pending",
connectionId: "c1",
runId: "r1",
targetEntity: "entry",
targetId: "e1",
payload: {},
createdAt: "2026-10-30T10:00:00.000Z",
title: "Piranesi",
};
const sources = [
{ id: "c1", pluginId: "hardcover", name: "Hardcover" },
{ id: "c2", pluginId: "trakt", name: "Trakt" },
];
describe("describeChange", () => {
test("removed", () => {
const d = describeChange({ ...base, type: "removed", payload: { key: "b:1", kind: "entry" } }, sources);
expect(d.message).toBe("Hardcover no longer has this. Your copy here is safe.");
expect(d.actions).toEqual([
{ action: "accept", label: "Delete here too" },
{ action: "ignore", label: "Keep it" },
]);
});
test("merged, with and without the target title", () => {
const ev = { ...base, type: "merged", payload: { key: "b:1", intoKey: "b:2" }, intoTitle: "Piranesi (Deluxe)" };
const d = describeChange(ev, sources);
expect(d.message).toBe("Hardcover merged this into Piranesi (Deluxe).");
expect(d.actions).toEqual([
{ action: "accept", label: "Follow the merge" },
{ action: "ignore", label: "Ignore" },
]);
expect(describeChange({ ...ev, intoTitle: null }, sources).message).toBe("Hardcover merged this into another entry.");
});
test("conflict shows both sources' values", () => {
const d = describeChange(
{
...base,
type: "conflict",
connectionId: "c2",
payload: { field: "rating", current: 80, currentSource: "plugin:hardcover:c1", incoming: 60, incomingSource: "plugin:trakt:c2" },
},
sources,
);
expect(d.message).toBe("Trakt and Hardcover disagree about the rating.");
expect(d.field).toBe("rating");
expect(d.before).toEqual({ label: "Hardcover", value: 80 });
expect(d.after).toEqual({ label: "Trakt", value: 60 });
expect(d.actions).toEqual([
{ action: "accept", label: "Use the new value" },
{ action: "keep_mine", label: "Keep mine" },
{ action: "ignore", label: "Ignore" },
]);
});
test("changed shows yours against the source's", () => {
const d = describeChange({ ...base, payload: { field: "status", from: "completed", to: "in_progress" } }, sources);
expect(d.message).toBe("Hardcover changed the status.");
expect(d.before).toEqual({ label: "Yours", value: "completed" });
expect(d.after).toEqual({ label: "Hardcover", value: "in_progress" });
expect(d.actions.map((a) => a.label)).toEqual(["Use the new value", "Keep mine", "Ignore"]);
});
test("an unknown source still reads well", () => {
const d = describeChange({ ...base, type: "removed", connectionId: null, payload: {} }, []);
expect(d.message).toBe("A source no longer has this. Your copy here is safe.");
});
});
describe("scheduleLabel", () => {
test("the four choices", () => {
expect(scheduleLabel(null)).toBe("Off");
expect(scheduleLabel(60)).toBe("Every hour");
expect(scheduleLabel(360)).toBe("Every 6 hours");
expect(scheduleLabel(1440)).toBe("Daily");
expect(SCHEDULES).toEqual([null, 60, 360, 1440]);
});
test("other intervals", () => {
expect(scheduleLabel(30)).toBe("Every 30 minutes");
expect(scheduleLabel(120)).toBe("Every 2 hours");
expect(scheduleLabel(2880)).toBe("Every 2 days");
});
});
describe("groupByRun", () => {
test("keeps newest-first order and groups events of one run", () => {
const g = groupByRun([
{ ...base, id: "3", runId: "rB" },
{ ...base, id: "2", runId: "rA" },
{ ...base, id: "1", runId: "rB" },
]);
expect(g.map((x) => [x.runId, x.events.map((e) => e.id)])).toEqual([
["rB", ["3", "1"]],
["rA", ["2"]],
]);
});
});
describe("changeValue", () => {
test("values read in the user's terms", () => {
expect(changeValue("rating", 80, "5half")).toBe("★★★★☆");
expect(changeValue("rating", 85, "10")).toBe("8.5/10");
expect(changeValue("status", "in_progress", "5half", "book")).toBe("Reading");
expect(changeValue("finishedAt", { value: "2025-03", precision: "month" }, "5half")).toBe("Mar 2025");
expect(changeValue("tags", ["sci-fi", "re-read"], "5half")).toBe("sci-fi, re-read");
expect(changeValue("favorite", true, "5half")).toBe("Yes");
expect(changeValue("rating", null, "5half")).toBe("Nothing");
expect(changeValue("tags", [], "5half")).toBe("Nothing");
expect(changeValue("review", "x".repeat(300), "5half")).toHaveLength(160);
});
});
describe("logMonth", () => {
test("month headings from partial dates and timestamps", () => {
expect(logMonth("2026-10-01")).toBe("October 2026");
expect(logMonth("2025-03")).toBe("March 2025");
expect(logMonth("2019")).toBe("2019");
expect(logMonth("2026-01-31T23:59:00.000Z")).toBe("January 2026");
});
});