Files
opengroupware/tests/unit/repositories/TodoRepository.test.ts
Ken Yasue 385b251cc7 feat(m9): todo/kanban with DnD, assignee notify, completion, tests, e2e
- TodoRepository (column CRUD, item CRUD, soft-delete, ordering, maxOrderIndex,
  project isolation) + TodoService (lazy standard-column init, member/admin
  permission, create/update/move/toggleComplete/delete, todo_assigned notify,
  todo_created/updated/completed activity logs, SSE todo.updated)
- APIs: columns + items (GET/POST/PATCH/DELETE), toggleComplete + move
- KanbanBoard client (HTML5 drag&drop move, new task per column, complete),
  todos page, ProjectNav ToDo link
- Unit tests (TodoRepository, TodoService) + e2e todo-kanban
2026-06-25 01:54:24 +02:00

145 lines
4.0 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createMigratedTestDb } from '@/tests/helpers/db';
import type { SqliteDatabase } from '@/lib/db/sqlite';
import { UserRepository } from '@/repositories/UserRepository';
import { ProjectRepository } from '@/repositories/ProjectRepository';
import { ProjectMemberRepository } from '@/repositories/ProjectMemberRepository';
import { TodoRepository } from '@/repositories/TodoRepository';
describe('TodoRepository', () => {
let db: SqliteDatabase;
let repo: TodoRepository;
let projectId: number;
let userId: number;
let columnId: number;
beforeEach(() => {
db = createMigratedTestDb();
repo = new TodoRepository(db);
userId = new UserRepository(db).create({
name: 'U',
email: 'u@example.com',
passwordHash: 'h',
}).id;
projectId = new ProjectRepository(db).create({
name: 'P',
ownerId: userId,
}).id;
new ProjectMemberRepository(db).add(projectId, userId, 'admin');
columnId = repo.createColumn({ projectId, name: 'Todo', orderIndex: 0 }).id;
});
afterEach(() => db.close());
it('creates and lists columns ordered by order_index', () => {
repo.createColumn({ projectId, name: 'Done', orderIndex: 2 });
repo.createColumn({ projectId, name: 'Doing', orderIndex: 1 });
const cols = repo.findColumns(projectId);
expect(cols.map((c) => c.name)).toEqual(['Todo', 'Doing', 'Done']);
});
it('updates and deletes a column', () => {
expect(repo.updateColumn(columnId, { name: 'Renamed' })?.name).toBe(
'Renamed'
);
repo.deleteColumn(columnId);
expect(repo.findColumnById(columnId)).toBeNull();
});
it('creates an item and lists by column order then item order', () => {
repo.createItem({
projectId,
columnId,
title: 'second',
creatorId: userId,
orderIndex: 1,
});
repo.createItem({
projectId,
columnId,
title: 'first',
creatorId: userId,
orderIndex: 0,
});
const items = repo.findItemsByProject(projectId);
expect(items.map((i) => i.title)).toEqual(['first', 'second']);
});
it('excludes soft-deleted items', () => {
const item = repo.createItem({
projectId,
columnId,
title: 'x',
creatorId: userId,
orderIndex: 0,
});
repo.deleteItem(item.id);
expect(repo.findItemById(item.id)).toBeNull();
expect(repo.findItemsByProject(projectId)).toHaveLength(0);
});
it('maxItemOrderIndex returns -1 for empty column, else max', () => {
expect(repo.maxItemOrderIndex(columnId)).toBe(-1);
repo.createItem({
projectId,
columnId,
title: 'a',
creatorId: userId,
orderIndex: 5,
});
expect(repo.maxItemOrderIndex(columnId)).toBe(5);
});
it('updates item fields including column move', () => {
const col2 = repo.createColumn({
projectId,
name: 'Done',
orderIndex: 1,
}).id;
const item = repo.createItem({
projectId,
columnId,
title: 'x',
creatorId: userId,
orderIndex: 0,
});
const updated = repo.updateItem(item.id, {
title: 'renamed',
columnId: col2,
orderIndex: 0,
completedAt: '2026-01-01T00:00:00.000Z',
});
expect(updated?.columnId).toBe(col2);
expect(updated?.title).toBe('renamed');
expect(updated?.completedAt).toBeTruthy();
});
it('isolates items by project', () => {
const p2 = new ProjectRepository(db).create({
name: 'P2',
ownerId: userId,
}).id;
const c2 = repo.createColumn({
projectId: p2,
name: 'X',
orderIndex: 0,
}).id;
repo.createItem({
projectId,
columnId,
title: 'mine',
creatorId: userId,
orderIndex: 0,
});
repo.createItem({
projectId: p2,
columnId: c2,
title: 'theirs',
creatorId: userId,
orderIndex: 0,
});
expect(repo.findItemsByProject(projectId)).toHaveLength(1);
expect(repo.findItemsByProject(p2)).toHaveLength(1);
});
});