diff --git a/src/domain/index.ts b/src/domain/index.ts index aec69f2..ab0b404 100644 --- a/src/domain/index.ts +++ b/src/domain/index.ts @@ -1 +1,11 @@ export const DOMAIN_READY = true; + +export * from "./types.ts"; +export * from "./money.ts"; +export * from "./months.ts"; +export * from "./fx.ts"; +export * from "./allocation.ts"; +export * from "./charges.ts"; +export * from "./fold.ts"; +export * from "./views.ts"; +export * from "./seed.ts"; diff --git a/src/domain/seed.test.ts b/src/domain/seed.test.ts new file mode 100644 index 0000000..891eaa2 --- /dev/null +++ b/src/domain/seed.test.ts @@ -0,0 +1,48 @@ +import { test, expect } from "bun:test"; +import { createSeedDataset, SCHEMA_VERSION, newId } from "./seed.ts"; +import { monthView } from "./views.ts"; + +test("seeds exactly the three named posts, in order", () => { + const data = createSeedDataset("2026-09"); + expect(data.posts.map((p) => p.name)).toEqual([ + "Video Games", + "Food", + "Events and Social", + ]); + expect(data.posts.map((p) => p.order)).toEqual([0, 1, 2]); +}); + +test("seeded posts are active with a zero fixed standing rule", () => { + const data = createSeedDataset("2026-09"); + for (const post of data.posts) { + expect(post.archived).toBe(false); + expect(post.standingRule).toEqual({ + kind: "fixed", + amount: { amount: 0, currency: "DKK" }, + }); + } +}); + +test("the fold starts at the given month and DKK is the base currency", () => { + const data = createSeedDataset("2026-09"); + expect(data.settings.foldStartMonth).toBe("2026-09"); + expect(data.settings.baseCurrency).toBe("DKK"); + expect(data.settings.schemaVersion).toBe(SCHEMA_VERSION); +}); + +test("seeds a month record with zero income and no purchases", () => { + const data = createSeedDataset("2026-09"); + expect(data.months.map((m) => m.id)).toEqual(["2026-09"]); + expect(data.purchases).toEqual([]); +}); + +test("the seed dataset renders a valid month view", () => { + const view = monthView(createSeedDataset("2026-09"), "2026-09"); + expect(view.rows).toHaveLength(3); + expect(view.income).toBe(0); + expect(view.unallocated).toBe(0); +}); + +test("newId returns distinct ids", () => { + expect(newId()).not.toBe(newId()); +}); diff --git a/src/domain/seed.ts b/src/domain/seed.ts new file mode 100644 index 0000000..8c63473 --- /dev/null +++ b/src/domain/seed.ts @@ -0,0 +1,38 @@ +import type { Dataset, MonthId, Post } from "./types.ts"; + +export const SCHEMA_VERSION = 1; + +const SEED_POST_NAMES = ["Video Games", "Food", "Events and Social"] as const; + +export function newId(): string { + return crypto.randomUUID(); +} + +/** + * The first-run dataset. Takes the start month as an argument because the + * domain layer must not read the ambient clock. + */ +export function createSeedDataset(startMonth: MonthId): Dataset { + const posts: Post[] = SEED_POST_NAMES.map((name, order) => ({ + id: newId(), + name, + order, + archived: false, + currency: "DKK", + standingRule: { kind: "fixed", amount: { amount: 0, currency: "DKK" } }, + })); + + return { + settings: { + baseCurrency: "DKK", + foldStartMonth: startMonth, + schemaVersion: SCHEMA_VERSION, + }, + fxRates: [], + posts, + months: [ + { id: startMonth, income: { amount: 0, currency: "DKK" }, ruleOverrides: {} }, + ], + purchases: [], + }; +}