todo-app/test/repo_note_split_test.dart
Krzysztof kuhy Rudnicki 75248e9cde Add new_note to add_task with merge and start
new_note files a separate note per repo; an exact section-title
match in another open note merges into it and starts it via the
board, unless that note is running, none or low priority.

Todo: 64d39ab5

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 15:24:41 +01:00

141 lines
4.9 KiB
Dart

// new_note: a second open note per repo, the duplicate guard that merges
// into it, and the shared add path that never joins such a note.
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';
import 'package:todo/data/repo_note_split.dart';
const _repo = 'kuhyx/herosi';
Note _n(String id, String text, {int day = 1, String repo = _repo}) => Note(
id: id,
text: text,
priority: Priority.medium,
status: Status.todo,
createdAt: DateTime.utc(2026, 10, day),
updatedAt: DateTime.utc(2026, 10, day),
repo: repo,
doneCheck: 'old',
);
Future<Note> _get(NoteRepository r, String id) async =>
(await r.listNotes()).singleWhere((n) => n.id == id);
void main() {
late NoteRepository repo;
setUp(() async => repo = await NoteRepository.openInMemory());
tearDown(() => repo.close());
Future<SplitAdd> add(String title, {Future<bool> Function(Note)? busy}) =>
addSeparateTask(
repo,
repo: _repo,
title: title,
body: 'what',
doneCheck: 'new',
priority: Priority.high,
by: 'claude',
busy: busy,
);
final shared = _n('aaaaaaaa-shared', 'herosi todos\n\n## Mędrzec\nlisted');
test('C1: builds a second open note, the shared one untouched', () async {
await repo.upsert(shared);
final r = await add('Mędrzec');
expect(r.kind, SplitKind.added);
expect(r.note.id, isNot(shared.id));
expect(r.note.text, 'herosi todos: Mędrzec\n\n## Mędrzec\nwhat');
expect(r.note.doneCheck, 'new');
expect(isSeparateNote(r.note), isTrue);
expect(isSeparateNote(shared), isFalse);
expect((await _get(repo, shared.id)).text, shared.text);
});
test('C2: a plain add joins the shared note, never a separate one', () async {
final split = _n('00000000-split', 'herosi todos: Old\n\n## Old\nx');
await repo.upsert(split);
await repo.upsert(_n('bbbbbbbb-shared', shared.text, day: 2));
final r = await addRepoTask(repo, repo: _repo, title: 'B');
expect(r.appended, isTrue);
expect(r.note.id, 'bbbbbbbb-shared');
await repo.upsert(
_n('bbbbbbbb-shared', 'x', day: 2).copyWith(status: Status.done),
);
final fresh = await addRepoTask(repo, repo: _repo, title: 'C');
expect(fresh.appended, isFalse);
expect(fresh.note.text, startsWith('herosi todos\n'));
});
test('C3: an exact title in another separate note merges into it', () async {
final x = _n('cccccccc-x', 'herosi todos: Młody\n\n## Młody\nfirst');
await repo.upsert(x);
final r = await add('Młody');
expect(r.kind, SplitKind.merged);
expect(r.note.id, x.id);
expect(r.note.text, '${x.text}\n\n## Młody\nwhat');
expect(r.note.doneCheck, '( old ) && ( new )');
expect(r.note.priority, Priority.high);
});
test('C4: shared-note, other-repo, done or near hits do not merge', () async {
await repo.upsert(shared);
await repo.upsert(
_n('dddddddd-o', 'x todos: Hekser\n\n## Hekser\n', repo: 'kuhyx/x'),
);
await repo.upsert(_n('eeeeeeee-d', 'herosi todos: H\n\n## [x] Hekser\n'));
await repo.upsert(_n('ffffffff-c', 'herosi todos: H\n\n## hekser \n'));
await repo.upsert(
_n('99999999-f', 'herosi todos: H\n\n```\n## Hekser\n```'),
);
expect((await add('Hekser')).kind, SplitKind.added);
expect((await add('Mędrzec')).kind, SplitKind.added);
});
test(
'C5: a running target is not edited; a separate note says why',
() async {
final x = _n('cccccccc-x', 'herosi todos: Młody\n\n## Młody\nfirst');
await repo.upsert(x);
final seen = <String>[];
final r = await add(
'Młody',
busy: (n) async {
seen.add(n.id);
return true;
},
);
expect(seen, [x.id]);
expect(r.kind, SplitKind.fellBack);
expect(r.note.id, isNot(x.id));
expect(r.why, contains('cccccccc'));
expect((await _get(repo, x.id)).text, x.text);
},
);
test('C10: the one-time merge plans nothing for separate notes', () {
final notes = [
_n('a', 'herosi todos\n\n## One\nx', day: 1),
_n('b', 'herosi todos: Two\n\n## Two\ny', day: 2),
_n('c', 'herosi todos: Three\n\n## Three\nz', day: 3),
];
expect(planRepoMerges(notes), isEmpty);
final legacy = planRepoMerges([...notes, _n('d', 'Legacy\n\nl', day: 4)]);
expect(legacy.single.kept.id, 'a');
expect(legacy.single.removed.map((n) => n.id), ['d']);
});
group('startBlock', () {
test('a real check and a normal body may start', () {
expect(startBlock('test -f x', 'what'), isNull);
});
test('C6: none or low priority never starts', () {
expect(startBlock(null, 'what'), 'done_check is none');
expect(startBlock(' None ', 'what'), 'done_check is none');
expect(startBlock('x', '\nLow priority\nrest'), 'marked low priority');
});
});
}