import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtempSync, writeFileSync, existsSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { load, save, add, has, complete, remove, format } from "../src/todo.js"; const tempFile = () => join(mkdtempSync(join(tmpdir(), "todo-")), "todos.json"); test("add appends an open todo", () => { const todos = add([], "buy milk"); assert.deepEqual(todos, [{ id: 1, text: "buy milk", done: false }]); }); test("add after a remove does not reuse an existing id", () => { const todos = add(remove(add(add([], "a"), "b"), 1), "c"); assert.deepEqual(todos.map((t) => t.id), [2, 3]); }); test("has reports whether an id exists", () => { const todos = add([], "a"); assert.equal(has(todos, 1), true); assert.equal(has(todos, 2), false); }); test("complete marks only the matching todo", () => { const todos = complete(add(add([], "a"), "b"), 2); assert.equal(todos[0].done, false); assert.equal(todos[1].done, true); }); test("remove drops the matching todo", () => { const todos = remove(add(add([], "a"), "b"), 1); assert.deepEqual(todos.map((t) => t.text), ["b"]); }); test("format renders an empty list", () => { assert.equal(format([]), "Nothing to do."); }); test("load returns an empty list for a missing file", () => { assert.deepEqual(load(tempFile()), []); }); test("save then load round-trips and leaves no temp file", () => { const file = tempFile(); const todos = add([], "a"); save(file, todos); assert.deepEqual(load(file), todos); assert.equal(existsSync(`${file}.tmp`), false); }); test("load rejects invalid JSON with a readable error", () => { const file = tempFile(); writeFileSync(file, "{ not json"); assert.throws(() => load(file), /Could not read .*todos\.json/); }); test("load rejects JSON that is not a list", () => { const file = tempFile(); writeFileSync(file, "{}"); assert.throws(() => load(file), /expected a list of todos/); });