test: add deterministic task behaviour tests

This commit is contained in:
Mpho10111
2026-08-11 21:15:21 +02:00
parent 75797c8419
commit 67404c2de2
6 changed files with 204 additions and 1 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@
# testing
/coverage
/.test-build
# next.js
/.next/

View File

@@ -5,10 +5,17 @@ import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
{
files: ["tests/**/*.cjs"],
rules: {
"@typescript-eslint/no-require-imports": "off",
},
},
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
".test-build/**",
"out/**",
"build/**",
"next-env.d.ts",

View File

@@ -21,6 +21,12 @@ export function getDatabase() {
return globalDatabase.plannerDatabase;
}
if (globalDatabase.plannerDatabase) {
globalDatabase.plannerDatabase.close();
delete globalDatabase.plannerDatabase;
delete globalDatabase.plannerDatabasePath;
}
fs.mkdirSync(path.dirname(databasePath), { recursive: true });
const database = new DatabaseSync(databasePath);
@@ -34,3 +40,13 @@ export function getDatabase() {
return database;
}
export function closeDatabaseForTests() {
const globalDatabase = globalThis as DatabaseGlobal;
if (globalDatabase.plannerDatabase) {
globalDatabase.plannerDatabase.close();
delete globalDatabase.plannerDatabase;
delete globalDatabase.plannerDatabasePath;
}
}

View File

@@ -6,7 +6,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
"lint": "eslint",
"test": "tsc -p tsconfig.test.json && node --test --test-concurrency=1 --test-isolation=none tests/tasks.test.cjs"
},
"dependencies": {
"next": "16.3.0",

159
tests/tasks.test.cjs Normal file
View File

@@ -0,0 +1,159 @@
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const test = require("node:test");
const {
archiveTask,
createTask,
getTask,
listTasks,
updateTask,
} = require("../.test-build/lib/tasks.js");
const { closeDatabaseForTests } = require("../.test-build/lib/db.js");
function useThrowawayDatabase(t) {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), "planner-tests-"));
const databasePath = path.join(directory, "tasks.sqlite");
process.env.TASKS_DB_PATH = databasePath;
t.after(() => {
closeDatabaseForTests();
delete process.env.TASKS_DB_PATH;
fs.rmSync(directory, { recursive: true, force: true });
});
return databasePath;
}
test("creating a task persists all four required fields", (t) => {
useThrowawayDatabase(t);
const created = createTask({
title: "Finish Lab 1",
description: "Wire the planner to SQLite",
dueDate: "2026-08-20",
topic: "Academic",
});
const reloaded = getTask(created.id);
assert.equal(reloaded.title, "Finish Lab 1");
assert.equal(reloaded.description, "Wire the planner to SQLite");
assert.equal(reloaded.dueDate, "2026-08-20");
assert.equal(reloaded.topic, "Academic");
assert.equal(reloaded.status, "Todo");
assert.equal(reloaded.archivedAt, null);
});
test("editing a task persists the changed fields and status", (t) => {
useThrowawayDatabase(t);
const created = createTask({
title: "Draft notes",
description: "Initial wording",
dueDate: "2026-08-21",
topic: "Academic",
});
const edited = updateTask(created.id, {
title: "Submit notes",
description: "Final wording",
dueDate: "2026-08-22",
topic: "Work",
status: "In-Progress",
});
const reloaded = getTask(created.id);
assert.equal(edited.title, "Submit notes");
assert.equal(reloaded.description, "Final wording");
assert.equal(reloaded.dueDate, "2026-08-22");
assert.equal(reloaded.topic, "Work");
assert.equal(reloaded.status, "In-Progress");
});
test("archiving marks a task archived without deleting it", (t) => {
useThrowawayDatabase(t);
const created = createTask({
title: "Old task",
description: "Keep for records",
dueDate: "2026-08-23",
topic: "Personal",
});
const archived = archiveTask(created.id);
const activeTasks = listTasks();
const allTasks = listTasks({ includeArchived: true });
assert.equal(typeof archived.archivedAt, "string");
assert.equal(getTask(created.id).id, created.id);
assert.equal(activeTasks.some((task) => task.id === created.id), false);
assert.equal(allTasks.some((task) => task.id === created.id), true);
});
test("overdue is derived from due date and is not stored as a status", (t) => {
useThrowawayDatabase(t);
const overdue = createTask({
title: "Past task",
description: "This date has passed",
dueDate: "2000-01-01",
topic: "Work",
});
const future = createTask({
title: "Future task",
description: "This date has not passed",
dueDate: "2999-01-01",
topic: "Work",
});
const completed = updateTask(overdue.id, { status: "Complete" });
assert.equal(overdue.status, "Todo");
assert.equal(overdue.isOverdue, true);
assert.equal(future.isOverdue, false);
assert.equal(completed.status, "Complete");
assert.equal(completed.isOverdue, false);
assert.notEqual(completed.status, "Overdue");
});
test("sorting by topic, status, and due date is deterministic", (t) => {
useThrowawayDatabase(t);
const work = createTask({
title: "Work item",
description: "Second topic alphabetically",
dueDate: "2026-09-03",
topic: "Work",
});
const academic = createTask({
title: "Academic item",
description: "First topic alphabetically",
dueDate: "2026-09-02",
topic: "Academic",
});
const personal = createTask({
title: "Personal item",
description: "Earliest due date",
dueDate: "2026-09-01",
topic: "Personal",
});
updateTask(work.id, { status: "Complete" });
updateTask(academic.id, { status: "In-Progress" });
assert.deepEqual(
listTasks({ sort: "topic" }).map((task) => task.topic),
["Academic", "Personal", "Work"]
);
assert.deepEqual(
listTasks({ sort: "status" }).map((task) => task.status),
["Todo", "In-Progress", "Complete"]
);
assert.deepEqual(
listTasks({ sort: "dueDate" }).map((task) => task.id),
[personal.id, academic.id, work.id]
);
});

19
tsconfig.test.json Normal file
View File

@@ -0,0 +1,19 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"allowJs": false,
"baseUrl": ".",
"incremental": false,
"module": "CommonJS",
"moduleResolution": "node",
"noEmit": false,
"outDir": ".test-build",
"paths": {
"@/*": ["./*"]
},
"target": "ES2022",
"tsBuildInfoFile": ".test-build/tsconfig.test.tsbuildinfo"
},
"include": ["lib/**/*.ts", "types/**/*.ts"],
"exclude": ["node_modules", ".next", ".test-build"]
}