Stage 4: guided Player for both Modes and the Session summary
- buildSegments lays a Plan out as holds with Transitions (get ready, switch sides, next round, next exercise) or sets with count-up Rests; Supersets alternate members and rest per round unless asked otherwise - SessionRunner drives segments against an injected clock: auto-advance with a chime, pause, skip, back, rep sets confirmed with logged reps and kg, timed sets started on demand, early end - Player screen: countdown ring, accent pulse in the last 3 s, Flip Animation, next-up cards, Rest Screen, mute toggle, screen kept on - Sessions table (schema v2 migration) keeps a Plan snapshot and Set Logs; nothing is saved when no hold or set was done - Summary with time, exercises or sets logged, and every Set Log - Synthesised quiet cue sounds (tools/sounds), status bar per Mode Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bbdca95541
commit
971ff64117
28 files changed
+3465
-84
No files matched your search
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:zenxercise/src/database/database.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/player/session_runner.dart';
|
||||
|
||||
import '../support/test_database.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase db;
|
||||
late SessionRepository sessions;
|
||||
|
||||
setUp(() {
|
||||
db = testDatabase();
|
||||
sessions = SessionRepository(db);
|
||||
});
|
||||
tearDown(() => db.close());
|
||||
|
||||
Plan plan(Mode mode) => Plan(
|
||||
id: 'p1',
|
||||
mode: mode,
|
||||
name: 'Legs',
|
||||
items: const [WorkoutStep(exerciseId: 'Squat', sets: 1, target: WorkoutTarget.reps(10))],
|
||||
createdAt: DateTime(2026),
|
||||
updatedAt: DateTime(2026),
|
||||
);
|
||||
|
||||
SessionResult result(Mode mode, DateTime end, {bool complete = true}) => SessionResult(
|
||||
plan: plan(mode),
|
||||
startedAt: end.subtract(const Duration(minutes: 30)),
|
||||
endedAt: end,
|
||||
duration: const Duration(minutes: 28),
|
||||
complete: complete,
|
||||
setLogs: const [SetLog(exerciseId: 'Squat', set: 1, reps: 8, weightKg: 60)],
|
||||
);
|
||||
|
||||
test('a saved Session keeps its Plan snapshot, logs and outcome', () async {
|
||||
final saved = await sessions.save(result(Mode.workout, DateTime(2026, 10, 29, 19), complete: false));
|
||||
|
||||
expect(saved.planName, 'Legs');
|
||||
expect(saved.items.single, isA<WorkoutStep>());
|
||||
expect(saved.duration, const Duration(minutes: 28));
|
||||
expect(saved.complete, isFalse);
|
||||
expect(saved.setLogs.single.reps, 8);
|
||||
expect(saved.setLogs.single.weightKg, 60);
|
||||
});
|
||||
|
||||
test('Sessions are listed per Mode, the latest first', () async {
|
||||
await sessions.save(result(Mode.workout, DateTime(2026, 10, 28)));
|
||||
await sessions.save(result(Mode.workout, DateTime(2026, 10, 29)));
|
||||
await sessions.save(result(Mode.stretching, DateTime(2026, 10, 30)));
|
||||
|
||||
final workout = await sessions.watchSessions(Mode.workout).first;
|
||||
|
||||
expect(workout.map((s) => s.endedAt.day), [29, 28]);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:zenxercise/src/modes/mode.dart';
|
||||
import 'package:zenxercise/src/plans/models/plan.dart';
|
||||
import 'package:zenxercise/src/player/segments.dart';
|
||||
|
||||
Plan _plan(Mode mode, List<PlanItem> items, {int transition = 5}) => Plan(
|
||||
id: 'p',
|
||||
mode: mode,
|
||||
name: 'P',
|
||||
items: items,
|
||||
transitionSeconds: transition,
|
||||
createdAt: DateTime(2026),
|
||||
updatedAt: DateTime(2026),
|
||||
);
|
||||
|
||||
String _describe(Segment s) => switch (s) {
|
||||
HoldSegment(:final exerciseId, :final side, :final round) =>
|
||||
'hold $exerciseId${side == null ? '' : ' ${side.name}'} r$round',
|
||||
TransitionSegment(:final kind, :final seconds) => '${kind.name} ${seconds}s',
|
||||
SetSegment(:final exerciseId, :final set) => 'set $exerciseId $set',
|
||||
RestSegment() => 'rest',
|
||||
};
|
||||
|
||||
void main() {
|
||||
group('Stretching', () {
|
||||
test('holds every side and round with Transitions between, after a get-ready', () {
|
||||
final segments = buildSegments(
|
||||
_plan(Mode.stretching, const [
|
||||
StretchingStep(exerciseId: 'A', holdSeconds: 30, bothSides: true, rounds: 2),
|
||||
StretchingStep(exerciseId: 'B', holdSeconds: 20, bothSides: false, rounds: 1),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(segments.map(_describe), [
|
||||
'getReady 5s',
|
||||
'hold A left r1',
|
||||
'switchSides 5s',
|
||||
'hold A right r1',
|
||||
'nextRound 5s',
|
||||
'hold A left r2',
|
||||
'switchSides 5s',
|
||||
'hold A right r2',
|
||||
'nextExercise 5s',
|
||||
'hold B r1',
|
||||
]);
|
||||
});
|
||||
|
||||
test('a pause of 0 s means no Transitions at all', () {
|
||||
final segments = buildSegments(
|
||||
_plan(Mode.stretching, const [
|
||||
StretchingStep(exerciseId: 'A', holdSeconds: 30, bothSides: true, rounds: 1),
|
||||
], transition: 0),
|
||||
);
|
||||
|
||||
expect(segments.map(_describe), ['hold A left r1', 'hold A right r1']);
|
||||
});
|
||||
});
|
||||
|
||||
group('Workout', () {
|
||||
test('each set is followed by a Rest, except the very last', () {
|
||||
final segments = buildSegments(
|
||||
_plan(Mode.workout, const [
|
||||
WorkoutStep(exerciseId: 'A', sets: 2, target: WorkoutTarget.reps(10)),
|
||||
WorkoutStep(exerciseId: 'B', sets: 1, target: WorkoutTarget.seconds(30)),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(segments.map(_describe), ['set A 1', 'rest', 'set A 2', 'rest', 'set B 1']);
|
||||
});
|
||||
|
||||
test('a Superset alternates its members and rests after each round only', () {
|
||||
final segments = buildSegments(
|
||||
_plan(Mode.workout, const [
|
||||
Superset(
|
||||
sets: 2,
|
||||
restBetween: false,
|
||||
members: [
|
||||
SupersetMember(exerciseId: 'A', target: WorkoutTarget.reps(10)),
|
||||
SupersetMember(exerciseId: 'B', target: WorkoutTarget.reps(12)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(segments.map(_describe), ['set A 1', 'set B 1', 'rest', 'set A 2', 'set B 2']);
|
||||
});
|
||||
|
||||
test('with rest between, a Superset also rests between its members', () {
|
||||
final segments = buildSegments(
|
||||
_plan(Mode.workout, const [
|
||||
Superset(
|
||||
sets: 1,
|
||||
restBetween: true,
|
||||
members: [
|
||||
SupersetMember(exerciseId: 'A', target: WorkoutTarget.reps(10)),
|
||||
SupersetMember(exerciseId: 'B', target: WorkoutTarget.reps(12)),
|
||||
],
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(segments.map(_describe), ['set A 1', 'rest', 'set B 1']);
|
||||
});
|
||||
|
||||
test('a set carries its target, weight and position in the Step', () {
|
||||
final set =
|
||||
buildSegments(
|
||||
_plan(Mode.workout, const [
|
||||
WorkoutStep(exerciseId: 'A', sets: 3, target: WorkoutTarget.reps(8), weightKg: 40),
|
||||
]),
|
||||
)[2]
|
||||
as SetSegment;
|
||||
|
||||
expect(set.target, const WorkoutTarget.reps(8));
|
||||
expect(set.weightKg, 40);
|
||||
expect((set.set, set.sets), (2, 3));
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:zenxercise/src/modes/mode.dart';
|
||||
import 'package:zenxercise/src/plans/models/plan.dart';
|
||||
import 'package:zenxercise/src/player/segments.dart';
|
||||
import 'package:zenxercise/src/player/session_runner.dart';
|
||||
|
||||
Plan _plan(Mode mode, List<PlanItem> items, {int transition = 5}) => Plan(
|
||||
id: 'p',
|
||||
mode: mode,
|
||||
name: 'Plan',
|
||||
items: items,
|
||||
transitionSeconds: transition,
|
||||
createdAt: DateTime(2026),
|
||||
updatedAt: DateTime(2026),
|
||||
);
|
||||
|
||||
final t0 = DateTime(2026, 10, 29, 7);
|
||||
DateTime at(int seconds) => t0.add(Duration(seconds: seconds));
|
||||
|
||||
void main() {
|
||||
group('Stretching runs by itself', () {
|
||||
late SessionRunner runner;
|
||||
var cues = 0;
|
||||
|
||||
setUp(() {
|
||||
cues = 0;
|
||||
runner = SessionRunner(
|
||||
_plan(Mode.stretching, const [StretchingStep(exerciseId: 'A', holdSeconds: 30, bothSides: true, rounds: 1)]),
|
||||
startedAt: t0,
|
||||
onCue: () => cues++,
|
||||
);
|
||||
});
|
||||
|
||||
test('counting down a get-ready, two holds and the switch between them', () {
|
||||
expect(runner.current, isA<TransitionSegment>());
|
||||
expect(runner.remaining(at(2)), const Duration(seconds: 3));
|
||||
|
||||
runner.tick(at(5));
|
||||
expect(runner.current, isA<HoldSegment>().having((h) => h.side, 'side', Side.left));
|
||||
|
||||
runner.tick(at(35));
|
||||
expect(runner.current, isA<TransitionSegment>().having((t) => t.kind, 'kind', TransitionKind.switchSides));
|
||||
|
||||
runner.tick(at(40));
|
||||
runner.tick(at(70));
|
||||
expect(runner.isFinished, isTrue);
|
||||
expect(runner.isComplete, isTrue);
|
||||
expect(cues, 4);
|
||||
});
|
||||
|
||||
test('pausing stops the clock', () {
|
||||
runner.tick(at(5));
|
||||
runner.pause(at(10));
|
||||
runner.tick(at(100));
|
||||
expect(runner.current, isA<HoldSegment>());
|
||||
expect(runner.remaining(at(100)), const Duration(seconds: 25));
|
||||
|
||||
runner.resume(at(100));
|
||||
expect(runner.remaining(at(110)), const Duration(seconds: 15));
|
||||
});
|
||||
|
||||
test('skipping and going back move between holds, passing over Transitions', () {
|
||||
runner.skip(at(1));
|
||||
expect(runner.current, isA<HoldSegment>().having((h) => h.side, 'side', Side.left));
|
||||
runner.skip(at(2));
|
||||
runner.skip(at(3));
|
||||
expect(runner.current, isA<HoldSegment>().having((h) => h.side, 'side', Side.right));
|
||||
|
||||
runner.back(at(4));
|
||||
expect(runner.current, isA<HoldSegment>().having((h) => h.side, 'side', Side.left));
|
||||
expect(runner.remaining(at(4)), const Duration(seconds: 30));
|
||||
});
|
||||
|
||||
test('there is progress worth saving only once a hold has ended', () {
|
||||
expect(runner.hasProgress, isFalse);
|
||||
runner.tick(at(5));
|
||||
// Skipping the left side is not progress; the right side starts after its switch at 15 s.
|
||||
runner.skip(at(10));
|
||||
runner.tick(at(15));
|
||||
expect(runner.hasProgress, isFalse);
|
||||
runner.tick(at(45));
|
||||
expect(runner.hasProgress, isTrue);
|
||||
});
|
||||
|
||||
test('ending early finishes incomplete', () {
|
||||
runner.end(at(12));
|
||||
|
||||
expect(runner.isFinished, isTrue);
|
||||
expect(runner.isComplete, isFalse);
|
||||
expect(runner.result(at(12)).duration, const Duration(seconds: 12));
|
||||
});
|
||||
});
|
||||
|
||||
group('Workout runs at the user\'s pace', () {
|
||||
final plan = _plan(Mode.workout, const [
|
||||
WorkoutStep(exerciseId: 'Squat', sets: 2, target: WorkoutTarget.reps(10), weightKg: 60),
|
||||
WorkoutStep(exerciseId: 'Plank', sets: 1, target: WorkoutTarget.seconds(30)),
|
||||
]);
|
||||
|
||||
test('a rep set waits for "Set done", then the Rest counts up until the next set starts', () {
|
||||
final runner = SessionRunner(plan, startedAt: t0);
|
||||
|
||||
runner.tick(at(600));
|
||||
expect(runner.current, isA<SetSegment>());
|
||||
|
||||
runner.completeSet(reps: 8, weightKg: 62.5, now: at(40));
|
||||
expect(runner.hasProgress, isTrue);
|
||||
expect(runner.current, isA<RestSegment>());
|
||||
expect(runner.elapsed(at(112)), const Duration(seconds: 72));
|
||||
|
||||
runner.startNext(at(120));
|
||||
expect(runner.current, isA<SetSegment>().having((s) => s.set, 'set', 2));
|
||||
});
|
||||
|
||||
test('a timed set starts on demand, counts down and logs itself', () {
|
||||
var cues = 0;
|
||||
final runner = SessionRunner(plan, startedAt: t0, onCue: () => cues++);
|
||||
runner.completeSet(reps: 10, weightKg: 60, now: at(30));
|
||||
runner.startNext(at(90));
|
||||
runner.completeSet(reps: 10, weightKg: 60, now: at(120));
|
||||
runner.startNext(at(180));
|
||||
|
||||
expect(runner.isWaitingToStart, isTrue);
|
||||
runner.tick(at(500));
|
||||
expect(runner.current, isA<SetSegment>());
|
||||
|
||||
runner.startTimer(at(500));
|
||||
runner.tick(at(530));
|
||||
|
||||
expect(runner.isFinished, isTrue);
|
||||
expect(cues, 1);
|
||||
expect(runner.result(at(530)).setLogs.map((l) => (l.exerciseId, l.set, l.reps, l.seconds, l.weightKg)), [
|
||||
('Squat', 1, 10, null, 60.0),
|
||||
('Squat', 2, 10, null, 60.0),
|
||||
('Plank', 1, null, 30, null),
|
||||
]);
|
||||
});
|
||||
|
||||
test('redoing a set after going back replaces its log', () {
|
||||
final runner = SessionRunner(plan, startedAt: t0);
|
||||
runner.completeSet(reps: 6, weightKg: 60, now: at(30));
|
||||
|
||||
runner.back(at(40));
|
||||
runner.completeSet(reps: 10, weightKg: 60, now: at(60));
|
||||
|
||||
expect(runner.result(at(60)).setLogs.single.reps, 10);
|
||||
});
|
||||
|
||||
test('a skipped set is not logged', () {
|
||||
final runner = SessionRunner(plan, startedAt: t0);
|
||||
runner.skip(at(10));
|
||||
runner.skip(at(11));
|
||||
|
||||
expect(runner.current, isA<SetSegment>().having((s) => s.set, 'set', 2));
|
||||
expect(runner.result(at(11)).setLogs, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import 'package:zenxercise/src/app.dart';
|
||||
import 'package:zenxercise/src/exercises/exercise.dart';
|
||||
import 'package:zenxercise/src/exercises/exercise_library.dart';
|
||||
import 'package:zenxercise/src/modes/mode.dart';
|
||||
import 'package:zenxercise/src/history/session_repository.dart';
|
||||
import 'package:zenxercise/src/plans/plan_repository.dart';
|
||||
import 'package:zenxercise/src/player/player_effects.dart';
|
||||
import 'package:zenxercise/src/settings/settings_controller.dart';
|
||||
|
||||
import 'test_database.dart';
|
||||
@@ -48,6 +50,18 @@ final sampleLibrary = ExerciseLibrary([
|
||||
exercise('Barbell_Squat', Mode.workout, primary: const ['quadriceps'], equipment: 'barbell'),
|
||||
]);
|
||||
|
||||
/// Counts chimes instead of playing them and leaves the screen alone.
|
||||
class FakePlayerEffects implements PlayerEffects {
|
||||
final chimes = <Mode>[];
|
||||
bool screenOn = false;
|
||||
|
||||
@override
|
||||
Future<void> chime(Mode mode) async => chimes.add(mode);
|
||||
|
||||
@override
|
||||
Future<void> keepScreenOn(bool on) async => screenOn = on;
|
||||
}
|
||||
|
||||
/// A Pixel-sized screen instead of the default 800×600, so the floating
|
||||
/// navigation bar does not cover what a test taps.
|
||||
void usePhoneScreen(WidgetTester tester) {
|
||||
@@ -62,6 +76,8 @@ Future<SettingsController> pumpApp(
|
||||
ExerciseLibrary? library,
|
||||
Map<String, Object> prefs = const {},
|
||||
PlanRepository? plans,
|
||||
SessionRepository? sessions,
|
||||
PlayerEffects? effects,
|
||||
}) async {
|
||||
SharedPreferences.setMockInitialValues(prefs);
|
||||
final settings = SettingsController(SettingsService());
|
||||
@@ -74,6 +90,8 @@ Future<SettingsController> pumpApp(
|
||||
ChangeNotifierProvider.value(value: settings),
|
||||
Provider.value(value: library ?? sampleLibrary),
|
||||
Provider.value(value: plans ?? PlanRepository(db)),
|
||||
Provider.value(value: sessions ?? SessionRepository(db)),
|
||||
Provider<PlayerEffects>.value(value: effects ?? FakePlayerEffects()),
|
||||
],
|
||||
child: const ZenXerciseApp(),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
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/views/player_view.dart';
|
||||
|
||||
import '../support/test_app.dart';
|
||||
import '../support/test_database.dart';
|
||||
|
||||
void main() {
|
||||
late PlanRepository plans;
|
||||
late SessionRepository sessions;
|
||||
late FakePlayerEffects effects;
|
||||
|
||||
setUp(() {
|
||||
final db = testDatabase();
|
||||
addTearDown(db.close);
|
||||
plans = PlanRepository(db);
|
||||
sessions = SessionRepository(db);
|
||||
effects = FakePlayerEffects();
|
||||
});
|
||||
|
||||
Future<void> startPlan(WidgetTester tester, Mode mode, List<PlanItem> items) async {
|
||||
final plan = await plans.create(mode, 'Routine');
|
||||
await plans.save(plan.copyWith(items: items));
|
||||
usePhoneScreen(tester);
|
||||
await pumpApp(tester, plans: plans, sessions: sessions, effects: effects, prefs: {'mode': mode.name});
|
||||
await tester.tap(find.byTooltip('Start'));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
/// Drift's streams need real time to deliver, outside the test's fake clock.
|
||||
Future<List<Session>> sessionsOf(WidgetTester tester, Mode mode) async =>
|
||||
(await tester.runAsync(() => sessions.watchSessions(mode).first))!;
|
||||
|
||||
/// The Player's ticker never lets frames settle, so it is pumped for a fixed time.
|
||||
Future<void> settle(WidgetTester tester) async {
|
||||
for (var i = 0; i < 8; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> wait(WidgetTester tester, int seconds) async {
|
||||
for (var i = 0; i < seconds * 5; i++) {
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
}
|
||||
}
|
||||
|
||||
testWidgets('a Stretching Session counts down, chimes, keeps the screen on and ends on its summary', (tester) async {
|
||||
await startPlan(tester, Mode.stretching, const [
|
||||
StretchingStep(exerciseId: 'Calf_Stretch', holdSeconds: 10, bothSides: false, rounds: 1),
|
||||
]);
|
||||
|
||||
expect(find.text('Get ready'), findsOneWidget);
|
||||
expect(effects.screenOn, isTrue);
|
||||
|
||||
// After the get-ready and the 0.7 s crossfade into the hold.
|
||||
await wait(tester, 5);
|
||||
await settle(tester);
|
||||
expect(find.text('Calf Stretch'), findsOneWidget);
|
||||
expect(find.text('Get ready'), findsNothing);
|
||||
|
||||
await wait(tester, 10);
|
||||
await settle(tester);
|
||||
|
||||
expect(find.text('Session complete'), findsOneWidget);
|
||||
expect(effects.chimes, [Mode.stretching, Mode.stretching]);
|
||||
expect(effects.screenOn, isFalse);
|
||||
expect((await sessionsOf(tester, Mode.stretching)).single.complete, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('a Workout set is confirmed with its logged reps, then rested and repeated', (tester) async {
|
||||
await startPlan(tester, Mode.workout, const [
|
||||
WorkoutStep(exerciseId: 'Barbell_Squat', sets: 2, target: WorkoutTarget.reps(10), weightKg: 60),
|
||||
]);
|
||||
|
||||
expect(find.text('Set 1 of 2'), findsOneWidget);
|
||||
expect(find.text('10 reps · 60 kg'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Set done'));
|
||||
await settle(tester);
|
||||
await tester.tap(find.byTooltip('Reps −'));
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Save set'));
|
||||
await settle(tester);
|
||||
|
||||
expect(find.text('Rest'), findsOneWidget);
|
||||
await wait(tester, 3);
|
||||
expect(find.text('0:03'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Start next set'));
|
||||
await settle(tester);
|
||||
await tester.tap(find.text('Set done'));
|
||||
await settle(tester);
|
||||
await tester.tap(find.text('Save set'));
|
||||
await settle(tester);
|
||||
|
||||
expect(find.text('Session complete'), findsOneWidget);
|
||||
expect(find.text('Set 1 · 9 reps · 60 kg'), findsOneWidget);
|
||||
expect(find.text('Set 2 · 10 reps · 60 kg'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('ending early after a set saves an unfinished Session', (tester) async {
|
||||
await startPlan(tester, Mode.workout, const [
|
||||
WorkoutStep(exerciseId: 'Barbell_Squat', sets: 3, target: WorkoutTarget.reps(10)),
|
||||
]);
|
||||
await tester.tap(find.text('Set done'));
|
||||
await settle(tester);
|
||||
await tester.tap(find.text('Save set'));
|
||||
await settle(tester);
|
||||
|
||||
await tester.tap(find.byTooltip('End session'));
|
||||
await settle(tester);
|
||||
await tester.tap(find.text('End session').last);
|
||||
await settle(tester);
|
||||
|
||||
expect(find.text('Ended early'), findsOneWidget);
|
||||
expect((await sessionsOf(tester, Mode.workout)).single.complete, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('skipping through everything leaves no Session behind', (tester) async {
|
||||
await startPlan(tester, Mode.workout, const [
|
||||
WorkoutStep(exerciseId: 'Barbell_Squat', sets: 1, target: WorkoutTarget.reps(10)),
|
||||
]);
|
||||
|
||||
await tester.tap(find.byTooltip('Skip'));
|
||||
await settle(tester);
|
||||
|
||||
expect(find.byType(PlayerView), findsNothing);
|
||||
expect(await sessionsOf(tester, Mode.workout), isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('ending before anything is done leaves no Session behind', (tester) async {
|
||||
await startPlan(tester, Mode.workout, const [
|
||||
WorkoutStep(exerciseId: 'Barbell_Squat', sets: 3, target: WorkoutTarget.reps(10)),
|
||||
]);
|
||||
|
||||
await tester.tap(find.byTooltip('End session'));
|
||||
await settle(tester);
|
||||
await tester.tap(find.text('End session').last);
|
||||
await settle(tester);
|
||||
|
||||
expect(find.byType(PlayerView), findsNothing);
|
||||
expect(await sessionsOf(tester, Mode.workout), isEmpty);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user