2026-09-16 08:58:16 +00:00
|
|
|
import { describe, it, expect } from "vitest";
|
|
|
|
|
import { describeActivity } from "@/lib/activity/format";
|
|
|
|
|
import type { ActivityRecord } from "@/lib/pb/types";
|
|
|
|
|
|
|
|
|
|
function row(overrides: Partial<ActivityRecord>): ActivityRecord {
|
|
|
|
|
return {
|
|
|
|
|
id: "a1",
|
|
|
|
|
created: "2026-09-16 10:00:00.000Z",
|
|
|
|
|
project: "p1",
|
|
|
|
|
card: "c1",
|
|
|
|
|
actor: "u1",
|
|
|
|
|
action: "card_updated",
|
|
|
|
|
field: "",
|
|
|
|
|
old_value: "",
|
|
|
|
|
new_value: "",
|
|
|
|
|
...overrides,
|
|
|
|
|
} as ActivityRecord;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe("describeActivity", () => {
|
|
|
|
|
it("describes a priority change in words, not codes", () => {
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(
|
|
|
|
|
row({ action: "priority_changed", field: "priority", old_value: "medium", new_value: "high" })
|
|
|
|
|
)
|
|
|
|
|
).toBe("changed priority from Medium to High");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("describes setting a target date that had none", () => {
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(
|
|
|
|
|
row({ action: "date_changed", field: "target_date", old_value: "", new_value: "2026-10-01" })
|
|
|
|
|
)
|
|
|
|
|
).toBe("set the target date to 1 Oct 2026");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("describes moving a target date", () => {
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(
|
|
|
|
|
row({
|
|
|
|
|
action: "date_changed",
|
|
|
|
|
field: "target_date",
|
|
|
|
|
old_value: "2026-10-01 00:00:00.000Z",
|
|
|
|
|
new_value: "2026-11-15",
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
).toBe("moved the target date from 1 Oct 2026 to 15 Nov 2026");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("describes clearing a target date", () => {
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(
|
|
|
|
|
row({ action: "date_changed", field: "target_date", old_value: "2026-10-01", new_value: "" })
|
|
|
|
|
)
|
|
|
|
|
).toBe("cleared the target date");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("describes card creation, acceptance and archiving", () => {
|
|
|
|
|
expect(describeActivity(row({ action: "card_created", new_value: "Add a POS system" })))
|
|
|
|
|
.toBe("added this card");
|
|
|
|
|
expect(describeActivity(row({ action: "card_accepted" }))).toBe("accepted this request");
|
|
|
|
|
expect(describeActivity(row({ action: "card_archived" }))).toBe("archived this card");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("names the destination when a card moves", () => {
|
|
|
|
|
expect(describeActivity(row({ action: "card_moved", new_value: "In Progress" })))
|
|
|
|
|
.toBe("moved this card to In Progress");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("falls back gracefully when a move has no destination recorded", () => {
|
|
|
|
|
expect(describeActivity(row({ action: "card_moved", new_value: "" })))
|
|
|
|
|
.toBe("moved this card");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("describes comments and subtasks", () => {
|
|
|
|
|
expect(describeActivity(row({ action: "comment_added" }))).toBe("commented");
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(row({ action: "subtask_toggled", new_value: "true", field: "Wire up the till" }))
|
|
|
|
|
).toBe("completed “Wire up the till”");
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(row({ action: "subtask_toggled", new_value: "false", field: "Wire up the till" }))
|
|
|
|
|
).toBe("reopened “Wire up the till”");
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-19 16:35:33 +00:00
|
|
|
it("describes a column move with the new column name", () => {
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(
|
|
|
|
|
row({ action: "column_changed", field: "column", old_value: "col_done", new_value: "Backlog" })
|
|
|
|
|
)
|
|
|
|
|
).toBe("moved this card to Backlog");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("describes a column move out of a column when only the old name is known", () => {
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(
|
|
|
|
|
row({ action: "column_changed", field: "column", old_value: "Done", new_value: "" })
|
|
|
|
|
)
|
|
|
|
|
).toBe("moved this card out of Done");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("describes a client proposal in human terms", () => {
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(
|
|
|
|
|
row({
|
|
|
|
|
action: "date_proposed",
|
|
|
|
|
field: "proposed_target_date",
|
|
|
|
|
old_value: "",
|
|
|
|
|
new_value: "2026-11-15",
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
).toBe("proposed moving this card to 15 Nov 2026");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("describes an accepted proposal with its date", () => {
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(
|
|
|
|
|
row({
|
|
|
|
|
action: "date_proposal_resolved",
|
|
|
|
|
field: "proposed_target_date",
|
|
|
|
|
old_value: "2026-11-15",
|
|
|
|
|
new_value: "accepted",
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
).toBe("accepted the proposal for 15 Nov 2026");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("describes a dismissed proposal with its date", () => {
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(
|
|
|
|
|
row({
|
|
|
|
|
action: "date_proposal_resolved",
|
|
|
|
|
field: "proposed_target_date",
|
|
|
|
|
old_value: "2026-11-15",
|
|
|
|
|
new_value: "dismissed",
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
).toBe("dismissed the proposal for 15 Nov 2026");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("falls back gracefully when a resolved proposal has no date", () => {
|
|
|
|
|
expect(
|
|
|
|
|
describeActivity(
|
|
|
|
|
row({
|
|
|
|
|
action: "date_proposal_resolved",
|
|
|
|
|
field: "proposed_target_date",
|
|
|
|
|
old_value: "",
|
|
|
|
|
new_value: "accepted",
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
).toBe("accepted a proposal");
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-16 08:58:16 +00:00
|
|
|
it("describes a generic update", () => {
|
|
|
|
|
expect(describeActivity(row({ action: "card_updated", field: "description" })))
|
|
|
|
|
.toBe("updated the description");
|
|
|
|
|
expect(describeActivity(row({ action: "card_updated", field: "title" })))
|
|
|
|
|
.toBe("renamed this card");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("never returns an empty string for an unknown action", () => {
|
|
|
|
|
// Double assertion on purpose: the point is to simulate an action value
|
|
|
|
|
// added by a later phase that this switch has not learned yet.
|
|
|
|
|
const unknown = "something_new" as unknown as ActivityRecord["action"];
|
|
|
|
|
const result = describeActivity(row({ action: unknown }));
|
|
|
|
|
expect(result.length).toBeGreaterThan(0);
|
|
|
|
|
});
|
|
|
|
|
});
|