Skip to content
Open
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
6 changes: 3 additions & 3 deletions src/components/Calendar/Components/CalendarComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
const weightsQuery = useBodyWeightQuery();
const sessionQuery = useSessionsQuery({
filtersetQuerySessions: {
"date__gte": dateToYYYYMMDD(startOfMonth),
"date__lte": dateToYYYYMMDD(endOfMonth),
"datetime_start__gte": startOfMonth.toISOString(),
"datetime_start__lt": new Date(currentYear, currentMonth + 1, 1).toISOString(),
},
filtersetQueryLogs: {
"date__gte": dateToYYYYMMDD(startOfMonth),
Expand Down Expand Up @@ -99,7 +99,7 @@
date: new Date(date),
weightEntry: weightsQuery.data?.find(w => isSameDay(w.date, date)),
measurements: measurements.filter(m => isSameDay(m.date, date)) || [],
workoutSession: sessionQuery.data?.find(m => isSameDay(m.date, date)) || undefined,
workoutSession: sessionQuery.data?.find(m => isSameDay(m.datetimeStart, date)) || undefined,
nutritionLogs: nutritionDiaryQuery.data?.filter(m => isSameDay(m.datetime, date)) || [],
});
date.setDate(date.getDate() + 1);
Expand Down Expand Up @@ -133,7 +133,7 @@
setSelectedDay(todayWithData);
}
}
}, [isSuccess]);

Check warning on line 136 in src/components/Calendar/Components/CalendarComponent.tsx

View workflow job for this annotation

GitHub Actions / build

React Hook useEffect has missing dependencies: 'currentDate' and 'days'. Either include them or remove the dependency array


