Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions FIREBASE_TEST_CASES.yaml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
- displayName: Launch App and View Home Screen
id: launch_app
steps:
- goal: Open the app and verify the home screen displays
hint: Tap the app icon to launch
successCriteria: Screen shows "Workout Logger" title with dashboard cards showing workout statistics

- displayName: View Exercise Library
id: view_exercise_library
prerequisiteTestCaseId: launch_app
steps:
- goal: Navigate to the exercise library screen
hint: Tap on the "Exercises" tab or button in the navigation
successCriteria: Screen displays a list of exercises grouped by muscle groups
- goal: Search for a specific exercise
hint: Tap the search field and type "bench press"
successCriteria: Filtered list shows bench press exercise

- displayName: Start a Quick Workout
id: start_quick_workout
prerequisiteTestCaseId: launch_app
steps:
- goal: Navigate to start workout screen
hint: Tap "Start Workout" or "Quick Start" button on home screen
successCriteria: Screen shows "Select Exercises" or exercise selection interface
- goal: Select exercises for the workout
hint: Select at least 2 exercises from the list (e.g., Bench Press, Squats)
successCriteria: Selected exercises appear in the workout list
- goal: Start the workout session
hint: Tap "Start Workout" or "Begin" button
successCriteria: Workout flow screen displays with first exercise and set entry interface

- displayName: Log Exercise Sets
id: log_exercise_sets
prerequisiteTestCaseId: start_quick_workout
steps:
- goal: Enter weight and reps for a set
hint: Tap weight field, enter "100", tap reps field, enter "10"
successCriteria: Weight shows "100" and reps shows "10"
- goal: Add the set to the exercise log
hint: Tap "Add Set" or checkmark button
successCriteria: Set appears in the list showing "100 kg × 10 reps"
- goal: Add another set
hint: Enter weight "105" and reps "8", then tap "Add Set"
successCriteria: Second set appears showing "105 kg × 8 reps"
Comment on lines +42 to +45

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Success criteria assumes metric units.

The success criteria hardcode "100 kg × 10 reps" and "105 kg × 8 reps". If the app supports user-configurable units (lbs/kg) or localization, consider making these criteria unit-agnostic (e.g., "100 × 10" or regex patterns) to avoid test brittleness.

🤖 Prompt for AI Agents
In `@FIREBASE_TEST_CASES.yaml` around lines 42 - 45, The successCriteria entries
currently hardcode units ("100 kg × 10 reps" and "105 kg × 8 reps"), which
breaks when units/locales change; update the FIREBASE_TEST_CASES.yaml
successCriteria fields (the two successCriteria values associated with the first
set and the "Add another set" step) to be unit-agnostic—either strip unit text
(e.g., "100 × 10" / "105 × 8"), use a more generic phrase ("100 × 10 reps" /
"105 × 8 reps"), or replace them with regex-like patterns that allow kg or lb
and localized separators (e.g., "\d+\s*(kg|lb)?\s*×\s*\d+" or similar) so the
tests aren’t brittle across unit settings.


- displayName: Complete Workout
id: complete_workout
prerequisiteTestCaseId: log_exercise_sets
steps:
- goal: Navigate to the next exercise or finish workout
hint: Tap "Next Exercise" or "Finish Workout" button
successCriteria: Confirmation dialog appears asking to save workout
- goal: Save the completed workout
hint: Tap "Save & Finish" or "Save" button in the dialog
successCriteria: Screen returns to home showing success message and updated workout count

- displayName: View Workout History
id: view_workout_history
prerequisiteTestCaseId: complete_workout
steps:
- goal: Navigate to workout history screen
hint: Tap "History" tab or button in navigation
successCriteria: Screen displays list of completed workouts with dates
- goal: View details of a specific workout
hint: Tap on the most recent workout in the list
successCriteria: Workout details screen shows exercises, sets, weights, and reps for that session

- displayName: Create a New Routine
id: create_new_routine
prerequisiteTestCaseId: launch_app
steps:
- goal: Navigate to routines screen
hint: Tap "Routines" tab or button in navigation
successCriteria: Screen shows list of routines with "Create Routine" or "+" button
- goal: Start creating a new routine
hint: Tap "Create Routine" or "+" button
successCriteria: Routine creation screen appears with name input field
- goal: Enter routine name
hint: Tap name field and type "Push Day"
successCriteria: Name field shows "Push Day"
- goal: Add exercises to the routine
hint: Tap "Add Exercises" button and select 3-4 exercises (e.g., Bench Press, Shoulder Press, Tricep Dips)
successCriteria: Selected exercises appear in the routine exercise list
- goal: Save the routine
hint: Tap "Save" button
successCriteria: Screen returns to routines list showing the new "Push Day" routine

- displayName: Start Workout from Routine
id: start_workout_from_routine
prerequisiteTestCaseId: create_new_routine
steps:
- goal: Select a routine to start workout
hint: On routines screen, tap on "Push Day" routine
successCriteria: Routine details screen shows exercises in the routine
- goal: Start workout from this routine
hint: Tap "Start Workout" button
successCriteria: Workout flow screen displays with first exercise from the routine
Comment on lines +69 to +98

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding test cases for routine and history deletion.

The RoutineManager and HistoryManager support delete operations, but there are no test cases for:

  • Deleting a routine
  • Editing a routine
  • Deleting a workout from history

These would complement the existing coverage for create_new_routine and edit_workout_history.

🤖 Prompt for AI Agents
In `@FIREBASE_TEST_CASES.yaml` around lines 69 - 98, Add three new YAML test cases
to cover deletion and editing flows: create a test case id delete_routine that
has prerequisiteTestCaseId: create_new_routine and steps to open the "Push Day"
routine, tap "Delete" (confirm) and assert the routine no longer appears; add
edit_routine with prerequisiteTestCaseId: create_new_routine that opens the
routine, edits the name/exercises, saves, and asserts the updated details
display; and add delete_history_entry with prerequisiteTestCaseId:
edit_workout_history (or start_workout_from_routine) that navigates to History,
selects a workout, deletes it (confirm) and asserts it is removed; reference
RoutineManager and HistoryManager behaviors in hints/successCriteria to ensure
tests exercise the supported delete/edit operations.


- displayName: Add Custom Exercise
id: add_custom_exercise
prerequisiteTestCaseId: view_exercise_library
steps:
- goal: Navigate to add custom exercise screen
hint: On exercise library screen, tap "Add Custom Exercise" or "+" button
successCriteria: Custom exercise creation form appears
- goal: Enter exercise details
hint: Enter name "Cable Flyes", select category "Isolation", select primary muscle "Chest"
successCriteria: All fields are filled with entered values
- goal: Save the custom exercise
hint: Tap "Save" or "Create" button
successCriteria: Screen returns to exercise library with new exercise visible

- displayName: View Analytics Dashboard
id: view_analytics
prerequisiteTestCaseId: complete_workout
steps:
- goal: Navigate to analytics screen
hint: Tap "Analytics" or "Stats" tab in navigation
successCriteria: Screen displays workout statistics, charts, and progress metrics
- goal: View exercise-specific analytics
hint: Tap on an exercise from the list or chart
successCriteria: Detailed analytics for that exercise showing volume progression or performance trends

- displayName: Set a Fitness Target
id: set_fitness_target
prerequisiteTestCaseId: launch_app
steps:
- goal: Navigate to targets or goals section
hint: Look for "Targets", "Goals", or similar option in navigation or home screen
successCriteria: Targets screen displays with list of current targets and "Add Target" option
- goal: Create a new target
hint: Tap "Add Target" or "+" button
successCriteria: Target creation form appears
- goal: Set target details
hint: Select exercise "Bench Press", select type "Weight", enter target value "120"
successCriteria: Target form shows all entered values
- goal: Save the target
hint: Tap "Save" or "Create" button
successCriteria: Screen returns to targets list showing new target with progress indicator

