120 lines
4.6 KiB
TypeScript
120 lines
4.6 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import type { ChangeEvent } from "../src/api.ts";
|
|
import { changeValue, describeChange, groupByRun, 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);
|
|
});
|
|
});
|