feat(app): the home dashboard's data: activity, streak, goal, layout
What the dashboard shows, worked out from this device's data: cards rated (crammed included, fresh starts not) and cards written per local day; the streak, up to today or yesterday, rest days counting; the cards falling due per day, but those resting in archived studysets; the study goal's progress in its day or week. The goal, the rest days and the dashboard's arrangement live in the synced settings (payload schema 3). Cards tell when they were made by their UUID v7 ids; reviews report their changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
5cec9f71dc
commit
d846ee4582
1 changed files with 1024 additions and 36 deletions
|
|
@ -0,0 +1,103 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/home/model/study_activity.dart';
|
||||
import 'package:halkyon_learn_flutter/src/feature/home/model/study_goal.dart';
|
||||
|
||||
void main() {
|
||||
// A Wednesday, in the afternoon, local time.
|
||||
final now = DateTime(2026, 10, 7, 15);
|
||||
DateTime day(int d) => DateTime(2026, 10, d);
|
||||
DateTime at(int d, [int hour = 10]) => DateTime(2026, 10, d, hour);
|
||||
|
||||
StudyActivity studied(List<int> days) =>
|
||||
StudyActivity.of(ratings: [for (final d in days) at(d)], written: []);
|
||||
|
||||
test('counts ratings and cards written by the local day they fell on', () {
|
||||
final activity = StudyActivity.of(
|
||||
ratings: [at(5, 9), at(5, 23)],
|
||||
written: [at(6)],
|
||||
);
|
||||
|
||||
expect(activity.on(day(5)), 2);
|
||||
expect(activity.on(day(6)), 1);
|
||||
expect(activity.studiedOn(day(4)), isFalse);
|
||||
});
|
||||
|
||||
group('The streak', () {
|
||||
test('runs up to today once studied today', () {
|
||||
expect(studyStreak(studied([5, 6, 7]), {}, now), 3);
|
||||
});
|
||||
|
||||
test('runs up to yesterday while today is still open', () {
|
||||
expect(studyStreak(studied([5, 6]), {}, now), 2);
|
||||
});
|
||||
|
||||
test('is broken by a day without studying or resting', () {
|
||||
expect(studyStreak(studied([4, 6, 7]), {}, now), 2);
|
||||
expect(studyStreak(studied([4, 5]), {}, now), 0);
|
||||
});
|
||||
|
||||
test('keeps going over a rest day, which counts', () {
|
||||
expect(studyStreak(studied([4, 6, 7]), {day(5)}, now), 4);
|
||||
});
|
||||
});
|
||||
|
||||
test('a week runs Monday to Sunday, each day as it stands', () {
|
||||
final week = weekOf(day(7), studied([5, 7]), {day(6)}, now);
|
||||
|
||||
expect(week.map((each) => each.day.day), [5, 6, 7, 8, 9, 10, 11]);
|
||||
expect(week.map((each) => each.state), [
|
||||
DayState.studied,
|
||||
DayState.rested,
|
||||
DayState.studied,
|
||||
DayState.upcoming,
|
||||
DayState.upcoming,
|
||||
DayState.upcoming,
|
||||
DayState.upcoming,
|
||||
]);
|
||||
expect(
|
||||
weekOf(day(7), studied([]), {}, now).take(3).map((each) => each.state),
|
||||
[DayState.missed, DayState.missed, DayState.today],
|
||||
);
|
||||
});
|
||||
|
||||
test('heat starts at one card and darkens in coarse steps', () {
|
||||
expect([0, 1, 4, 5, 9, 10, 19, 20].map(heatLevel), [
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
3,
|
||||
4,
|
||||
]);
|
||||
});
|
||||
|
||||
test('cards fall due on their day; overdue ones today', () {
|
||||
expect(dueByDay([at(1), at(7), at(9), at(9, 20)], day(7)), {
|
||||
day(7): 2,
|
||||
day(9): 2,
|
||||
});
|
||||
});
|
||||
|
||||
test("a goal's progress counts the ratings of its day or week", () {
|
||||
final ratings = [at(4), at(5), at(6), at(7), at(7, 14)];
|
||||
|
||||
expect(
|
||||
goalProgress(
|
||||
const StudyGoal(period: GoalPeriod.day, target: 20),
|
||||
ratings,
|
||||
now,
|
||||
),
|
||||
2,
|
||||
);
|
||||
expect(
|
||||
goalProgress(
|
||||
const StudyGoal(period: GoalPeriod.week, target: 20),
|
||||
ratings,
|
||||
now,
|
||||
),
|
||||
4,
|
||||
);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue