Skip to content

Commit

Permalink
comments and automated test
Browse files Browse the repository at this point in the history
  • Loading branch information
lcorkuni committed May 16, 2024
1 parent ceae309 commit 0cf7e37
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 8 deletions.
27 changes: 20 additions & 7 deletions client/src/pages/WorkoutEditorPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default function WorkoutEditor() {
const navigate = useNavigate();
const { email, isLoggedIn } = useAuth();

// Render a message if the user is not logged in
if (!isLoggedIn) {
return (
<Container>
Expand All @@ -31,6 +32,7 @@ export default function WorkoutEditor() {
);
}

// Extract workoutId from query params
const searchParams = new URLSearchParams(location.search);
const workoutId = searchParams.get('workoutId');

Expand All @@ -40,16 +42,19 @@ export default function WorkoutEditor() {
const [workoutData, setWorkoutData] = useState([]);
const [exerciseData, setExerciseData] = useState([]);

// Function to add a new exercise field
const handleAddExercise = () => {
setExercises([...exercises, { exerciseName: '', sets: '', reps: '', targetWeight: '' }]);
};

// Function to handle changes in exercise fields
const handleExerciseChange = (event, index, field) => {
const newExercises = [...exercises];
newExercises[index][field] = event.target.value;
setExercises(newExercises);
};

// Function to delete an exercise field
const handleDeleteExercise = (index) => {
if (index > 0) {
const newExercises = [...exercises];
Expand All @@ -58,20 +63,22 @@ export default function WorkoutEditor() {
}
};

// Function to apply changes to the workout
const handleApplyChanges = async () => {
try {
// Check if any field is blank
// Check for empty fields
if (!workoutName || exercises.some(exercise => !exercise.exerciseName || !exercise.sets || !exercise.reps || !exercise.targetWeight)) {
alert('Please fill in all fields.');
return;
}

// Validate numeric fields
if (exercises.some(exercise => isNaN(parseInt(exercise.sets)) || parseInt(exercise.sets) < 1 || parseInt(exercise.sets) > 10 || isNaN(parseInt(exercise.reps)) || parseInt(exercise.reps) < 1 || parseInt(exercise.reps) > 50 || isNaN(parseInt(exercise.targetWeight)) || parseInt(exercise.targetWeight) < 1)) {
alert('Please enter valid values for sets (1-10), reps (1-50), and target weight (greater than 1).');
alert('Please enter valid values for sets (1-10), reps (1-50). All numbers must also be < 1.');
return;
}

// Fetch user data
const user_response = await fetch(`/api/getCollection?collection=users`, {
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
Expand All @@ -81,6 +88,7 @@ const handleApplyChanges = async () => {
const user = userData.find(user => user.email === email);
const userId = user ? user._id : null;

// Refine exercises data
const refinedExercises = await Promise.all(exercises.map(async (exercise) => {
if (!exercise.exerciseId) {
const matchingExercise = exerciseData.find((ex) => ex.exerciseName === exercise.exerciseName);
Expand All @@ -98,6 +106,7 @@ const handleApplyChanges = async () => {
return { ...exercise };
}));

// Prepare request body
const requestBody = {
workoutName,
exercises: refinedExercises,
Expand Down Expand Up @@ -135,7 +144,7 @@ const handleApplyChanges = async () => {
}
};


// Fetch workout and exercise data on component mount
useEffect(() => {
const fetchData = async () => {
try {
Expand Down Expand Up @@ -187,6 +196,7 @@ const handleApplyChanges = async () => {
fetchData();
}, []);

// Populate form fields if editing existing workout
useEffect(() => {
if (workoutId && workoutData.length > 0) {
const foundWorkout = workoutData.find((workout) => workout._id === workoutId);
Expand All @@ -211,6 +221,7 @@ const handleApplyChanges = async () => {
fullWidth
value={workoutName}
onChange={(event) => setWorkoutName(event.target.value)}
inputProps={{ "data-testid": "workout-name-input" }}
/>
</Grid>
<Grid item xs={12}>
Expand All @@ -225,6 +236,7 @@ const handleApplyChanges = async () => {
<Select
required
value={exercise.exerciseName}
inputProps={{ "data-testid": "exercise-dropdown-" + index}}
onChange={(event) => handleExerciseChange(event, index, 'exerciseName')}
>
<MenuItem value="">None</MenuItem>
Expand All @@ -243,7 +255,7 @@ const handleApplyChanges = async () => {
variant="outlined"
fullWidth
type="number"
inputProps={{ min: 1, max: 10 }}
inputProps={{ min: 1, max: 10, "data-testid": "sets-input-" + index}}
value={exercise.sets}
onChange={(event) => handleExerciseChange(event, index, 'sets')}
/>
Expand All @@ -255,7 +267,7 @@ const handleApplyChanges = async () => {
variant="outlined"
fullWidth
type="number"
inputProps={{ min: 1, max: 50 }}
inputProps={{ min: 1, max: 50, "data-testid": "reps-input-" + index}}
value={exercise.reps}
onChange={(event) => handleExerciseChange(event, index, 'reps')}
/>
Expand All @@ -268,12 +280,13 @@ const handleApplyChanges = async () => {
fullWidth
type="number"
value={exercise.targetWeight}
inputProps={{ "data-testid": "weight-input-" + index}}
onChange={(event) => handleExerciseChange(event, index, 'targetWeight')}
/>
</Grid>
{index > 0 && (
<Grid item xs={2}>
<Button variant="outlined" color="error" style={{ height: '100%' }} onClick={() => handleDeleteExercise(index)}>
<Button variant="outlined" color="error" style={{ height: '100%' }} inputProps={{ "data-testid": "delete-exercise-" + index}} onClick={() => handleDeleteExercise(index)}>
Delete Exercise
</Button>
</Grid>
Expand All @@ -283,7 +296,7 @@ const handleApplyChanges = async () => {
</Grid>
<Grid container justifyContent="center" mt={2}>
<Grid item>
<Button variant="contained" color="secondary" size="large" sx={{ width: '300px', marginBottom: '40px' }} onClick={handleAddExercise}>
<Button variant="contained" color="secondary" size="large" sx={{ width: '300px', marginBottom: '40px' }} inputProps={{ "data-testid": "add-exercise"}} onClick={handleAddExercise}>
Add Exercise
</Button>
</Grid>
Expand Down
6 changes: 6 additions & 0 deletions client/src/pages/WorkoutTrackerPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default function WorkoutTracker() {
const searchParams = new URLSearchParams(location.search);
const workoutId = searchParams.get('workoutId')

// Returning early if the user is not logged in or workout ID is missing
if (!isLoggedIn || !workoutId) {
return (
<Container>
Expand All @@ -41,6 +42,7 @@ export default function WorkoutTracker() {
const [exercises, setExercises] = useState([]);
const [workoutData, setWorkoutData] = useState([]);

// Effect hook to fetch data on component mount
useEffect(() => {
const fetchData = async () => {
try {
Expand Down Expand Up @@ -93,6 +95,7 @@ export default function WorkoutTracker() {
fetchData();
}, []);

// Effect hook to update workout details when workoutId or workoutData changes
useEffect(() => {
if (workoutId && workoutData.length > 0) {
const foundWorkout = workoutData.find((workout) => workout._id === workoutId);
Expand Down Expand Up @@ -272,6 +275,7 @@ const handleLogWorkout = async () => {
type="number"
variant="outlined"
value={exercise.repsDone[setIndex]}
inputProps={{ "data-testid": "reps-input-" + index + "-" + setIndex}}
onChange={(e) => {
const updatedExercises = [...exercises];
updatedExercises[index].repsDone[setIndex] = e.target.value;
Expand All @@ -284,6 +288,7 @@ const handleLogWorkout = async () => {
type="number"
variant="outlined"
value={exercise.weightDone[setIndex]}
inputProps={{ "data-testid": "weight-input-" + index + "-" + setIndex}}
onChange={(e) => {
const updatedExercises = [...exercises];
updatedExercises[index].weightDone[setIndex] = e.target.value;
Expand All @@ -300,6 +305,7 @@ const handleLogWorkout = async () => {
type="number"
variant="outlined"
value={exercise.nextTargetWeight}
inputProps={{ "data-testid": "target-input-" + index}}
onChange={(e) => {
const updatedExercises = [...exercises];
updatedExercises[index].nextTargetWeight = e.target.value;
Expand Down
20 changes: 19 additions & 1 deletion client/src/pages/WorkoutsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const TABLE_HEAD = [

// ----------------------------------------------------------------------

// Helper function for sorting in descending order
function descendingComparator(a, b, orderBy) {
if (b[orderBy] < a[orderBy]) {
return -1;
Expand All @@ -51,12 +52,14 @@ function descendingComparator(a, b, orderBy) {
return 0;
}

// Returns a comparison function based on the order and orderBy values
function getComparator(order, orderBy) {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}

// Sorts the array, applies the search filter if provided, and returns the result
function applySortFilter(array, comparator, query) {
const stabilizedThis = array.map((el, index) => [el, index]);
stabilizedThis.sort((a, b) => {
Expand All @@ -73,6 +76,7 @@ function applySortFilter(array, comparator, query) {
export default function WorkoutsPage() {
const { email, isLoggedIn } = useAuth();

// If user is not logged in, display a message prompting to log in
if (!isLoggedIn) {
return (
<Container>
Expand All @@ -96,6 +100,7 @@ export default function WorkoutsPage() {
const [workoutData, setWorkoutData] = useState([]);
const [exerciseData, setExerciseData] = useState([]);

// Fetches workout and exercise data from the API
const fetchData = async (collection) => {
try {
const response = await fetch(`/api/getCollection?collection=${collection}`, {
Expand Down Expand Up @@ -128,20 +133,24 @@ export default function WorkoutsPage() {
}
};

// Fetch workout and exercise data when component mounts
useEffect(() => {
fetchData('workouts');
fetchData('exercises');
}, []);

// Opens the menu for a specific workout
const handleOpenMenu = (event, workoutId) => {
setWorkoutId(workoutId);
setOpen(event.currentTarget);
};

// Closes the menu
const handleCloseMenu = () => {
setOpen(null);
};

// Deletes a workout
const handleDeleteWorkout = async () => {
const confirmed = window.confirm('Are you sure you want to delete this workout?');

Expand All @@ -168,16 +177,19 @@ export default function WorkoutsPage() {
}
};

// Fetch workout data when a workout is deleted
useEffect(() => {
fetchData('workouts');
}, [workoutData, workoutId]);

// Handles sorting by column
const handleRequestSort = (event, property) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};

// Handles selecting all workouts
const handleSelectAllClick = (event) => {
if (event.target.checked) {
const newSelecteds = workoutData.map((n) => n.workoutName);
Expand All @@ -187,26 +199,32 @@ export default function WorkoutsPage() {
setSelected([]);
};


// Handles changing pagination page
const handleChangePage = (event, newPage) => {
setPage(newPage);
};

// Handles changing number of rows per page
const handleChangeRowsPerPage = (event) => {
setPage(0);
setRowsPerPage(parseInt(event.target.value, 10));
};

// Handles filtering workouts by name
const handleFilterByName = (event) => {
setPage(0);
setFilterName(event.target.value);
};

// Calculates the number of empty rows to display
const emptyRows = page > 0 ? Math.max(0, (1 + page) * rowsPerPage - workoutData.length) : 0;

// Applies sorting and filtering to workouts
const filteredWorkouts = applySortFilter(workoutData, getComparator(order, orderBy), filterName);
const isNotFound = !filteredWorkouts.length && !!filterName;

// Updated function to format exercises with exercise names instead of IDs
// Formats exercises with exercise names instead of IDs
const formatExercises = (exercises) => {
return exercises.map((exercise) => {
// Find the exercise object with matching _id
Expand Down
68 changes: 68 additions & 0 deletions client/src/pages/__tests__/WorkoutEditorPage.spec.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, fireEvent, waitFor, screen } from "@testing-library/react";
import { useAuth } from "../../context/AuthContext";
import WorkoutEditor from '../WorkoutEditorPage';
import { BrowserRouter, useNavigate } from 'react-router-dom';
import '@testing-library/jest-dom';

vi.mock("../../context/AuthContext", () => ({
useAuth: vi.fn(),
}));

vi.mock('react-router-dom', async (importOriginal) => {
const actual = await importOriginal();
const useNavigate = vi.fn();
return {
...actual,
useNavigate: () => useNavigate // Return a function that returns the spy
};
});

describe("AppGoals Component", () => {
// let workoutName, alertMock
beforeEach(() => {
console.error = vi.fn();
vi.clearAllMocks();
});
useAuth.mockImplementation(() => ({
isLoggedIn: true,
email: "dave@gmail.com",
}));

const alertMock = vi.fn();
window.alert = alertMock;
const mockNavigate = useNavigate()



describe('Workout Editor Tests', () => {

it('Test if a felid is missing will an error pop up?', async () => {
render(
<BrowserRouter>
<WorkoutEditor />
</BrowserRouter>
);
const workoutName = await screen.findByTestId('workout-name-input');
fireEvent.change(workoutName, { target: { value: 'Test Workout' } });
expect(workoutName.value).toBe('Test Workout');

const sets = await screen.findByTestId('sets-input-0');
fireEvent.change(sets, { target: { value: 3 } });

const reps = await screen.findByTestId('reps-input-0');
fireEvent.change(reps, { target: { value: 4 } });

const target_weight = await screen.findByTestId('weight-input-0');
fireEvent.change(target_weight, { target: { value: 4 } });


// Triggering the creation action
fireEvent.click(screen.getByRole('button', { name: "Create Workout" }));
await waitFor(() => {
expect(alertMock).toHaveBeenCalledWith("Please fill in all fields.")
});
});
})

});
Loading

0 comments on commit 0cf7e37

Please sign in to comment.