test: update tests, add eslint rules
This commit is contained in:
@@ -4,11 +4,18 @@ import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { InMemoryFileSystem } from "../../tests/helpers/InMemoryFileSystem";
|
||||
import { FsTaskRepository } from "./FsTaskRepository";
|
||||
|
||||
const UUID_RE =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
describe("FsTaskRepository", () => {
|
||||
let fs: InMemoryFileSystem;
|
||||
let repo: ITaskRepository;
|
||||
let clockStep: number;
|
||||
|
||||
const folder = "/tasks";
|
||||
const t0 = "2026-01-01T00:00:00.000Z";
|
||||
const t1 = "2026-01-01T00:00:01.000Z";
|
||||
const t2 = "2026-01-01T00:00:02.000Z";
|
||||
|
||||
const sampleTask = {
|
||||
title: "Fix login bug",
|
||||
@@ -21,35 +28,41 @@ describe("FsTaskRepository", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fs = new InMemoryFileSystem();
|
||||
repo = new FsTaskRepository(fs, ".task.md");
|
||||
clockStep = 0;
|
||||
const timestamps = [t0, t1, t2];
|
||||
repo = new FsTaskRepository(fs, ".task.md", () => {
|
||||
const value = timestamps[clockStep] ?? t2;
|
||||
clockStep += 1;
|
||||
return value;
|
||||
});
|
||||
});
|
||||
|
||||
it("creates a task and writes it as a .task.md file", async () => {
|
||||
const task = await repo.create(folder, sampleTask);
|
||||
|
||||
expect(task.id).toBeTruthy();
|
||||
expect(task.id).toMatch(UUID_RE);
|
||||
expect(task.title).toBe("Fix login bug");
|
||||
expect(task.created).toBeTruthy();
|
||||
expect(task.updated).toBe(task.created);
|
||||
expect(task.created).toBe(t0);
|
||||
expect(task.updated).toBe(t0);
|
||||
|
||||
const files = await fs.readdir(folder);
|
||||
expect(files).toHaveLength(1);
|
||||
expect(files[0]).toMatch(/\.task\.md$/);
|
||||
expect(files).toEqual(["fix-login-bug.task.md"]);
|
||||
|
||||
const content = await fs.readFile(`${folder}/${files[0]}`);
|
||||
const content = await fs.readFile(`${folder}/fix-login-bug.task.md`);
|
||||
expect(content).toContain("Fix login bug");
|
||||
expect(content).toContain("status: todo");
|
||||
});
|
||||
|
||||
it("list returns created tasks sorted by updated desc", async () => {
|
||||
await repo.create(folder, { ...sampleTask, title: "A" });
|
||||
await delay(10);
|
||||
await repo.create(folder, { ...sampleTask, title: "B" });
|
||||
|
||||
const tasks = await repo.list(folder);
|
||||
expect(tasks).toHaveLength(2);
|
||||
expect(tasks[0].title).toBe("B");
|
||||
expect(tasks[0].updated).toBe(t1);
|
||||
expect(tasks[1].title).toBe("A");
|
||||
expect(tasks[1].updated).toBe(t0);
|
||||
});
|
||||
|
||||
it("getById returns the correct task", async () => {
|
||||
@@ -63,19 +76,50 @@ describe("FsTaskRepository", () => {
|
||||
expect(found).toBeUndefined();
|
||||
});
|
||||
|
||||
it("update modifies the file and updates timestamp", async () => {
|
||||
it("update modifies fields and bumps updated timestamp", async () => {
|
||||
const task = await repo.create(folder, sampleTask);
|
||||
await delay(5);
|
||||
const updated: Task = { ...task, title: "Fixed!", status: "done" };
|
||||
const updated: Task = { ...task, status: "done" };
|
||||
|
||||
await repo.update(folder, updated);
|
||||
const found = await repo.getById(folder, task.id);
|
||||
|
||||
expect(found!.title).toBe("Fixed!");
|
||||
expect(found!.status).toBe("done");
|
||||
expect(new Date(found!.updated).getTime()).toBeGreaterThan(
|
||||
new Date(task.updated).getTime(),
|
||||
);
|
||||
expect(found!.title).toBe(task.title);
|
||||
expect(found!.updated).toBe(t1);
|
||||
expect(found!.created).toBe(t0);
|
||||
});
|
||||
|
||||
it("update renames file when title changes", async () => {
|
||||
const task = await repo.create(folder, sampleTask);
|
||||
expect(await fs.readdir(folder)).toEqual(["fix-login-bug.task.md"]);
|
||||
|
||||
await repo.update(folder, { ...task, title: "Fixed!" });
|
||||
|
||||
const files = await fs.readdir(folder);
|
||||
expect(files).toEqual(["fixed.task.md"]);
|
||||
const found = await repo.getById(folder, task.id);
|
||||
expect(found!.title).toBe("Fixed!");
|
||||
expect(found!.updated).toBe(t1);
|
||||
});
|
||||
|
||||
it("update of unknown id writes a new file", async () => {
|
||||
const orphan: Task = {
|
||||
...sampleTask,
|
||||
id: "orphan-id",
|
||||
title: "Orphan",
|
||||
created: t0,
|
||||
updated: t0,
|
||||
};
|
||||
|
||||
await repo.update(folder, orphan);
|
||||
|
||||
const found = await repo.getById(folder, "orphan-id");
|
||||
expect(found).toMatchObject({
|
||||
id: "orphan-id",
|
||||
title: "Orphan",
|
||||
updated: t0,
|
||||
});
|
||||
expect(await fs.readdir(folder)).toEqual(["orphan.task.md"]);
|
||||
});
|
||||
|
||||
it("delete removes the task file", async () => {
|
||||
@@ -84,6 +128,7 @@ describe("FsTaskRepository", () => {
|
||||
|
||||
const tasks = await repo.list(folder);
|
||||
expect(tasks).toHaveLength(0);
|
||||
expect(await fs.readdir(folder)).toEqual([]);
|
||||
});
|
||||
|
||||
it("delete is a no-op for unknown id", async () => {
|
||||
@@ -96,8 +141,8 @@ describe("FsTaskRepository", () => {
|
||||
});
|
||||
|
||||
it("filters files by extension", async () => {
|
||||
fs.writeFile(`${folder}/notes.txt`, "hello");
|
||||
fs.writeFile(`${folder}/chore.md`, "---\nid: x\n---\n");
|
||||
await fs.writeFile(`${folder}/notes.txt`, "hello");
|
||||
await fs.writeFile(`${folder}/chore.md`, "---\nid: x\n---\n");
|
||||
|
||||
await repo.create(folder, sampleTask);
|
||||
|
||||
@@ -105,15 +150,41 @@ describe("FsTaskRepository", () => {
|
||||
expect(tasks).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("skips unparseable .task.md files silently", async () => {
|
||||
fs.writeFile(`${folder}/corrupt.task.md`, "not valid frontmatter");
|
||||
it("list returns empty when only non-matching extensions exist", async () => {
|
||||
await fs.writeFile(`${folder}/notes.txt`, "hello");
|
||||
await fs.writeFile(`${folder}/chore.md`, "---\nid: x\n---\n");
|
||||
|
||||
const tasks = await repo.list(folder);
|
||||
expect(tasks).toEqual([]);
|
||||
});
|
||||
|
||||
it("skips files without id", async () => {
|
||||
await fs.writeFile(`${folder}/no-id.task.md`, "not valid frontmatter");
|
||||
await fs.writeFile(
|
||||
`${folder}/empty-id.task.md`,
|
||||
"---\ntitle: No Id\n---\n",
|
||||
);
|
||||
|
||||
await repo.create(folder, sampleTask);
|
||||
const tasks = await repo.list(folder);
|
||||
expect(tasks).toHaveLength(1);
|
||||
expect(tasks[0].title).toBe("Fix login bug");
|
||||
});
|
||||
|
||||
it("create with same title overwrites the previous file", async () => {
|
||||
const first = await repo.create(folder, sampleTask);
|
||||
const second = await repo.create(folder, {
|
||||
...sampleTask,
|
||||
description: "second",
|
||||
});
|
||||
|
||||
const files = await fs.readdir(folder);
|
||||
expect(files).toEqual(["fix-login-bug.task.md"]);
|
||||
|
||||
const tasks = await repo.list(folder);
|
||||
expect(tasks).toHaveLength(1);
|
||||
expect(tasks[0].id).toBe(second.id);
|
||||
expect(tasks[0].id).not.toBe(first.id);
|
||||
expect(tasks[0].description).toBe("second");
|
||||
});
|
||||
});
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user