useEffect(() => {
Expand Down
41 changes: 18 additions & 23 deletions src/components/Routines/api/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,10 @@ describe("Session service tests", () => {
"id": SESSION_UUID,
"routine": 39764,
"day": null,
"date": "2025-08-07",
"notes": null,
"impression": "3",
"time_start": "20:10:58",
"time_end": "23:28:21"
"datetime_start": "2025-08-07T20:10:58+02:00",
"datetime_end": "2025-08-07T23:28:21+02:00"
},
]
}
Expand Down Expand Up @@ -163,21 +162,21 @@ describe("Session service tests", () => {
results: [
{
id: SESSION_UUID, routine: 39764, day: 5,
date: "2025-08-07",
notes: "ok",
impression: "3",
time_start: "20:10:58", time_end: "23:28:21",
datetime_start: "2025-08-07T20:10:58+02:00",
datetime_end: "2025-08-07T23:28:21+02:00",
},
],
};
(axios.get as Mock).mockResolvedValue({ data: apiResponse });

const result = await searchSession({ routine: 39764, date: "2025-08-07" });
const result = await searchSession({ routine: 39764, datetime_start__date: "2025-08-07" });

const url = (axios.get as Mock).mock.calls[0][0] as string;
expect(url).toContain("/api/v2/workoutsession/");
expect(url).toContain("routine=39764");
expect(url).toContain("date=2025-08-07");
expect(url).toContain("datetime_start__date=2025-08-07");
expect(result).toBeInstanceOf(WorkoutSession);
expect(result?.id).toBe(SESSION_UUID);
});
Expand Down Expand Up @@ -205,21 +204,20 @@ describe("Session service tests", () => {
test('addSession POSTs the serialized session and returns the parsed session', async () => {
(axios.post as Mock).mockResolvedValue({
data: {
id: SESSION_UUID_2, routine: 39764, day: 5, date: "2025-08-07",
id: SESSION_UUID_2, routine: 39764, day: 5,
notes: null, impression: "3",
time_start: null, time_end: null,
datetime_start: "2025-08-07T00:00:00+02:00", datetime_end: null,
},
});

const result = await addSession(new WorkoutSession({
id: null,
routineId: 39764,
dayId: 5,
date: new Date(2025, 7, 7),
notes: null,
impression: "3",
timeStart: null,
timeEnd: null,
datetimeStart: new Date(2025, 7, 7, 20, 10),
datetimeEnd: null,
}));

expect(axios.post).toHaveBeenCalledTimes(1);
Expand All @@ -228,11 +226,10 @@ describe("Session service tests", () => {
expect(body).toEqual({
routine: 39764,
day: 5,
date: "2025-08-07",
notes: null,
impression: "3",
time_start: null,
time_end: null,
datetime_start: new Date(2025, 7, 7, 20, 10).toISOString(),
datetime_end: null,
});
expect(result).toBeInstanceOf(WorkoutSession);
expect(result.id).toBe(SESSION_UUID_2);
Expand All @@ -241,21 +238,20 @@ describe("Session service tests", () => {
test('editSession PATCHes /workoutsession/<id>/ with the serialized session', async () => {
(axios.patch as Mock).mockResolvedValue({
data: {
id: SESSION_UUID, routine: 39764, day: 5, date: "2025-08-07",
id: SESSION_UUID, routine: 39764, day: 5,
notes: "edited", impression: "3",
time_start: null, time_end: null,
datetime_start: "2025-08-07T00:00:00+02:00", datetime_end: null,
},
});

const result = await editSession(new WorkoutSession({
id: SESSION_UUID,
routineId: 39764,
dayId: 5,
date: new Date(2025, 7, 7),
notes: "edited",
impression: "3",
timeStart: null,
timeEnd: null,
datetimeStart: new Date(2025, 7, 7, 20, 10),
datetimeEnd: null,
}));

expect(axios.patch).toHaveBeenCalledTimes(1);
Expand All @@ -265,11 +261,10 @@ describe("Session service tests", () => {
id: SESSION_UUID,
routine: 39764,
day: 5,
date: "2025-08-07",
notes: "edited",
impression: "3",
time_start: null,
time_end: null,
datetime_start: new Date(2025, 7, 7, 20, 10).toISOString(),
datetime_end: null,
});
expect(result.notes).toBe("edited");
});
Expand Down
36 changes: 15 additions & 21 deletions src/components/Routines/models/WorkoutSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { Day } from "@/components/Routines/models/Day";
import { WorkoutLog } from "@/components/Routines/models/WorkoutLog";
import i18n from 'i18next';
import { Adapter } from "@/core/lib/Adapter";
import { dateTimeToHHMM, dateToYYYYMMDD, HHMMToDateTime, yyyymmddToDate } from "@/core/lib/date";

export const NOTES_MAX_LENGTH = 1000 as const;

Expand All @@ -14,11 +13,10 @@ interface WorkoutSessionParams {
id: string | null;
dayId: number;
routineId: number;
date: Date;
datetimeStart: Date;
datetimeEnd: Date | null;
notes: string | null;
impression: string;
timeStart: Date | null;
timeEnd: Date | null;
dayObj?: Day;
logs?: WorkoutLog[];
}
Expand All @@ -28,23 +26,21 @@ export class WorkoutSession {
id: string | null;
dayId: number;
routineId: number;
date: Date;
datetimeStart: Date;
datetimeEnd: Date | null;
notes: string | null;
impression: string;
timeStart: Date | null;
timeEnd: Date | null;
dayObj?: Day;
logs: WorkoutLog[] = [];

constructor(params: WorkoutSessionParams) {
this.id = params.id;
this.dayId = params.dayId;
this.routineId = params.routineId;
this.date = params.date;
this.datetimeStart = params.datetimeStart;
this.datetimeEnd = params.datetimeEnd;
this.notes = params.notes;
this.impression = params.impression;
this.timeStart = params.timeStart;
this.timeEnd = params.timeEnd;
if (params.dayObj) {
this.dayObj = params.dayObj;
}
Expand All @@ -70,10 +66,10 @@ export class WorkoutSession {
}

get textRepresentation(): string {
const time = this.timeStart && this.timeEnd ? `${this.timeStart.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit'
})} - ${this.timeEnd.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} /` : "";
const format = (date: Date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const time = this.datetimeEnd
? `${format(this.datetimeStart)} - ${format(this.datetimeEnd)} /`
: `${format(this.datetimeStart)} /`;

const notes = this.notes ?? "";

Expand All @@ -89,11 +85,10 @@ export class WorkoutSessionAdapter implements Adapter<WorkoutSession> {
id: item.id,
dayId: item.day!,
routineId: item.routine!,
date: yyyymmddToDate(item.date!),
datetimeStart: new Date(item.datetime_start),
datetimeEnd: item.datetime_end ? new Date(item.datetime_end) : null,
notes: item.notes !== undefined ? item.notes : null,
impression: item.impression!,
timeStart: item.time_start !== undefined ? HHMMToDateTime(item.time_start) : null,
timeEnd: item.time_end !== undefined ? HHMMToDateTime(item.time_end) : null,
dayObj: item.dayObj,
logs: item.logs
});
Expand All @@ -102,13 +97,12 @@ export class WorkoutSessionAdapter implements Adapter<WorkoutSession> {
toJson = (item: WorkoutSession) => ({
...(item.id != null ? { id: item.id } : {}),
day: item.dayId,
date: dateToYYYYMMDD(item.date),
routine: item.routineId,
notes: item.notes,
impression: item.impression,
// eslint-disable-next-line camelcase
time_start: dateTimeToHHMM(item.timeStart),
datetime_start: item.datetimeStart.toISOString(),
// eslint-disable-next-line camelcase
time_end: dateTimeToHHMM(item.timeEnd),
datetime_end: item.datetimeEnd ? item.datetimeEnd.toISOString() : null,
});
}
}
71 changes: 63 additions & 8 deletions src/components/Routines/widgets/forms/SessionForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('SessionForm', () => {
// Assert
expect(mockUseFindSessionQuery).toHaveBeenCalledWith(
routineId,
{ routine: routineId, date: '2024-05-01', day: dayId }
{ routine: routineId, datetime_start__date: '2024-05-01', day: dayId }
);

// Act - the parent selects another date
Expand All @@ -80,7 +80,7 @@ describe('SessionForm', () => {
// Assert
expect(mockUseFindSessionQuery).toHaveBeenLastCalledWith(
routineId,
{ routine: routineId, date: '2024-05-08', day: dayId }
{ routine: routineId, datetime_start__date: '2024-05-08', day: dayId }
);
});

Expand Down Expand Up @@ -129,11 +129,11 @@ describe('SessionForm', () => {
id: 'bbbbbbbb-bbbb-bbbb-bbbb-000000000001',
dayId: dayId,
routineId: routineId,
date: date.toJSDate(),

notes: 'Test notes',
impression: '3',
timeStart: timeStart.toJSDate(),
timeEnd: timeEnd.toJSDate()
datetimeStart: timeStart.toJSDate(),
datetimeEnd: timeEnd.toJSDate()
});

mockUseFindSessionQuery.mockReturnValue({
Expand Down Expand Up @@ -219,6 +219,61 @@ describe('SessionForm', () => {
expect(editMutateAsync).not.toHaveBeenCalled();
});

test('shows what the server rejected', async () => {

// Arrange
mockUseFindSessionQuery.mockReturnValue({
data: null,
isLoading: false,
isSuccess: true
});
mockUseAddSessionQuery.mockReturnValue({
data: null,
isPending: false,
mutateAsync: addMutateAsync,
isError: true,
error: {
message: 'Request failed with status code 400',
response: { data: { datetime_end: ['A session cannot be longer than 5 hours.'] } }
},
});

// Act
renderForm(DateTime.fromISO('2024-05-01'));

// Assert
expect(screen.getByText(/A session cannot be longer than 5 hours/)).toBeInTheDocument();
});

test('submits a session that runs past midnight with the end on the next day', async () => {

// Arrange
const user = userEvent.setup();
mockUseFindSessionQuery.mockReturnValue({
data: new WorkoutSession({
id: null,
dayId: dayId,
routineId: routineId,
notes: '',
impression: '2',
datetimeStart: DateTime.fromISO('2024-05-01T23:00').toJSDate(),
datetimeEnd: DateTime.fromISO('2024-05-01T01:30').toJSDate(),
}),
isLoading: false,
isSuccess: true
});

// Act
renderForm(DateTime.fromISO('2024-05-01'));
await user.click(screen.getByRole('button', { name: /submit/i }));

// Assert
await waitFor(() => expect(editMutateAsync).toHaveBeenCalled());
const draft = editMutateAsync.mock.calls[0][0] as WorkoutSession;
expect(draft.datetimeStart).toEqual(DateTime.fromISO('2024-05-01T23:00').toJSDate());
expect(draft.datetimeEnd).toEqual(DateTime.fromISO('2024-05-02T01:30').toJSDate());
});

test('submits an existing session through the edit mutation', async () => {

// Arrange
Expand All @@ -227,11 +282,11 @@ describe('SessionForm', () => {
id: 'bbbbbbbb-bbbb-bbbb-bbbb-000000000001',
dayId: dayId,
routineId: routineId,
date: DateTime.fromISO('2024-05-01').toJSDate(),

notes: 'Test notes',
impression: '3',
timeStart: null,
timeEnd: null
datetimeStart: DateTime.fromISO('2024-05-01').toJSDate(),
datetimeEnd: null
});
mockUseFindSessionQuery.mockReturnValue({
data: mockSession,
Expand Down
Loading
Loading