todo-app/test/repo_note_test.dart
Krzysztof kuhy Rudnicki a0c1938d8d
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Split notes on first non-blank line
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-02 14:14:16 +01:00

193 lines
6.2 KiB
Dart

// One open note per repo: the shared add path, the done_check composition
// and the one-time merge plan.
import 'package:flutter_test/flutter_test.dart';
import 'package:todo/data/note.dart';
import 'package:todo/data/note_repository.dart';
import 'package:todo/data/repo_note.dart';
import 'package:todo/data/repo_note_merge.dart';
const _repo = 'kuhyx/wake-alarm';
Note _n(
String id,
String text, {
int day = 1,
String repo = _repo,
Status status = Status.todo,
Priority priority = Priority.medium,
String? doneCheck,
}) => Note(
id: id,
text: text,
priority: priority,
status: status,
createdAt: DateTime.utc(2026, 10, day),
updatedAt: DateTime.utc(2026, 10, day),
repo: repo,
doneCheck: doneCheck,
by: 'claude',
);
void main() {
group('composeDoneCheck', () {
test('both present are wrapped and joined with &&', () {
expect(composeDoneCheck('a', 'b'), '( a ) && ( b )');
});
test('only one present is kept as is', () {
expect(composeDoneCheck('a', null), 'a');
expect(composeDoneCheck(null, 'b'), 'b');
expect(composeDoneCheck('', 'b'), 'b');
expect(composeDoneCheck(null, null), isNull);
});
});
test('splitNote of blank text is empty', () {
expect(splitNote(' \n'), (title: '', body: ''));
});
group('addRepoTask', () {
late NoteRepository repo;
setUp(() async => repo = await NoteRepository.openInMemory());
tearDown(() => repo.close());
test('creates "<name> todos" with the task as the first section', () async {
final r = await addRepoTask(
repo,
repo: _repo,
title: 'Fix A',
body: 'what: a',
doneCheck: 'true',
priority: Priority.high,
by: 'claude',
);
expect(r.appended, isFalse);
expect(r.note.text, 'wake-alarm todos\n\n## Fix A\nwhat: a');
expect(r.note.doneCheck, 'true');
expect(r.note.priority, Priority.high);
expect(r.note.by, 'claude');
expect(r.note.status, Status.todo);
});
test('appends to the open note: body, check, max priority', () async {
final first = await addRepoTask(
repo,
repo: _repo,
title: 'Fix A',
body: 'what: a',
doneCheck: 'test a',
priority: Priority.high,
);
await repo.upsert(first.note);
final second = await addRepoTask(
repo,
repo: _repo,
title: 'Fix B',
body: 'what: b',
doneCheck: 'test b',
priority: Priority.low,
);
expect(second.appended, isTrue);
expect(second.note.id, first.note.id);
expect(
second.note.text,
'wake-alarm todos\n\n## Fix A\nwhat: a\n\n## Fix B\nwhat: b',
);
expect(second.note.doneCheck, '( test a ) && ( test b )');
expect(second.note.priority, Priority.high);
});
test('a missing check on either side keeps the other', () async {
final first = await addRepoTask(repo, repo: _repo, title: 'A');
await repo.upsert(first.note);
final second = await addRepoTask(
repo,
repo: _repo,
title: 'B\nCheck: manual',
doneCheck: 'test b',
);
expect(second.note.doneCheck, 'test b');
expect(second.note.text, contains('Check: manual'));
});
test('closed notes and other repos do not absorb the task', () async {
await repo.upsert(_n('d', 'old\n\n## x', status: Status.done));
await repo.upsert(_n('o', 'other\n\n## x', repo: 'kuhyx/other'));
final r = await addRepoTask(repo, repo: _repo, title: 'Fix A');
expect(r.appended, isFalse);
expect(r.note.id, isNot(anyOf('d', 'o')));
});
test('an in-progress note counts as open; the oldest wins', () async {
await repo.upsert(_n('new', 'T\n\n## n', day: 5));
await repo.upsert(
_n('old', 'T\n\n## o', day: 2, status: Status.inProgress),
);
final r = await addRepoTask(repo, repo: _repo, title: 'Z');
expect(r.note.id, 'old');
});
});
group('planRepoMerges', () {
test('merges three notes into the oldest, sections oldest first', () {
final plan = planRepoMerges([
_n('b', 'Second\n\nbody b', day: 2, doneCheck: 'cb'),
_n('a', 'First', day: 1, doneCheck: 'ca', priority: Priority.low),
_n('c', 'Third\n\nbody c', day: 3, priority: Priority.high),
_n('x', 'Elsewhere', repo: 'kuhyx/other', day: 1),
]);
final m = plan.singleWhere((p) => p.kept.repo == _repo);
expect(m.kept.id, 'a');
expect(m.removed.map((n) => n.id), ['b', 'c']);
expect(
m.kept.text,
'wake-alarm todos\n\n## First\n\n## Second\nbody b\n\n## Third\nbody c',
);
expect(m.kept.doneCheck, '( ca ) && ( cb )');
expect(m.kept.priority, Priority.high);
});
test('single-note repos are retitled with the body as one section', () {
final plan = planRepoMerges([_n('x', 'Only\n\nbody')]);
expect(plan.single.removed, isEmpty);
expect(plan.single.kept.text, 'wake-alarm todos\n\n## Only\nbody');
});
test('already merged repos are left alone (idempotent)', () {
final merged = planRepoMerges([
_n('a', 'One', day: 1),
_n('b', 'Two', day: 2),
]).single.kept;
expect(planRepoMerges([merged]), isEmpty);
});
test('a merged note plus a new one folds in without re-wrapping', () {
final merged = _n('a', 'wake-alarm todos\n\n## One\nx', day: 1);
final plan = planRepoMerges([merged, _n('b', 'Two\n\ny', day: 2)]);
expect(
plan.single.kept.text,
'wake-alarm todos\n\n## One\nx\n\n## Two\ny',
);
});
test('a leading blank line and a # prefix do not leak into sections', () {
final plan = planRepoMerges([_n('a', '\n# Title\nbody')]);
expect(plan.single.kept.text, 'wake-alarm todos\n\n## Title\nbody');
});
test('closed notes and plain ideas are ignored', () {
final idea = Note(
id: 'i',
text: 'idea',
priority: Priority.medium,
status: Status.todo,
createdAt: DateTime.utc(2026, 10),
updatedAt: DateTime.utc(2026, 10),
);
expect(
planRepoMerges([idea, _n('d', 'Done', status: Status.done)]),
isEmpty,
);
});
});
}