chore: Halkyon Learn moves into apps/learn

Its server, client, app and curator, and the tools that write its
assets (maps, Anki fixtures, icons), as they are; paths are fixed in
the commits that follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-19 20:08:06 +02:00
commit 649499be23
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -0,0 +1,108 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_learn_flutter/src/feature/statistics/model/study_statistics.dart';
import 'package:halkyon_learn_flutter/src/feature/study/model/rating.dart';
import 'package:halkyon_learn_flutter/src/feature/study/model/review.dart';
void main() {
// Monday, 12 October 2026, in the afternoon, local time.
final now = DateTime(2026, 10, 12, 15);
var ids = 0;
Review review(
DateTime at, {
Duration duration = const Duration(seconds: 20),
Rating rating = Rating.good,
StudyMode mode = StudyMode.review,
}) => Review(
id: 'review-${ids++}',
cardId: 'card',
rating: rating,
reviewedAt: at.toUtc(),
duration: duration,
mode: mode,
);
test('counts at most a minute a card, cramming too, a fresh start '
'not', () {
final statistics = studyStatistics(
[
review(now, duration: const Duration(minutes: 45)),
review(now, duration: const Duration(seconds: 30)),
review(now, mode: StudyMode.cram),
review(
now,
duration: const Duration(minutes: 5),
mode: StudyMode.reset,
),
],
now: now,
span: StatisticsSpan.days,
);
expect(statistics.total, const Duration(seconds: 110));
expect(statistics.periods.last.time, const Duration(seconds: 110));
expect(statistics.periods.last.reviews, 3);
expect(statistics.studied, true);
});
test('per day counts every day from the first studied to today; the '
'record is the most in one day', () {
final statistics = studyStatistics(
[
// Three days ago, 2 minutes; today, 1.
for (var i = 0; i < 2; i++)
review(
now.subtract(const Duration(days: 3)),
duration: const Duration(minutes: 1),
),
review(now, duration: const Duration(minutes: 1)),
],
now: now,
span: StatisticsSpan.days,
);
expect(statistics.total, const Duration(minutes: 3));
// Four days: three ago, two, one, today.
expect(statistics.perDay, const Duration(seconds: 45));
expect(statistics.record, const Duration(minutes: 2));
});
test('ten days, today last; ten weeks from Monday; twelve months', () {
final days = studyStatistics([], now: now, span: StatisticsSpan.days);
expect(days.periods, hasLength(10));
expect(days.periods.first.start, DateTime(2026, 10, 3));
expect(days.periods.last.start, DateTime(2026, 10, 12));
expect(days.studied, false);
final weeks = studyStatistics([], now: now, span: StatisticsSpan.weeks);
expect(weeks.periods, hasLength(10));
expect(weeks.periods.last.start, DateTime(2026, 10, 12));
expect(weeks.periods.first.start, DateTime(2026, 8, 10));
final months = studyStatistics([], now: now, span: StatisticsSpan.months);
expect(months.periods, hasLength(12));
expect(months.periods.first.start, DateTime(2025, 11));
expect(months.periods.last.start, DateTime(2026, 10));
});
test("each period holds its reviews' time and ratings; older ones count "
'in the totals only', () {
final statistics = studyStatistics(
[
review(DateTime(2026, 10, 6, 9), rating: Rating.again),
review(DateTime(2026, 10, 11, 23), rating: Rating.easy),
review(DateTime(2026, 10, 12, 8), rating: Rating.easy),
review(DateTime(2026, 7, 1, 8)),
],
now: now,
span: StatisticsSpan.weeks,
);
final [...earlier, lastWeek, thisWeek] = statistics.periods;
expect(thisWeek.ratings, {Rating.easy: 1});
expect(lastWeek.ratings, {Rating.again: 1, Rating.easy: 1});
expect(lastWeek.time, const Duration(seconds: 40));
expect(earlier.every((period) => period.reviews == 0), true);
expect(statistics.total, const Duration(seconds: 80));
});
}