chore: commit chybějících souborů

This commit is contained in:
Ondřej Anděl committed 2026-10-02 19:38:02 +02:00
1 parent f1ce3ff297
commit 9e72f431a1
7 files changed
+890

No files matched your search

+209
View File
@@ -0,0 +1,209 @@
process.env.CALORIE_PROVIDER = 'none';
import { addActivity, deleteActivity, getActivityDay, setActivityMultiplier, updateActivity } from "../activities";
import { applyWorkoutTemplate, deleteWorkoutTemplate, saveWorkoutTemplate } from "../workouts";
import { getActivityStats } from "../statsService";
import { getWorkoutPlans } from "../workoutPlans";
import { resetMemoryStorage } from "../storage/memory";
import { applyMealTemplate, saveMealTemplate } from "../mealTemplates";
import { addMeal, getDay } from "../meals";
import { ActivityUnit, MealType, PlanDeviationKind, WorkoutItemKind } from "../../../types/gen/types.gen";
const LOGIN = 'tester';
const DATE = '2025-03-03';
const NEXT_DATE = '2025-03-04';
/** Cvik jako položka šablony. */
const cvik = (name: string, quantity: number, rate: number) => ({
kind: WorkoutItemKind.CVIK,
name,
unit: ActivityUnit.OPAKOVANI,
quantity,
caloriesPer100Units: rate,
});
/** Založí šablonu Kliky 100 × 0,5 kcal + Dřepy 100 × 0,3 kcal, tedy 80 kcal. */
async function createWorkout(): Promise<string> {
const [template] = await saveWorkoutTemplate(LOGIN, undefined, 'Workout', [
cvik('Kliky', 100, 50),
cvik('Dřepy', 100, 30),
]);
return template.id;
}
const stats = () => getActivityStats(LOGIN, '2025-03-01', '2025-03-31');
beforeEach(() => resetMemoryStorage());
describe('statistiky výdeje', () => {
it('odcvičená šablona se shoduje s plánem', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
const result = await stats();
expect(result.templateCalories).toBe(80);
expect(result.plannedCalories).toBe(80);
expect(result.extraCalories).toBe(0);
expect(result.byTemplate).toEqual([expect.objectContaining({
name: 'Workout', dayCount: 1, plannedCalories: 80, actualCalories: 80,
plannedItemCount: 2, doneItemCount: 2,
})]);
});
it('smazaný cvik ze šablony se projeví jako odchylka', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
const drepy = (await getActivityDay(LOGIN, DATE)).entries.find(entry => entry.name === 'Dřepy')!;
await deleteActivity(LOGIN, drepy.id, DATE);
const result = await stats();
expect(result.templateCalories).toBe(50);
expect(result.plannedCalories).toBe(80);
expect(result.byTemplate[0]).toEqual(expect.objectContaining({ plannedItemCount: 2, doneItemCount: 1 }));
});
it('zkrácený cvik ze šablony se projeví jako odchylka', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
const kliky = (await getActivityDay(LOGIN, DATE)).entries.find(entry => entry.name === 'Kliky')!;
await setActivityMultiplier(LOGIN, kliky.id, DATE, 0.5);
const result = await stats();
expect(result.templateCalories).toBe(55);
expect(result.plannedCalories).toBe(80);
});
it('smazání všech cviků šablony vrátí její použití, nepočítá se jako odchylka', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
for (const entry of (await getActivityDay(LOGIN, DATE)).entries) {
await deleteActivity(LOGIN, entry.id, DATE);
}
expect(await getWorkoutPlans(LOGIN, DATE)).toEqual([]);
const result = await stats();
expect(result.plannedCalories).toBe(0);
expect(result.byTemplate).toEqual([]);
});
it('plán se drží podoby šablony v době použití, ne její pozdější úpravy', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
await saveWorkoutTemplate(LOGIN, id, 'Workout v2', [cvik('Kliky', 200, 50)]);
const result = await stats();
expect(result.plannedCalories).toBe(80);
expect(result.byTemplate[0].name).toBe('Workout');
});
it('aktivity mimo šablonu se sčítají zvlášť podle názvu', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
await addActivity(LOGIN, { date: DATE, name: 'Chůze', unit: ActivityUnit.KROKY, quantity: 5000, caloriesPer100Units: 4 });
await addActivity(LOGIN, { date: NEXT_DATE, name: 'Chůze', unit: ActivityUnit.KROKY, quantity: 5000, caloriesPer100Units: 4 });
const result = await stats();
expect(result.totalCalories).toBe(480);
expect(result.extraCalories).toBe(400);
expect(result.extras).toEqual([{ key: 'Chůze', calories: 400, count: 2 }]);
expect(result.dayCount).toBe(2);
expect(result.byDay).toEqual([
{ period: DATE, templateCalories: 80, plannedCalories: 80, extraCalories: 200 },
{ period: NEXT_DATE, templateCalories: 0, plannedCalories: 0, extraCalories: 200 },
]);
});
it('cvik přesunutý do jiného dne chybí v původním a v novém se bere bez odchylky', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
const kliky = (await getActivityDay(LOGIN, DATE)).entries.find(entry => entry.name === 'Kliky')!;
await updateActivity(LOGIN, kliky.id, { ...kliky, date: NEXT_DATE });
const result = await stats();
// Původní den: plán 80, zůstaly dřepy za 30; nový den: kliky 50 bez plánu
expect(result.templateCalories).toBe(80);
expect(result.plannedCalories).toBe(130);
});
it('šablona smazaná po použití se ve statistikách drží názvu z plánu', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
await deleteWorkoutTemplate(LOGIN, id);
const result = await stats();
expect(result.byTemplate[0].name).toBe('Workout');
});
it('odmítne obrácený rozsah', async () => {
await expect(getActivityStats(LOGIN, '2025-03-31', '2025-03-01')).rejects.toThrow();
});
});
describe('denní detail výdeje', () => {
it('ukáže, jaká šablona se použila a který cvik z ní chybí', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
const drepy = (await getActivityDay(LOGIN, DATE)).entries.find(entry => entry.name === 'Dřepy')!;
await deleteActivity(LOGIN, drepy.id, DATE);
const [day] = (await stats()).days;
expect(day.date).toBe(DATE);
expect(day.templates).toEqual([expect.objectContaining({
name: 'Workout', plannedCalories: 80, actualCalories: 50, hasPlan: true,
deviations: [{
kind: PlanDeviationKind.ODEBRANO,
planned: { name: 'Dřepy', unit: ActivityUnit.OPAKOVANI, quantity: 100, calories: 30 },
}],
})]);
});
it('upravený cvik ukáže plán i skutečnost', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
const kliky = (await getActivityDay(LOGIN, DATE)).entries.find(entry => entry.name === 'Kliky')!;
await updateActivity(LOGIN, kliky.id, { ...kliky, quantity: 40 });
const [usage] = (await stats()).days[0].templates;
expect(usage.deviations).toEqual([{
kind: PlanDeviationKind.UPRAVENO,
planned: expect.objectContaining({ name: 'Kliky', quantity: 100, calories: 50 }),
actual: expect.objectContaining({ name: 'Kliky', quantity: 40, calories: 20 }),
}]);
});
it('odcvičená šablona nemá žádnou odchylku', async () => {
const id = await createWorkout();
await applyWorkoutTemplate(LOGIN, id, DATE);
expect((await stats()).days[0].templates[0].deviations).toEqual([]);
});
it('aktivity mimo šablonu vrátí jednotlivě i se součtem', async () => {
await addActivity(LOGIN, { date: DATE, name: 'Chůze', unit: ActivityUnit.KROKY, quantity: 5000, caloriesPer100Units: 4 });
await addActivity(LOGIN, { date: DATE, name: 'Běh', unit: ActivityUnit.MINUTY, quantity: 20, caloriesPer100Units: 1000 });
const [day] = (await stats()).days;
expect(day.templates).toEqual([]);
expect(day.extraCalories).toBe(400);
expect(day.extras.map(item => item.name)).toEqual(['Chůze', 'Běh']);
});
});
describe('vazba jídla na šablonu', () => {
it('jídlo ze šablony si pamatuje, ze které vzniklo', async () => {
const [template] = await saveMealTemplate(LOGIN, { name: 'Štrůdl', mealType: MealType.PULNOCNI_SVACINA });
await applyMealTemplate(LOGIN, template.id, DATE);
const [meal] = (await getDay(LOGIN, DATE)).entries;
expect(meal.templateId).toBe(template.id);
expect(meal.mealType).toBe(MealType.PULNOCNI_SVACINA);
});
it('ručně zadané jídlo vazbu nemá a klient ji podstrčit nemůže', async () => {
await addMeal(LOGIN, { date: DATE, mealType: MealType.OBED, name: 'Guláš', templateId: 'podvrh' });
const [meal] = (await getDay(LOGIN, DATE)).entries;
expect(meal.templateId).toBeUndefined();
});
});