test: add edge case tests
This commit is contained in:
@@ -68,4 +68,44 @@ body
|
||||
if (result.isOk()) return;
|
||||
expect(result.error.variant).toBe(AppErrorVariant.INVALID_TASK_FILE);
|
||||
});
|
||||
|
||||
it("handles raw content with fields in different order", () => {
|
||||
const raw = `---
|
||||
status: done
|
||||
id: "should-be-ignored"
|
||||
title: Reordered
|
||||
priority: low
|
||||
tags: [x, y]
|
||||
assignee: ""
|
||||
created: 2099-01-01T00:00:00.000Z
|
||||
updated: 2026-01-02T00:00:00.000Z
|
||||
---
|
||||
body text
|
||||
`;
|
||||
const result = applyRawTaskContent(raw, previous);
|
||||
expect(result.isOk()).toBe(true);
|
||||
if (result.isErr()) return;
|
||||
expect(result.value.title).toBe("Reordered");
|
||||
expect(result.value.id).toBe(previous.id);
|
||||
expect(result.value.created).toBe(previous.created);
|
||||
expect(result.value.status).toBe(TaskStatus.DONE);
|
||||
});
|
||||
|
||||
it("handles raw content with empty description body", () => {
|
||||
const raw = `---
|
||||
id: "x"
|
||||
title: No body
|
||||
status: todo
|
||||
priority: medium
|
||||
tags: []
|
||||
assignee: ""
|
||||
created: 2026-01-01T00:00:00.000Z
|
||||
updated: 2026-01-01T00:00:00.000Z
|
||||
---
|
||||
`;
|
||||
const result = applyRawTaskContent(raw, previous);
|
||||
expect(result.isOk()).toBe(true);
|
||||
if (result.isErr()) return;
|
||||
expect(result.value.description).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -80,4 +80,11 @@ describe("buildDashboardView", () => {
|
||||
const view = buildDashboardView(tasks, { selectedId: "missing" });
|
||||
expect(view.selected).toBeUndefined();
|
||||
});
|
||||
|
||||
it("handles empty tasks array", () => {
|
||||
const view = buildDashboardView([]);
|
||||
expect(view.visibleTasks).toEqual([]);
|
||||
expect(view.groups).toEqual([]);
|
||||
expect(view.selected).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,4 +98,9 @@ describe("filterTasks", () => {
|
||||
});
|
||||
expect(result.map((t) => t.id)).toEqual(["4"]);
|
||||
});
|
||||
|
||||
it("returns empty array for empty input", () => {
|
||||
expect(filterTasks([], {})).toEqual([]);
|
||||
expect(filterTasks([], { statuses: ["todo"] })).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,4 +93,30 @@ describe("groupTasks", () => {
|
||||
expect(groupTasks([], GroupBy.STATUS)).toEqual([]);
|
||||
expect(groupTasks([], GroupBy.NONE)).toEqual([]);
|
||||
});
|
||||
|
||||
it("silently drops tasks with unknown status from groups", () => {
|
||||
const tasks: Task[] = [
|
||||
task({ id: "a", title: "A", status: "__garbage__" as TaskStatus }),
|
||||
task({ id: "b", title: "B", status: TaskStatus.TODO }),
|
||||
];
|
||||
const groups = groupTasks(tasks, GroupBy.STATUS);
|
||||
const allGrouped = groups.flatMap((g) => g.tasks.map((t) => t.id));
|
||||
expect(allGrouped).toEqual(["b"]);
|
||||
expect(allGrouped).not.toContain("a");
|
||||
});
|
||||
|
||||
it("silently drops tasks with unknown priority from groups", () => {
|
||||
const tasks: Task[] = [
|
||||
task({
|
||||
id: "a",
|
||||
title: "A",
|
||||
priority: "__garbage__" as TaskPriority,
|
||||
}),
|
||||
task({ id: "b", title: "B", priority: TaskPriority.LOW }),
|
||||
];
|
||||
const groups = groupTasks(tasks, GroupBy.PRIORITY);
|
||||
const allGrouped = groups.flatMap((g) => g.tasks.map((t) => t.id));
|
||||
expect(allGrouped).toEqual(["b"]);
|
||||
expect(allGrouped).not.toContain("a");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -61,4 +61,28 @@ describe("sortTasks", () => {
|
||||
sortTasks(tasks, TaskSortDirection.DESC);
|
||||
expect(tasks.map((t) => t.id)).toEqual(copy.map((t) => t.id));
|
||||
});
|
||||
|
||||
it("returns empty array for empty input", () => {
|
||||
expect(sortTasks([])).toEqual([]);
|
||||
});
|
||||
|
||||
it("preserves input order when created dates are identical (stable sort)", () => {
|
||||
const sameDate = "2026-01-01T00:00:00.000Z";
|
||||
const tasks: Task[] = [
|
||||
task({ id: "a", title: "A", created: sameDate }),
|
||||
task({ id: "b", title: "B", created: sameDate }),
|
||||
task({ id: "c", title: "C", created: sameDate }),
|
||||
];
|
||||
const result = sortTasks(tasks, TaskSortDirection.ASC);
|
||||
expect(result.map((t) => t.id)).toEqual(["a", "b", "c"]);
|
||||
});
|
||||
|
||||
it("handles invalid date strings without crashing", () => {
|
||||
const tasks: Task[] = [
|
||||
task({ id: "a", title: "A", created: "invalid-date" }),
|
||||
task({ id: "b", title: "B", created: "2026-01-01T00:00:00.000Z" }),
|
||||
];
|
||||
const result = sortTasks(tasks, TaskSortDirection.ASC);
|
||||
expect(result).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -117,4 +117,31 @@ describe("buildKanbanBoard", () => {
|
||||
expect(col.tasks).toEqual([]);
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves input order for tasks with same updated timestamp (stable sort)", () => {
|
||||
const sameUpdated = "2026-01-01T00:00:00.000Z";
|
||||
const tasks: Task[] = [
|
||||
task({
|
||||
id: "a",
|
||||
title: "A",
|
||||
status: TaskStatus.TODO,
|
||||
updated: sameUpdated,
|
||||
}),
|
||||
task({
|
||||
id: "b",
|
||||
title: "B",
|
||||
status: TaskStatus.TODO,
|
||||
updated: sameUpdated,
|
||||
}),
|
||||
task({
|
||||
id: "c",
|
||||
title: "C",
|
||||
status: TaskStatus.TODO,
|
||||
updated: sameUpdated,
|
||||
}),
|
||||
];
|
||||
const board = buildKanbanBoard(tasks);
|
||||
const todo = board.columns.find((col) => col.status === TaskStatus.TODO);
|
||||
expect(todo?.tasks.map((t) => t.id)).toEqual(["a", "b", "c"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,4 +195,52 @@ describe("FsTaskRepository", () => {
|
||||
expect(tasks[0].id).not.toBe(first.id);
|
||||
expect(tasks[0].description).toBe("second");
|
||||
});
|
||||
|
||||
it("creates task with Cyrillic title and slug", async () => {
|
||||
const task = await repo.create(folder, {
|
||||
...sampleTask,
|
||||
title: "Тестовая задача",
|
||||
});
|
||||
expect(task.title).toBe("Тестовая задача");
|
||||
|
||||
const files = await fs.readdir(folder);
|
||||
expect(files).toEqual([`тестовая-задача${TASK_FILE_EXTENSION}`]);
|
||||
});
|
||||
|
||||
it("creates task with title exactly at MAX_TASK_TITLE_LENGTH", async () => {
|
||||
const title = "a".repeat(80);
|
||||
const task = await repo.create(folder, { ...sampleTask, title });
|
||||
expect(task.title).toBe(title);
|
||||
|
||||
const files = await fs.readdir(folder);
|
||||
expect(files).toEqual([`${title}${TASK_FILE_EXTENSION}`]);
|
||||
|
||||
const found = await repo.getById(folder, task.id);
|
||||
expect(found?.title).toBe(title);
|
||||
});
|
||||
|
||||
it("creates task with tags containing special characters", async () => {
|
||||
const task = await repo.create(folder, {
|
||||
...sampleTask,
|
||||
tags: ["c#", "c++", "tag name", "tag:value"],
|
||||
});
|
||||
|
||||
const files = await fs.readdir(folder);
|
||||
expect(files).toHaveLength(1);
|
||||
|
||||
const content = await fs.readFile(
|
||||
`${folder}/fix-login-bug${TASK_FILE_EXTENSION}`,
|
||||
);
|
||||
expect(content).toContain("c#");
|
||||
expect(content).toContain("tag:value");
|
||||
});
|
||||
|
||||
it("getFilePath produces consistent slug regardless of special characters", async () => {
|
||||
const task = await repo.create(folder, {
|
||||
...sampleTask,
|
||||
title: "Fix___bug...",
|
||||
});
|
||||
const files = await fs.readdir(folder);
|
||||
expect(files).toEqual([`fix-bug${TASK_FILE_EXTENSION}`]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user