feat(todo): the open sidebar lists favourites and the project tree, folds kept

Todo's open sidebar is laid out as Todoist's (the developer's decisions
of 10 Nov 2026): the favourite projects, filters and labels, then the
projects as a tree with their open counts, sub-projects folding. Browse
and the sidebar share their rows (project_lists.dart) and their folds,
which the device now keeps per person (FoldedProjects,
folded_projects.json in the person's folder) instead of forgetting them
at each start.

Whats-New: On a wide screen, the open sidebar lists your favourites and projects; folded sub-projects stay folded.
Whats-New-De: Auf breiten Bildschirmen zeigt die geöffnete Seitenleiste deine Favoriten und Projekte; eingeklappte Unterprojekte bleiben eingeklappt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-10 17:03:17 +01:00
commit a37ca123b1
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -0,0 +1,51 @@
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_todo_flutter/src/feature/project/data/folded_projects.dart';
/// Which projects' sub-projects are folded away, kept per device (the
/// developer's decision of 10 Nov 2026), in Browse and the sidebar alike.
void main() {
late Directory folder;
late File file;
setUp(() {
folder = Directory.systemTemp.createTempSync('folded');
file = File('${folder.path}/folded_projects.json');
});
tearDown(() => folder.deleteSync(recursive: true));
test('nothing is folded at first', () async {
final folded = await FoldedProjects.open(file);
expect(folded.isFolded('school'), isFalse);
});
test('a fold is kept for the next start, an unfold too', () async {
final folded = await FoldedProjects.open(file);
var told = 0;
folded.addListener(() => told++);
await folded.toggle('school');
await folded.toggle('home');
await folded.toggle('home');
expect(told, 3);
final again = await FoldedProjects.open(file);
expect(again.isFolded('school'), isTrue);
expect(again.isFolded('home'), isFalse);
});
test('a damaged file folds nothing', () async {
file.writeAsStringSync('{"school": ');
expect((await FoldedProjects.open(file)).isFolded('school'), isFalse);
});
test('without a file it folds for this session alone', () async {
final folded = FoldedProjects();
await folded.toggle('school');
expect(folded.isFolded('school'), isTrue);
});
}