- displayName: Edit Workout History
id: edit_workout_history
prerequisiteTestCaseId: view_workout_history
steps:
- goal: Open a workout for editing
hint: On workout details screen, tap "Edit" button or icon
successCriteria: Workout edit screen appears with editable sets and exercises
- goal: Modify a set's weight
hint: Tap on a set's weight value, change to a different value, and save
successCriteria: Updated weight value is displayed in the set list
- goal: Save changes
hint: Tap "Save" button
successCriteria: Screen returns to workout details showing updated values

- displayName: Delete Custom Exercise
id: delete_custom_exercise
prerequisiteTestCaseId: add_custom_exercise
steps:
- goal: Find the custom exercise
hint: On exercise library, locate the "Cable Flyes" custom exercise
successCriteria: Custom exercise is visible with "Custom" badge or indicator
- goal: Delete the custom exercise
hint: Long press or tap menu on the exercise, select "Delete"
successCriteria: Confirmation dialog appears
- goal: Confirm deletion
hint: Tap "Delete" or "Confirm" in the dialog
successCriteria: Exercise is removed from the library list

- displayName: Cancel Active Workout
id: cancel_active_workout
prerequisiteTestCaseId: start_quick_workout
steps:
- goal: Initiate workout cancellation
hint: During active workout, tap back button or menu and select "Cancel Workout"
successCriteria: Confirmation dialog appears warning that progress will not be saved
- goal: Confirm cancellation
hint: Tap "Cancel Workout" in the confirmation dialog
successCriteria: Returns to home screen without saving, workout count unchanged
Comment on lines +170 to +179

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider test isolation for branching prerequisite chains.

Both cancel_active_workout and log_exercise_sets depend on start_quick_workout, creating a branching execution path. Ensure your Firebase Test Lab configuration runs each test chain independently to avoid state conflicts (e.g., one test completing the workout while another tries to cancel it).

🤖 Prompt for AI Agents
In `@FIREBASE_TEST_CASES.yaml` around lines 170 - 179, The test chain branching
causes state conflicts: ensure each chain that references start_quick_workout
(e.g., cancel_active_workout and log_exercise_sets) executes in isolation by
either (1) making start_quick_workout idempotent and adding an explicit
teardown/reset step after it (clear workout state, return to home) or (2)
configuring Firebase Test Lab to run each prerequisite chain independently
(separate test matrices or isolated test runs for cancel_active_workout and
log_exercise_sets) so one chain’s completion cannot change the other's initial
state; update the FIREBASE_TEST_CASES.yaml entries for cancel_active_workout and
log_exercise_sets to include the chosen setup/teardown or to be executed in
isolated runs.


- displayName: App Navigation Flow
id: app_navigation_flow
prerequisiteTestCaseId: launch_app
steps:
- goal: Navigate to each main section
hint: Tap through all navigation tabs - Home, Exercises, Routines, History, Analytics
successCriteria: Each screen loads successfully and displays relevant content without errors
45 changes: 44 additions & 1 deletion SOLID_ANALYSIS_REPORT.md
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,49 @@
# SOLID Principles Analysis Report

This report provides a detailed analysis of the current Flutter codebase ("Workout Logger") against the SOLID principles. The analysis identifies areas where the code adheres to these principles and, more importantly, where it violates them, offering a roadmap for refactoring.
This report provides a detailed analysis of the Flutter codebase ("Workout Logger") against the SOLID principles.

## ✅ SOLID Refactoring Complete

The following refactoring has been implemented to address SOLID violations:

### Changes Made

#### 1. Dependency Inversion Principle (DIP)
- Created `IStorageService` interface ([storage_service_interface.dart](workout-logger/lib/services/interfaces/storage_service_interface.dart))
- Created `IMLService` interface ([ml_service_interface.dart](workout-logger/lib/services/interfaces/ml_service_interface.dart))
- Updated `StorageService` to implement `IStorageService`
- Updated `MLService` to implement `IMLService` (injectable, not static-only)
- Updated `WorkoutProvider` to depend on abstractions via constructor injection
- Updated `main.dart` to use composition root pattern for DI

