feat: add month breakdown and session backups
Continuous Integration / lint-and-test (push) Successful in 17s
Continuous Integration / docker-build-and-push (push) Successful in 35s

Allow recurring costs and confirmations to remain unassigned when created from a purchase without a post. Add browser backup retention and restore controls, with coverage and documentation updates.
This commit is contained in:
Lucas Winther
2026-10-01 04:01:49 +02:00
parent 019517e6e9
commit 7cccd7a41a
32 changed files with 640 additions and 41 deletions
+20 -2
View File
@@ -54,6 +54,24 @@ describe("round trip", () => {
});
describe("validation", () => {
test("accepts an unassigned recurring cost and its unassigned charge", () => {
const data = populated();
const cost = addRecurringCost(data, {
name: "Phone", folderId: null, archived: false,
amount: { amount: 30, currency: "DKK" }, startDate: "2026-01",
recurrence: { kind: "everyNMonths", n: 1 }, anchoring: "calendar",
splitMode: "percent", splits: [],
});
data.purchases.push({
id: "phone-charge", date: "2026-01", description: "Phone",
total: { amount: 30, currency: "DKK" }, splitMode: "percent", splits: [],
schedule: null, source: { recurringId: cost.id, occurrenceDate: "2026-01" },
});
const restored = parseDatasetJson(exportDatasetJson(data));
expect(restored.recurring[0]!.splits).toEqual([]);
expect(restored.purchases.at(-1)!.splits).toEqual([]);
expect(() => monthView(restored, "2026-01")).not.toThrow();
});
test("accepts a purchase with no post and preserves it through export", () => {
const data = populated();
data.purchases[0]!.splits = [];
@@ -553,8 +571,8 @@ describe("recurring cost validation", () => {
).toThrow(/unknown post/);
});
test("no splits at all is refused", () => {
expect(() => parseDatasetJson(withRecurring([{ ...valid, splits: [] }])))
test("a missing splits array is refused", () => {
expect(() => parseDatasetJson(withRecurring([{ ...valid, splits: null }])))
.toThrow(/no splits/);
});