const test = require("node:test"); const assert = require("node:assert/strict"); const Model = require("../assets/scripts/pages/resource-loader-model.js"); function resource(overrides = {}) { return { id: "resource-1", title: "A useful book", creators: ["Reader One"], type: "book", format: "physical", status: "active", folderId: null, tags: [], sessions: [], createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", ...overrides, }; } function library(resources = []) { return { schemaVersion: 1, vaults: [{ id: "vault-1", name: "Test vault", colour: "#123456", folders: [{ id: "folder-1", name: "Research" }], resources, createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-01T00:00:00.000Z", }], }; } test("seed contains the two initial empty vaults", () => { const seed = Model.createSeed(); assert.equal(Model.validateLibrary(seed), true); assert.deepEqual(seed.vaults.map((vault) => vault.name), ["Work", "Personal Study"]); assert.ok(seed.vaults.every((vault) => vault.resources.length === 0)); }); test("schema validation rejects unsupported and dangling records", () => { assert.throws(() => Model.validateLibrary({ schemaVersion: 2, vaults: [] }), /schemaVersion/); assert.throws(() => Model.validateLibrary(library([resource({ folderId: "missing" })])), /missing folder/); }); test("merge replaces matching ids and keeps unrelated records", () => { const current = library([resource(), resource({ id: "resource-2", title: "Keep me" })]); const incoming = library([resource({ title: "Imported title", status: "completed" })]); const merged = Model.mergeLibraries(current, incoming); assert.equal(merged.vaults[0].resources.length, 2); assert.equal(merged.vaults[0].resources.find((item) => item.id === "resource-1").title, "Imported title"); assert.ok(merged.vaults[0].resources.some((item) => item.id === "resource-2")); }); test("vault and folder operations preserve records safely", () => { let data = Model.addVault(library(), "Second", "#abcdef"); assert.equal(data.vaults.length, 2); data = Model.renameVault(data, data.vaults[1].id, "Renamed"); assert.equal(data.vaults[1].name, "Renamed"); data = Model.deleteVault(data, data.vaults[1].id); assert.equal(data.vaults.length, 1); data.vaults[0].resources.push(resource({ folderId: "folder-1" })); data = Model.deleteFolder(data, "vault-1", "folder-1"); assert.equal(data.vaults[0].folders.length, 0); assert.equal(data.vaults[0].resources[0].folderId, null); }); test("progress is derived for pages, minutes, and article percentages", () => { assert.equal(Model.progressFor(resource({ pageCount: 200, sessions: [{ end: 80 }] })).percent, 40); assert.equal(Model.progressFor(resource({ type: "video", format: "web", durationMinutes: 60, sessions: [{ end: 45 }] })).percent, 75); assert.equal(Model.progressFor(resource({ type: "article", format: "web", sessions: [{ end: 35 }] })).percent, 35); assert.equal(Model.progressFor(resource({ status: "completed" })).percent, 100); }); test("serialization round-trips metadata and marks PDF reattachment", () => { const parsed = JSON.parse(Model.serialise(library([resource({ format: "pdf", attachmentId: "blob-1" })]))); assert.equal(Model.validateLibrary(parsed), true); assert.equal(parsed.vaults[0].resources[0].attachmentState, "reattach-required-after-import"); assert.ok(parsed.exportedAt); });