- Streak computed from all complete Sessions: Grace Hour (00:00-01:00 counts for yesterday unless it is done), a Freeze per 7 Streak Days (max 2) spent automatically on a missed day (ADR 0006) - Top bar flame (grey until today is done) opening a panel with current and longest Streak, Freezes and a month calendar; 'Day N of your streak' on a fresh summary - History per Mode grouped by day, reopening each summary - Two adjustable Reminders (19:00, 22:30) scheduled 14 days ahead with flutter_local_notifications, none on a done day; permission asked after the first complete Session or from Settings - Settings: Reminders, the Player chime, exercise credits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
4.0 KiB
Dart
115 lines
4.0 KiB
Dart
import 'package:clock/clock.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:zenxercise/src/history/session_repository.dart';
|
|
import 'package:zenxercise/src/modes/mode.dart';
|
|
import 'package:zenxercise/src/plans/models/plan.dart';
|
|
import 'package:zenxercise/src/plans/plan_repository.dart';
|
|
import 'package:zenxercise/src/player/session_runner.dart';
|
|
|
|
import '../support/test_app.dart';
|
|
import '../support/test_database.dart';
|
|
|
|
/// Noon, so the Grace Hour never shifts what counts as today.
|
|
final noon = DateTime(2026, 10, 29, 12);
|
|
|
|
void main() {
|
|
late PlanRepository plans;
|
|
late SessionRepository sessions;
|
|
late FakeNotifier notifier;
|
|
|
|
setUp(() {
|
|
final db = testDatabase();
|
|
addTearDown(db.close);
|
|
plans = PlanRepository(db);
|
|
sessions = SessionRepository(db);
|
|
notifier = FakeNotifier();
|
|
});
|
|
|
|
Plan plan(Mode mode) => Plan(
|
|
id: 'p1',
|
|
mode: mode,
|
|
name: 'Legs',
|
|
items: const [WorkoutStep(exerciseId: 'Barbell_Squat', sets: 1, target: WorkoutTarget.reps(10))],
|
|
createdAt: DateTime(2026),
|
|
updatedAt: DateTime(2026),
|
|
);
|
|
|
|
Future<void> finished(WidgetTester tester, Mode mode, DateTime end, {bool complete = true}) async => tester.runAsync(
|
|
() => sessions.save(
|
|
SessionResult(
|
|
plan: plan(mode),
|
|
startedAt: end.subtract(const Duration(minutes: 20)),
|
|
endedAt: end,
|
|
duration: const Duration(minutes: 20),
|
|
complete: complete,
|
|
setLogs: const [SetLog(exerciseId: 'Barbell_Squat', set: 1, reps: 10)],
|
|
),
|
|
),
|
|
);
|
|
|
|
/// Lets drift's streams deliver (they need real time), then settles.
|
|
Future<void> settle(WidgetTester tester) async {
|
|
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 50)));
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
testWidgets('the flame counts finished days across both Modes and opens the Streak panel', (tester) async {
|
|
await withClock(Clock.fixed(noon), () async {
|
|
await finished(tester, Mode.workout, noon.subtract(const Duration(days: 1)));
|
|
await finished(tester, Mode.stretching, noon);
|
|
usePhoneScreen(tester);
|
|
await pumpApp(tester, plans: plans, sessions: sessions, notifier: notifier);
|
|
await settle(tester);
|
|
|
|
expect(find.descendant(of: find.byTooltip('Streak'), matching: find.text('2')), findsOneWidget);
|
|
|
|
await tester.tap(find.byTooltip('Streak'));
|
|
await settle(tester);
|
|
|
|
expect(find.text('2-day streak'), findsOneWidget);
|
|
expect(find.text('Today is done.'), findsOneWidget);
|
|
expect(find.text('Freezes: 0 of 2'), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('History lists the Mode\'s Sessions by day and reopens a summary', (tester) async {
|
|
await withClock(Clock.fixed(noon), () async {
|
|
await finished(tester, Mode.workout, noon, complete: false);
|
|
await finished(tester, Mode.stretching, noon);
|
|
usePhoneScreen(tester);
|
|
await pumpApp(tester, plans: plans, sessions: sessions, prefs: {'mode': 'workout'});
|
|
await tester.tap(find.byTooltip('History'));
|
|
await settle(tester);
|
|
|
|
expect(find.text('Today'), findsOneWidget);
|
|
expect(find.text('Legs'), findsOneWidget);
|
|
expect(find.text('Ended early'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Legs'));
|
|
await settle(tester);
|
|
|
|
expect(find.text('Set 1 · 10 reps'), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('switching a Reminder off in Settings drops its notifications', (tester) async {
|
|
await withClock(Clock.fixed(noon), () async {
|
|
usePhoneScreen(tester);
|
|
await pumpApp(tester, plans: plans, sessions: sessions, notifier: notifier);
|
|
await tester.tap(find.byTooltip('Settings'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('19:00'), findsOneWidget);
|
|
expect(find.text('22:30'), findsOneWidget);
|
|
|
|
await tester.tap(find.byType(Switch).at(1));
|
|
await settle(tester);
|
|
|
|
expect(notifier.permissionRequests, 0);
|
|
expect(notifier.scheduled, isNotEmpty);
|
|
expect(notifier.scheduled.every((r) => r.$1.hour == 19), isTrue);
|
|
});
|
|
});
|
|
}
|