Files
ZenXercise/test/support/test_app.dart
T
CedricandClaude Opus 5.5 971ff64117 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>
2026-10-29 00:20:16 +01:00

102 lines
3.3 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
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';
Exercise exercise(
String id,
Mode mode, {
String? name,
List<String> primary = const ['hamstrings'],
List<String> secondary = const [],
String? equipment = 'body only',
String level = 'beginner',
List<String> instructions = const ['Breathe.'],
int imageCount = 0,
}) => Exercise(
id: id,
name: name ?? id.replaceAll('_', ' '),
mode: mode,
category: mode == Mode.stretching ? 'stretching' : 'strength',
level: level,
equipment: equipment,
primaryMuscles: primary,
secondaryMuscles: secondary,
instructions: instructions,
imageCount: imageCount,
);
/// Two stretches and a squat. The 90/90 Hamstring's images exist in the real assets.
final sampleLibrary = ExerciseLibrary([
exercise(
'90_90_Hamstring',
Mode.stretching,
name: '90/90 Hamstring',
secondary: const ['calves'],
instructions: const ['Lie on your back.', 'Straighten one leg.'],
imageCount: 2,
),
exercise('Calf_Stretch', Mode.stretching, primary: const ['calves'], equipment: 'other', level: 'intermediate'),
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) {
tester.view.physicalSize = const Size(1080, 2400);
tester.view.devicePixelRatio = 2.75;
addTearDown(tester.view.reset);
}
/// The whole app over [library], with preferences starting from [prefs].
Future<SettingsController> pumpApp(
WidgetTester tester, {
ExerciseLibrary? library,
Map<String, Object> prefs = const {},
PlanRepository? plans,
SessionRepository? sessions,
PlayerEffects? effects,
}) async {
SharedPreferences.setMockInitialValues(prefs);
final settings = SettingsController(SettingsService());
await settings.loadSettings();
final db = testDatabase();
addTearDown(db.close);
await tester.pumpWidget(
MultiProvider(
providers: [
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(),
),
);
await tester.pumpAndSettle();
return settings;
}