Metadata: - Bump dew CLI version 0.0.1 → 1.0.0 - Add repository + issue_tracker URLs to all pubspec.yaml files - Switch inter-package path deps to versioned deps (^1.0.0) - Remove publish_to: none from all packages - Add MIT LICENSE to root and all packages - Confirm all four pub.dev names available (dew, dew_core, dew_kanban, dew_mcp) Documentation: - Add CHANGELOG.md (Keep a Changelog format, full 1.0.0 feature history) - Overhaul README.md (pitch, pub.dev badge, quick-start, feature sections) - Add TUI section + full keybinding tables to docs/features/kanban.md - Add CONTRIBUTING.md (setup, test, lint, branch strategy, command guide) Tests: - Add packages/cli/test/cli_test.dart (6 smoke tests) - Add packages/kanban/test/integration_test.dart (6 TicketStore e2e tests) - Expand packages/mcp/test/mcp_test.dart (5 tool registration tests) - Add dew_kanban as dev dependency in packages/mcp/pubspec.yaml - 57/57 tests passing Code quality: - dart format applied across all 23 changed source files - dart analyze: zero errors, zero warnings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
146 lines
4.4 KiB
Dart
146 lines
4.4 KiB
Dart
import 'package:dew_kanban/dew_kanban.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
const _testConfig = '''
|
|
dew:
|
|
mcp:
|
|
host: localhost
|
|
port: 9090
|
|
kanban:
|
|
prefix: T
|
|
ticket_types:
|
|
- id: task
|
|
name: Task
|
|
columns:
|
|
- id: todo
|
|
name: To Do
|
|
color: blue
|
|
- id: doing
|
|
name: Doing
|
|
color: yellow
|
|
- id: done
|
|
name: Done
|
|
color: green
|
|
''';
|
|
|
|
TicketStore _makeStore(MemoryFileSystem fs) {
|
|
fs.directory('/.project/kanban').createSync(recursive: true);
|
|
fs.file('/.project/dew.yaml').writeAsStringSync(_testConfig);
|
|
return TicketStore(kanbanDir: '/.project/kanban', prefix: 'T', fs: fs);
|
|
}
|
|
|
|
void main() {
|
|
group('TicketStore integration', () {
|
|
late MemoryFileSystem fs;
|
|
late TicketStore store;
|
|
|
|
setUp(() {
|
|
fs = MemoryFileSystem();
|
|
store = _makeStore(fs);
|
|
});
|
|
|
|
test(
|
|
'create() writes file at correct path with YAML frontmatter',
|
|
() async {
|
|
final ticket = await store.create(
|
|
title: 'My ticket',
|
|
type: 'task',
|
|
column: 'todo',
|
|
);
|
|
|
|
expect(ticket.id, 'T-0001');
|
|
final file = fs.file('/.project/kanban/todo/T-0001.md');
|
|
expect(await file.exists(), isTrue);
|
|
final content = await file.readAsString();
|
|
expect(content, contains('title: My ticket'));
|
|
expect(content, contains('type: task'));
|
|
},
|
|
);
|
|
|
|
test('list() returns ticket created via create()', () async {
|
|
await store.create(title: 'Alpha', type: 'task', column: 'todo');
|
|
final tickets = await store.list();
|
|
expect(tickets, hasLength(1));
|
|
expect(tickets.first.id, 'T-0001');
|
|
expect(tickets.first.title, 'Alpha');
|
|
});
|
|
|
|
test('update(column:) moves ticket to correct directory', () async {
|
|
await store.create(title: 'Move me', type: 'task', column: 'todo');
|
|
|
|
await store.update('T-0001', column: 'doing');
|
|
|
|
// File removed from old location.
|
|
expect(
|
|
await fs.file('/.project/kanban/todo/T-0001.md').exists(),
|
|
isFalse,
|
|
);
|
|
// File present in new location.
|
|
expect(
|
|
await fs.file('/.project/kanban/doing/T-0001.md').exists(),
|
|
isTrue,
|
|
);
|
|
|
|
final ticket = await store.findById('T-0001');
|
|
expect(ticket!.column, 'doing');
|
|
});
|
|
|
|
test('linkTickets() adds link to both ticket files', () async {
|
|
await store.create(title: 'Source', type: 'task', column: 'todo');
|
|
await store.create(title: 'Target', type: 'task', column: 'todo');
|
|
|
|
await store.linkTickets('T-0001', 'T-0002', 'relates_to');
|
|
|
|
final sourceContent = await fs
|
|
.file('/.project/kanban/todo/T-0001.md')
|
|
.readAsString();
|
|
final targetContent = await fs
|
|
.file('/.project/kanban/todo/T-0002.md')
|
|
.readAsString();
|
|
|
|
expect(sourceContent, contains('T-0002'));
|
|
expect(targetContent, contains('T-0001'));
|
|
|
|
final source = await store.findById('T-0001');
|
|
final target = await store.findById('T-0002');
|
|
expect(source!.links.any((l) => l.targetId == 'T-0002'), isTrue);
|
|
expect(target!.links.any((l) => l.targetId == 'T-0001'), isTrue);
|
|
});
|
|
|
|
test('unlinkTickets() removes link from both ticket files', () async {
|
|
await store.create(title: 'Source', type: 'task', column: 'todo');
|
|
await store.create(title: 'Target', type: 'task', column: 'todo');
|
|
await store.linkTickets('T-0001', 'T-0002', 'relates_to');
|
|
|
|
await store.unlinkTickets('T-0001', 'T-0002');
|
|
|
|
final source = await store.findById('T-0001');
|
|
final target = await store.findById('T-0002');
|
|
expect(source!.links, isEmpty);
|
|
expect(target!.links, isEmpty);
|
|
|
|
final sourceContent = await fs
|
|
.file('/.project/kanban/todo/T-0001.md')
|
|
.readAsString();
|
|
final targetContent = await fs
|
|
.file('/.project/kanban/todo/T-0002.md')
|
|
.readAsString();
|
|
expect(sourceContent, isNot(contains('T-0002')));
|
|
expect(targetContent, isNot(contains('T-0001')));
|
|
});
|
|
|
|
test('delete() removes ticket file from disk', () async {
|
|
await store.create(title: 'Doomed', type: 'task', column: 'todo');
|
|
expect(await fs.file('/.project/kanban/todo/T-0001.md').exists(), isTrue);
|
|
|
|
await store.delete('T-0001');
|
|
|
|
expect(
|
|
await fs.file('/.project/kanban/todo/T-0001.md').exists(),
|
|
isFalse,
|
|
);
|
|
expect(await store.findById('T-0001'), isNull);
|
|
});
|
|
});
|
|
}
|