test: add deterministic task behaviour tests
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
/.test-build
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
|
||||
@@ -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",
|
||||
|
||||
16
lib/db.ts
16
lib/db.ts
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
159
tests/tasks.test.cjs
Normal 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
19
tsconfig.test.json
Normal 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"]
|
||||
}
|
||||
Reference in New Issue
Block a user