dew/packages/kanban/test/dew_kanban_test.dart
Chris Hendrickson a1c36f7136 Implement kanban subcommands (create, get, update, delete)
- Add ProjectContext and DewConfig models to core
- Add Ticket model with YAML frontmatter serialisation
- Add TicketStore with auto-incrementing 4-digit IDs
- Implement create/get/update/delete subcommands under KanbanCommand
- Expand tests: ProjectContext, Ticket roundtrip, TicketStore CRUD (19 total)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 14:47:25 -04:00

143 lines
4 KiB
Dart

import 'dart:io';
import 'package:dew_core/dew_core.dart';
import 'package:dew_kanban/dew_kanban.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
void main() {
group('KanbanCommand', () {
test('has correct name and description', () {
final cmd = KanbanCommand();
expect(cmd.name, 'kanban');
expect(cmd.description, isNotEmpty);
});
test('has expected subcommands', () {
final cmd = KanbanCommand();
expect(
cmd.subcommands.keys,
containsAll(['create', 'get', 'update', 'delete']),
);
});
test('registerCommands adds kanban command to registry', () {
final registry = CommandRegistry();
registerCommands(registry);
expect(registry.commands.map((c) => c.name), contains('kanban'));
});
});
group('Ticket', () {
test('roundtrip serialisation', () {
final t = Ticket(
id: 'TEST-0001',
title: 'Do a thing',
type: 'task',
column: 'todo',
created: DateTime.utc(2026, 1, 1, 12),
body: 'Some body.',
comments: ['First comment.', 'Second comment.'],
);
final parsed = Ticket.fromFileContent(t.id, t.toFileContent());
expect(parsed.id, t.id);
expect(parsed.title, t.title);
expect(parsed.type, t.type);
expect(parsed.column, t.column);
expect(parsed.created.toUtc(), t.created.toUtc());
expect(parsed.body, t.body);
expect(parsed.comments, t.comments);
});
test('empty body and no comments', () {
final t = Ticket(
id: 'TEST-0002',
title: 'Empty',
type: 'task',
column: 'todo',
created: DateTime.utc(2026, 1, 2),
body: '',
comments: const [],
);
final parsed = Ticket.fromFileContent(t.id, t.toFileContent());
expect(parsed.body, '');
expect(parsed.comments, isEmpty);
});
});
group('TicketStore', () {
late Directory tempDir;
setUp(() async {
tempDir = await Directory.systemTemp.createTemp('dew_kanban_test_');
});
tearDown(() => tempDir.delete(recursive: true));
TicketStore makeStore() => TicketStore(
kanbanDir: p.join(tempDir.path, 'kanban'),
prefix: 'TEST',
);
test('create assigns incrementing IDs', () async {
final store = makeStore();
final t1 = await store.create(
title: 'First',
type: 'task',
column: 'todo',
);
final t2 = await store.create(
title: 'Second',
type: 'bug',
column: 'todo',
);
expect(t1.id, 'TEST-0001');
expect(t2.id, 'TEST-0002');
});
test('findById returns null for missing ticket', () async {
final store = makeStore();
expect(await store.findById('TEST-0099'), isNull);
});
test('list returns sorted tickets', () async {
final store = makeStore();
await store.create(title: 'A', type: 'task', column: 'todo');
await store.create(title: 'B', type: 'task', column: 'todo');
final all = await store.list();
expect(all.map((t) => t.id), ['TEST-0001', 'TEST-0002']);
});
test('update patches specified fields', () async {
final store = makeStore();
await store.create(title: 'Old', type: 'task', column: 'todo');
final updated = await store.update('TEST-0001', title: 'New');
expect(updated.title, 'New');
expect(updated.type, 'task');
});
test('update throws for missing ticket', () async {
final store = makeStore();
expect(
() => store.update('TEST-0099', title: 'X'),
throwsA(isA<ArgumentError>()),
);
});
test('delete removes ticket', () async {
final store = makeStore();
await store.create(title: 'Bye', type: 'task', column: 'todo');
await store.delete('TEST-0001');
expect(await store.findById('TEST-0001'), isNull);
});
test('delete throws for missing ticket', () async {
final store = makeStore();
expect(
() => store.delete('TEST-0099'),
throwsA(isA<ArgumentError>()),
);
});
});
}