#### 2. Single Responsibility Principle (SRP)
Created focused managers in `lib/services/managers/`:
- `ActiveWorkoutManager` - Current workout session state only
- `HistoryManager` - Past workout sessions only
- `RoutineManager` - Workout routines only
- `ExerciseManager` - Exercise library (built-in + custom)
- `TargetManager` - Goals and targets only
- `AnalyticsManager` - Statistics and recommendations only

#### 3. Open/Closed Principle (OCP)
- Created `TargetCalculatorStrategy` pattern ([target_calculator.dart](workout-logger/lib/services/strategies/target_calculator.dart))
- New target types can be added by implementing `TargetCalculatorStrategy` without modifying existing code
- New ML algorithms can be added by implementing `IMLService`

#### 4. Interface Segregation Principle (ISP)
- Split the monolithic `WorkoutProvider` into focused managers
- Each screen can now depend on only the managers it needs

#### 5. Liskov Substitution Principle (LSP)
- Created `MockStorageService` implementing `IStorageService` for testing
- Created `MockMLService` implementing `IMLService` for testing
- Both mocks can be substituted for their real implementations

---

## Original Analysis (for reference)

This section contains the original analysis that guided the refactoring.

## 1. Single Responsibility Principle (SRP)

Expand Down
48 changes: 32 additions & 16 deletions workout-logger/lib/main.dart
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,65 @@
// Main App Entry Point
//
// Following Dependency Inversion Principle: we create concrete implementations
// here at the composition root and inject them into high-level modules.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';

import 'services/storage_service.dart';
import 'services/ml_service.dart';
import 'services/interfaces/storage_service_interface.dart';
import 'services/interfaces/ml_service_interface.dart';
import 'services/workout_provider.dart';
import 'theme/app_theme.dart';
import 'screens/home_screen.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();

// Set preferred orientations
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);

// Set system overlay style
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor: AppTheme.backgroundColor,
systemNavigationBarIconBrightness: Brightness.light,
));
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
systemNavigationBarColor: AppTheme.backgroundColor,
systemNavigationBarIconBrightness: Brightness.light,
),
);

runApp(const WorkoutLoggerApp());
}

class WorkoutLoggerApp extends StatelessWidget {
// Singleton instances created once at app startup
// This ensures the same instances are used throughout the app lifecycle
static final IStorageService _storageService = StorageService();
static final IMLService _mlService = MLService();

const WorkoutLoggerApp({super.key});

@override
Widget build(BuildContext context) {
// Composition Root: Provide the singleton implementations
// This is the only place where we reference concrete implementations.
// All other code depends on abstractions (interfaces).
return MultiProvider(
providers: [
// Provide the storage service interface for direct access if needed
Provider<IStorageService>.value(value: _storageService),
// Provide the ML service interface for direct access if needed
Provider<IMLService>.value(value: _mlService),
// WorkoutProvider receives dependencies via constructor injection
ChangeNotifierProvider(
create: (_) => WorkoutProvider(StorageService()),
create: (_) =>
WorkoutProvider(_storageService, mlService: _mlService),
),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
],
child: MaterialApp(
Expand DownExpand Up@@ -85,11 +107,7 @@ class _AppInitializerState extends State<AppInitializer> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.error_outline,
size: 64,
color: AppTheme.error,
),
const Icon(Icons.error_outline, size: 64, color: AppTheme.error),
const SizedBox(height: 16),
Text(
'Failed to initialize app',
Expand DownExpand Up@@ -138,9 +156,7 @@ class _AppInitializerState extends State<AppInitializer> {
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 32),
const CircularProgressIndicator(
color: AppTheme.primaryColor,
),
const CircularProgressIndicator(color: AppTheme.primaryColor),
],
),
),
Expand Down
Loading