mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(workouts): refuse a manual workout that would end in the future
The form's start defaults to the minute it opened, so a workout logged right after it finished was stored as ending up to a day ahead. The duration now fails validation when start + duration lies past now, with the same one-minute slack the start check uses.
This commit is contained in:
1 parent
2cd8b28a22
commit
a14ae57283
9 files changed
+28
-2
No files matched your search
@@ -3465,6 +3465,7 @@
|
||||
"durationRequired": "Gib an, wie lange das Workout gedauert hat.",
|
||||
"durationInvalid": "Gib ganze Stunden und Minuten von 0 bis 59 ein.",
|
||||
"durationTooLong": "Ein Workout dauert höchstens 24 Stunden.",
|
||||
"endInFuture": "Das Workout würde in der Zukunft enden. Stell den Beginn auf die Uhrzeit, zu der es angefangen hat.",
|
||||
"distanceInvalid": "Gib die Distanz als Zahl in einem realistischen Bereich ein.",
|
||||
"energyInvalid": "Gib die aktive Energie als Zahl bis 20.000 kcal ein."
|
||||
}
|
||||
|
||||
@@ -3465,6 +3465,7 @@
|
||||
"durationRequired": "Enter how long the workout lasted.",
|
||||
"durationInvalid": "Enter whole hours, and minutes from 0 to 59.",
|
||||
"durationTooLong": "A workout can last at most 24 hours.",
|
||||
"endInFuture": "It would end in the future. Set the start to when the workout began.",
|
||||
"distanceInvalid": "Enter the distance as a number within a realistic range.",
|
||||
"energyInvalid": "Enter active energy as a number up to 20,000 kcal."
|
||||
}
|
||||
|
||||
@@ -3465,6 +3465,7 @@
|
||||
"durationRequired": "Indica cuánto duró el entrenamiento.",
|
||||
"durationInvalid": "Introduce horas enteras y minutos de 0 a 59.",
|
||||
"durationTooLong": "Un entrenamiento puede durar como máximo 24 horas.",
|
||||
"endInFuture": "Terminaría en el futuro. Pon como inicio la hora en que empezó el entrenamiento.",
|
||||
"distanceInvalid": "Introduce la distancia como un número dentro de un rango realista.",
|
||||
"energyInvalid": "Introduce la energía activa como un número de hasta 20.000 kcal."
|
||||
}
|
||||
|
||||
@@ -3465,6 +3465,7 @@
|
||||
"durationRequired": "Indiquez la durée de l’entraînement.",
|
||||
"durationInvalid": "Saisissez des heures entières et des minutes de 0 à 59.",
|
||||
"durationTooLong": "Un entraînement dure au maximum 24 heures.",
|
||||
"endInFuture": "Il se terminerait dans le futur. Indiquez comme début l’heure à laquelle l’entraînement a commencé.",
|
||||
"distanceInvalid": "Saisissez la distance sous forme de nombre, dans une plage réaliste.",
|
||||
"energyInvalid": "Saisissez l’énergie active sous forme de nombre, jusqu’à 20 000 kcal."
|
||||
}
|
||||
|
||||
@@ -3465,6 +3465,7 @@
|
||||
"durationRequired": "Indica quanto è durato l’allenamento.",
|
||||
"durationInvalid": "Inserisci ore intere e minuti da 0 a 59.",
|
||||
"durationTooLong": "Un allenamento può durare al massimo 24 ore.",
|
||||
"endInFuture": "Finirebbe nel futuro. Imposta come inizio l’ora in cui è cominciato l’allenamento.",
|
||||
"distanceInvalid": "Inserisci la distanza come numero in un intervallo realistico.",
|
||||
"energyInvalid": "Inserisci l’energia attiva come numero fino a 20.000 kcal."
|
||||
}
|
||||
|
||||
@@ -3465,6 +3465,7 @@
|
||||
"durationRequired": "운동한 시간을 입력하세요.",
|
||||
"durationInvalid": "시간은 정수로, 분은 0에서 59 사이로 입력하세요.",
|
||||
"durationTooLong": "운동은 최대 24시간까지 입력할 수 있습니다.",
|
||||
"endInFuture": "종료 시간이 미래가 됩니다. 시작 시간을 운동을 시작한 시각으로 설정하세요.",
|
||||
"distanceInvalid": "거리를 현실적인 범위의 숫자로 입력하세요.",
|
||||
"energyInvalid": "활동 에너지는 20,000kcal 이하의 숫자로 입력하세요."
|
||||
}
|
||||
|
||||
@@ -3465,6 +3465,7 @@
|
||||
"durationRequired": "Podaj, jak długo trwał trening.",
|
||||
"durationInvalid": "Podaj pełne godziny i minuty od 0 do 59.",
|
||||
"durationTooLong": "Trening może trwać najwyżej 24 godziny.",
|
||||
"endInFuture": "Trening kończyłby się w przyszłości. Ustaw początek na godzinę, o której się zaczął.",
|
||||
"distanceInvalid": "Podaj dystans jako liczbę w realistycznym zakresie.",
|
||||
"energyInvalid": "Podaj energię czynną jako liczbę do 20 000 kcal."
|
||||
}
|
||||
|
||||
@@ -136,9 +136,9 @@ describe("buildManualWorkoutEntry — validation", () => {
|
||||
});
|
||||
|
||||
it("tolerates the minute the form was opened in", () => {
|
||||
// 14:00:30 Berlin is 30 s past `now`: inside the slack.
|
||||
// 13:15 Berlin + 45 min ends at 12:00Z, 30 s past `now`: inside the slack.
|
||||
const result = buildManualWorkoutEntry(
|
||||
draft({ start: "2026-09-15T14:00" }),
|
||||
draft({ start: "2026-09-15T13:15" }),
|
||||
{
|
||||
...CTX,
|
||||
now: new Date("2026-09-15T11:59:30Z"),
|
||||
@@ -147,6 +147,13 @@ describe("buildManualWorkoutEntry — validation", () => {
|
||||
expect(result.ok).toBe(true);
|
||||
});
|
||||
|
||||
it("refuses a workout that would end in the future", () => {
|
||||
// Logged at 14:00 Berlin with the start left at 13:30: 45 min ends 14:15.
|
||||
expect(errorsOf({ start: "2026-09-15T13:30" }).duration).toBe(
|
||||
"insights.workouts.manual.errors.endInFuture",
|
||||
);
|
||||
});
|
||||
|
||||
it("asks for a start", () => {
|
||||
expect(errorsOf({ start: "" }).start).toBe(
|
||||
"insights.workouts.manual.errors.startRequired",
|
||||
|
||||
@@ -235,6 +235,18 @@ function manualWorkoutSchema(ctx: ManualWorkoutContext) {
|
||||
path: ["duration"],
|
||||
message: `${ERR}.durationTooLong`,
|
||||
});
|
||||
} else if (
|
||||
// The default start is "now", so a workout logged right after it
|
||||
// finished would otherwise be stored as ending in the future.
|
||||
startedAt !== null &&
|
||||
startedAt.getTime() + sec * 1000 >
|
||||
ctx.now.getTime() + FUTURE_TOLERANCE_MS
|
||||
) {
|
||||
issue.addIssue({
|
||||
code: "custom",
|
||||
path: ["duration"],
|
||||
message: `${ERR}.endInFuture`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user