Files
ZenXercise/test/views/plan_editor_test.dart
T
CedricandClaude Opus 5.5 bbdca95541 Stage 3: Plan editor with Steps, Supersets and reordering
- Plan model with immutable edits: add with Mode defaults, move, remove,
  Superset with next, take a member out, ungroup; Stretching length
  estimate from holds, sides, rounds and Transitions
- drift database (plans table, items as JSON, ADR 0005) and
  PlanRepository; Plans list per Mode with rename/duplicate/delete
- Editor: drag handles, step editor sheet (sets, reps or time, optional
  kg; hold, both sides, rounds; Superset sets and rest between),
  pause between holds for Stretching Plans
- Exercise picker reusing the Library browser; Add to plan from the
  Exercise detail page
- Theme: Mode-styled snackbar, FAB, menus, segmented buttons, switches

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:48:07 +01:00

134 lines
4.8 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/material.dart';
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/plans/plan_repository.dart';
import '../support/test_app.dart';
import '../support/test_database.dart';
void main() {
late PlanRepository plans;
setUp(() {
final db = testDatabase();
addTearDown(db.close);
plans = PlanRepository(db);
});
/// A Workout Plan with two Squat Steps, open in the editor.
Future<Plan> openWorkoutPlan(WidgetTester tester) async {
final plan = await plans.create(Mode.workout, 'Legs');
await plans.save(plan.withExerciseAdded('Barbell_Squat').withExerciseAdded('Barbell_Squat'));
usePhoneScreen(tester);
await pumpApp(tester, plans: plans, prefs: {'mode': 'workout'});
await tester.tap(find.text('Legs'));
await tester.pumpAndSettle();
return plan;
}
testWidgets('a new Plan is named, filled from the picker and listed with its count', (tester) async {
usePhoneScreen(tester);
await pumpApp(tester, plans: plans);
await tester.tap(find.text('New plan'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Morning');
await tester.pump();
await tester.tap(find.text('Create'));
await tester.pumpAndSettle();
expect(find.text('Morning'), findsOneWidget);
expect(find.text('No exercises yet'), findsWidgets);
await tester.tap(find.text('Add exercises'));
await tester.pumpAndSettle();
await tester.tap(find.text('90/90 Hamstring'));
await tester.pumpAndSettle();
await tester.tap(find.text('Calf Stretch'));
await tester.pumpAndSettle();
await tester.tap(find.text('Done'));
await tester.pumpAndSettle();
expect(find.text('90/90 Hamstring'), findsOneWidget);
expect(find.text('Calf Stretch'), findsOneWidget);
// Two 30 s holds and one 5 s Transition.
expect(find.text('2 exercises · about 1 min'), findsOneWidget);
await tester.pageBack();
await tester.pumpAndSettle();
expect(find.text('Morning'), findsOneWidget);
expect(find.text('2 exercises · about 1 min'), findsOneWidget);
});
testWidgets('editing a Step saves at once and shows in its summary', (tester) async {
final plan = await openWorkoutPlan(tester);
await tester.tap(find.text('Barbell Squat').first);
await tester.pumpAndSettle();
await tester.tap(find.byTooltip('Sets +'));
await tester.pumpAndSettle();
await tester.tap(find.text('Add a target weight'));
await tester.pumpAndSettle();
await tester.tap(find.text('Done'));
await tester.pumpAndSettle();
expect(find.text('4 × 10 reps · 20 kg'), findsOneWidget);
expect((await plans.get(plan.id))!.items.first, isA<WorkoutStep>().having((s) => s.sets, 'sets', 4));
});
testWidgets('two Steps become a Superset and split again', (tester) async {
await openWorkoutPlan(tester);
// The first Step's menu; the Plan's own menu in the app bar comes last.
await tester.tap(find.byTooltip('More actions').first);
await tester.pumpAndSettle();
await tester.tap(find.text('Superset with next'));
await tester.pumpAndSettle();
expect(find.text('Superset · 3 sets'), findsOneWidget);
expect(find.text('10 reps'), findsNWidgets(2));
await tester.tap(find.byTooltip('More actions').first);
await tester.pumpAndSettle();
await tester.tap(find.text('Ungroup'));
await tester.pumpAndSettle();
expect(find.text('Superset · 3 sets'), findsNothing);
expect(find.text('3 × 10 reps'), findsNWidgets(2));
});
testWidgets('deleting the Plan from its editor returns to the list', (tester) async {
await openWorkoutPlan(tester);
await tester.tap(find.byTooltip('More actions').last);
await tester.pumpAndSettle();
await tester.tap(find.text('Delete'));
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(FilledButton, 'Delete'));
await tester.pumpAndSettle();
expect(find.text('Legs'), findsNothing);
expect(find.text('No plans yet'), findsOneWidget);
});
testWidgets('an Exercise is added to a Plan from its detail page', (tester) async {
final plan = await plans.create(Mode.stretching, 'Evening');
usePhoneScreen(tester);
await pumpApp(tester, plans: plans);
await tester.tap(find.byTooltip('Library'));
await tester.pumpAndSettle();
await tester.tap(find.text('Calf Stretch'));
await tester.pumpAndSettle();
await tester.tap(find.text('Add to plan'));
await tester.pumpAndSettle();
await tester.tap(find.text('Evening'));
await tester.pumpAndSettle();
expect(find.text('Added to Evening'), findsOneWidget);
expect((await plans.get(plan.id))!.items.single, isA<StretchingStep>());
});
}