feat: zlepšení šablon, přidány osobní údaje a spánkový režim
This commit is contained in:
1 parent
a5711f3e1e
commit
21b01528e9
51 files changed
+3739
-504
No files matched your search
@@ -0,0 +1,215 @@
|
||||
process.env.CALORIE_PROVIDER = 'none';
|
||||
|
||||
import { addActivity, getActivityDay, getTotalQuantity, updateActivity } from "../activities";
|
||||
import { applyWorkoutTemplate, getWorkoutTemplates, saveWorkoutTemplate } from "../workouts";
|
||||
import { applyMealTemplate, deleteMealTemplate, getMealTemplates, saveMealTemplate } from "../mealTemplates";
|
||||
import { getDay } from "../meals";
|
||||
import { resetMemoryStorage } from "../storage/memory";
|
||||
import { ActivityUnit, MealType, WorkoutItemKind } from "../../../types/gen/types.gen";
|
||||
|
||||
const LOGIN = 'tester';
|
||||
const DATE = '2025-03-03';
|
||||
|
||||
beforeEach(() => resetMemoryStorage());
|
||||
|
||||
describe('série u cviků', () => {
|
||||
|
||||
it('celkové množství je počet sérií krát množství v sérii', () => {
|
||||
expect(getTotalQuantity(40, 3)).toBe(120);
|
||||
expect(getTotalQuantity(40)).toBe(40);
|
||||
// Nula ani záporná hodnota sérií nedává smysl, bere se jako jedna
|
||||
expect(getTotalQuantity(40, 0)).toBe(40);
|
||||
});
|
||||
|
||||
it('3 × 40 spálí stejně jako 120 v kuse', async () => {
|
||||
await addActivity(LOGIN, {
|
||||
date: DATE, name: 'Kliky', unit: ActivityUnit.OPAKOVANI,
|
||||
quantity: 40, sets: 3, caloriesPer100Units: 50,
|
||||
});
|
||||
await addActivity(LOGIN, {
|
||||
date: '2025-03-04', name: 'Kliky', unit: ActivityUnit.OPAKOVANI,
|
||||
quantity: 120, caloriesPer100Units: 50,
|
||||
});
|
||||
|
||||
expect((await getActivityDay(LOGIN, DATE)).totalCalories).toBe(60);
|
||||
expect((await getActivityDay(LOGIN, '2025-03-04')).totalCalories).toBe(60);
|
||||
});
|
||||
|
||||
it('ale série zůstane v záznamu, aby šlo 3 × 40 od 120 rozlišit', async () => {
|
||||
await addActivity(LOGIN, {
|
||||
date: DATE, name: 'Kliky', unit: ActivityUnit.OPAKOVANI,
|
||||
quantity: 40, sets: 3, caloriesPer100Units: 50,
|
||||
});
|
||||
|
||||
const [entry] = (await getActivityDay(LOGIN, DATE)).entries;
|
||||
expect(entry.quantity).toBe(40);
|
||||
expect(entry.sets).toBe(3);
|
||||
});
|
||||
|
||||
it('jedna série se neukládá, je to výchozí stav', async () => {
|
||||
await addActivity(LOGIN, {
|
||||
date: DATE, name: 'Běh', unit: ActivityUnit.MINUTY, quantity: 20, sets: 1, caloriesPer100Units: 1000,
|
||||
});
|
||||
|
||||
expect((await getActivityDay(LOGIN, DATE)).entries[0].sets).toBeUndefined();
|
||||
});
|
||||
|
||||
it('úprava umí série přidat i odebrat', async () => {
|
||||
await addActivity(LOGIN, {
|
||||
date: DATE, name: 'Kliky', unit: ActivityUnit.OPAKOVANI, quantity: 40, caloriesPer100Units: 50,
|
||||
});
|
||||
const [created] = (await getActivityDay(LOGIN, DATE)).entries;
|
||||
|
||||
await updateActivity(LOGIN, created.id, {
|
||||
date: DATE, name: 'Kliky', unit: ActivityUnit.OPAKOVANI,
|
||||
quantity: 40, sets: 3, caloriesPer100Units: 50,
|
||||
});
|
||||
expect((await getActivityDay(LOGIN, DATE)).entries[0].sets).toBe(3);
|
||||
expect((await getActivityDay(LOGIN, DATE)).totalCalories).toBe(60);
|
||||
|
||||
await updateActivity(LOGIN, created.id, {
|
||||
date: DATE, name: 'Kliky', unit: ActivityUnit.OPAKOVANI,
|
||||
quantity: 40, caloriesPer100Units: 50,
|
||||
});
|
||||
expect((await getActivityDay(LOGIN, DATE)).entries[0].sets).toBeUndefined();
|
||||
expect((await getActivityDay(LOGIN, DATE)).totalCalories).toBe(20);
|
||||
});
|
||||
|
||||
it('šablona počítá odhad ze všech sérií', async () => {
|
||||
const templates = await saveWorkoutTemplate(LOGIN, undefined, 'Workout', [
|
||||
{ kind: WorkoutItemKind.CVIK, name: 'Kliky', unit: ActivityUnit.OPAKOVANI, quantity: 40, sets: 3, caloriesPer100Units: 50 },
|
||||
]);
|
||||
|
||||
expect(templates[0].estimatedCalories).toBe(60);
|
||||
expect(templates[0].items[0].sets).toBe(3);
|
||||
});
|
||||
|
||||
it('použití šablony přenese série do založené aktivity', async () => {
|
||||
const [template] = await saveWorkoutTemplate(LOGIN, undefined, 'Workout', [
|
||||
{ kind: WorkoutItemKind.CVIK, name: 'Kliky', unit: ActivityUnit.OPAKOVANI, quantity: 40, sets: 3, caloriesPer100Units: 50 },
|
||||
]);
|
||||
|
||||
await applyWorkoutTemplate(LOGIN, template.id, DATE);
|
||||
|
||||
const [entry] = (await getActivityDay(LOGIN, DATE)).entries;
|
||||
expect(entry.sets).toBe(3);
|
||||
expect(entry.quantity).toBe(40);
|
||||
expect(entry.calories).toBe(60);
|
||||
});
|
||||
|
||||
it('série se rozbalí i z vnořené šablony', async () => {
|
||||
const [zaklad] = await saveWorkoutTemplate(LOGIN, undefined, 'Základ', [
|
||||
{ kind: WorkoutItemKind.CVIK, name: 'Dřepy', unit: ActivityUnit.OPAKOVANI, quantity: 20, sets: 4, caloriesPer100Units: 40 },
|
||||
]);
|
||||
const templates = await saveWorkoutTemplate(LOGIN, undefined, 'Celý trénink', [
|
||||
{ kind: WorkoutItemKind.SABLONA, templateRefId: zaklad.id },
|
||||
]);
|
||||
const celyTrenink = templates.find(t => t.name === 'Celý trénink')!;
|
||||
expect(celyTrenink.estimatedCalories).toBe(32);
|
||||
|
||||
await applyWorkoutTemplate(LOGIN, celyTrenink.id, DATE);
|
||||
|
||||
const [entry] = (await getActivityDay(LOGIN, DATE)).entries;
|
||||
expect(entry.sets).toBe(4);
|
||||
expect(entry.calories).toBe(32);
|
||||
});
|
||||
});
|
||||
|
||||
describe('šablony jídel', () => {
|
||||
|
||||
const salat = {
|
||||
name: 'Těstovinový salát',
|
||||
source: 'Chefie',
|
||||
mealType: MealType.OBED,
|
||||
weight: 350,
|
||||
caloriesPer100g: 160,
|
||||
price: 12900,
|
||||
};
|
||||
|
||||
it('uloží šablonu a vrátí ji v seznamu', async () => {
|
||||
const templates = await saveMealTemplate(LOGIN, salat);
|
||||
|
||||
expect(templates).toHaveLength(1);
|
||||
expect(templates[0].name).toBe('Těstovinový salát');
|
||||
expect(templates[0].source).toBe('Chefie');
|
||||
});
|
||||
|
||||
it('úprava podle id šablonu přepíše, nezaloží druhou', async () => {
|
||||
const [created] = await saveMealTemplate(LOGIN, salat);
|
||||
|
||||
const templates = await saveMealTemplate(LOGIN, { ...salat, id: created.id, name: 'Salát velký' });
|
||||
|
||||
expect(templates).toHaveLength(1);
|
||||
expect(templates[0].name).toBe('Salát velký');
|
||||
});
|
||||
|
||||
it('použití založí jídlo do dne a dopočte kalorie', async () => {
|
||||
const [template] = await saveMealTemplate(LOGIN, salat);
|
||||
|
||||
await applyMealTemplate(LOGIN, template.id, DATE);
|
||||
|
||||
const day = await getDay(LOGIN, DATE);
|
||||
expect(day.entries).toHaveLength(1);
|
||||
expect(day.entries[0].name).toBe('Těstovinový salát');
|
||||
expect(day.entries[0].source).toBe('Chefie');
|
||||
// 350 g × 160 kcal/100 g
|
||||
expect(day.entries[0].calories).toBe(560);
|
||||
expect(day.totalPrice).toBe(12900);
|
||||
});
|
||||
|
||||
it('zvolený chod přebije ten ze šablony', async () => {
|
||||
const [template] = await saveMealTemplate(LOGIN, salat);
|
||||
|
||||
await applyMealTemplate(LOGIN, template.id, DATE, MealType.VECERE);
|
||||
|
||||
expect((await getDay(LOGIN, DATE)).entries[0].mealType).toBe(MealType.VECERE);
|
||||
});
|
||||
|
||||
it('bez chodu ve šabloně i v požadavku se založí jako oběd', async () => {
|
||||
const [template] = await saveMealTemplate(LOGIN, { name: 'Jablko', calories: 80 });
|
||||
|
||||
await applyMealTemplate(LOGIN, template.id, DATE);
|
||||
|
||||
expect((await getDay(LOGIN, DATE)).entries[0].mealType).toBe(MealType.OBED);
|
||||
});
|
||||
|
||||
it('šablona jen s absolutními kaloriemi funguje taky', async () => {
|
||||
const [template] = await saveMealTemplate(LOGIN, { name: 'Jablko', calories: 80 });
|
||||
|
||||
await applyMealTemplate(LOGIN, template.id, DATE);
|
||||
|
||||
expect((await getDay(LOGIN, DATE)).totalCalories).toBe(80);
|
||||
});
|
||||
|
||||
it('založené jídlo je samostatné — smazání šablony ho nesmaže', async () => {
|
||||
const [template] = await saveMealTemplate(LOGIN, salat);
|
||||
await applyMealTemplate(LOGIN, template.id, DATE);
|
||||
|
||||
await deleteMealTemplate(LOGIN, template.id);
|
||||
|
||||
expect(await getMealTemplates(LOGIN)).toEqual([]);
|
||||
expect((await getDay(LOGIN, DATE)).entries).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('unese poznámku s postupem přípravy', async () => {
|
||||
const recept = 'Těstoviny uvařit al dente.\nZeleninu nakrájet.\nSmíchat s dresinkem.';
|
||||
const [template] = await saveMealTemplate(LOGIN, { name: 'Domácí salát', note: recept });
|
||||
|
||||
expect(template.note).toBe(recept);
|
||||
|
||||
await applyMealTemplate(LOGIN, template.id, DATE);
|
||||
expect((await getDay(LOGIN, DATE)).entries[0].note).toBe(recept);
|
||||
});
|
||||
|
||||
it('odmítne šablonu bez názvu a neexistující id', async () => {
|
||||
await expect(saveMealTemplate(LOGIN, { name: ' ' })).rejects.toThrow(/název šablony/);
|
||||
await expect(applyMealTemplate(LOGIN, 'neexistuje', DATE)).rejects.toThrow(/nebyla nalezena/);
|
||||
await expect(deleteMealTemplate(LOGIN, 'neexistuje')).rejects.toThrow(/nebyla nalezena/);
|
||||
});
|
||||
|
||||
it('šablony jídel jsou oddělené mezi uživateli', async () => {
|
||||
await saveMealTemplate(LOGIN, salat);
|
||||
|
||||
expect(await getMealTemplates('nekdo-jiny')).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,168 @@
|
||||
process.env.CALORIE_PROVIDER = 'none';
|
||||
|
||||
import { addActivity, getActivityDay } from "../activities";
|
||||
import { addMeal, getDay } from "../meals";
|
||||
import { createMealTemplateFromMeal, getMealTemplates } from "../mealTemplates";
|
||||
import { createWorkoutTemplateFromActivities, getWorkoutTemplates } from "../workouts";
|
||||
import { resetMemoryStorage } from "../storage/memory";
|
||||
import { ActivityUnit, MealType } from "../../../types/gen/types.gen";
|
||||
|
||||
const LOGIN = 'tester';
|
||||
const DATE = '2025-03-03';
|
||||
|
||||
beforeEach(() => resetMemoryStorage());
|
||||
|
||||
describe('překlopení jídla do šablony', () => {
|
||||
|
||||
const gyros = {
|
||||
date: DATE, mealType: MealType.OBED, name: 'Kuřecí gyros',
|
||||
source: 'TechTower', price: 17600, pricePer100g: 4400, caloriesPer100g: 172,
|
||||
};
|
||||
|
||||
it('převezme název, zdroj, gramáž i jednotkové hodnoty', async () => {
|
||||
const day = await addMeal(LOGIN, gyros);
|
||||
|
||||
const templates = await createMealTemplateFromMeal(LOGIN, DATE, day.entries[0].id);
|
||||
|
||||
expect(templates).toHaveLength(1);
|
||||
expect(templates[0]).toMatchObject({
|
||||
name: 'Kuřecí gyros',
|
||||
source: 'TechTower',
|
||||
mealType: MealType.OBED,
|
||||
weight: 400,
|
||||
caloriesPer100g: 172,
|
||||
price: 17600,
|
||||
pricePer100g: 4400,
|
||||
});
|
||||
});
|
||||
|
||||
it('jídlo v dni zůstane beze změny', async () => {
|
||||
const day = await addMeal(LOGIN, gyros);
|
||||
|
||||
await createMealTemplateFromMeal(LOGIN, DATE, day.entries[0].id);
|
||||
|
||||
expect((await getDay(LOGIN, DATE)).entries).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('opakované uložení téhož jídla šablonu přepíše, nezaloží druhou', async () => {
|
||||
const day = await addMeal(LOGIN, gyros);
|
||||
await createMealTemplateFromMeal(LOGIN, DATE, day.entries[0].id);
|
||||
|
||||
// Totéž jídlo jindy a s jinou gramáží
|
||||
const later = await addMeal(LOGIN, { ...gyros, date: '2025-03-10', price: 22000 });
|
||||
const templates = await createMealTemplateFromMeal(LOGIN, '2025-03-10', later.entries[0].id);
|
||||
|
||||
expect(templates).toHaveLength(1);
|
||||
expect(templates[0].weight).toBe(500);
|
||||
});
|
||||
|
||||
it('u jídla s ručními kaloriemi uloží absolutní hodnotu', async () => {
|
||||
const day = await addMeal(LOGIN, {
|
||||
date: DATE, mealType: MealType.SNIDANE, name: 'Jablko', calories: 80,
|
||||
});
|
||||
|
||||
const templates = await createMealTemplateFromMeal(LOGIN, DATE, day.entries[0].id);
|
||||
|
||||
expect(templates[0].calories).toBe(80);
|
||||
expect(templates[0].caloriesPer100g).toBeUndefined();
|
||||
});
|
||||
|
||||
it('hlásí chybu u neexistujícího jídla', async () => {
|
||||
await expect(createMealTemplateFromMeal(LOGIN, DATE, 'neexistuje'))
|
||||
.rejects.toThrow(/nebylo nalezeno/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('překlopení aktivit do šablony tréninku', () => {
|
||||
|
||||
const kliky = {
|
||||
date: DATE, name: 'Kliky', unit: ActivityUnit.OPAKOVANI,
|
||||
quantity: 40, sets: 3, caloriesPer100Units: 50,
|
||||
};
|
||||
const beh = {
|
||||
date: DATE, name: 'Běh', unit: ActivityUnit.MINUTY,
|
||||
quantity: 20, caloriesPer100Units: 1000,
|
||||
};
|
||||
|
||||
it('bez výčtu vezme všechny aktivity dne', async () => {
|
||||
await addActivity(LOGIN, kliky);
|
||||
await addActivity(LOGIN, beh);
|
||||
|
||||
const templates = await createWorkoutTemplateFromActivities(LOGIN, DATE, 'Workout day 3');
|
||||
|
||||
expect(templates).toHaveLength(1);
|
||||
expect(templates[0].items.map(item => item.name)).toEqual(['Kliky', 'Běh']);
|
||||
// 3 × 40 × 0,5 + 20 × 10
|
||||
expect(templates[0].estimatedCalories).toBe(260);
|
||||
});
|
||||
|
||||
it('přenese i počet sérií', async () => {
|
||||
await addActivity(LOGIN, kliky);
|
||||
|
||||
const templates = await createWorkoutTemplateFromActivities(LOGIN, DATE, 'Silový blok');
|
||||
|
||||
expect(templates[0].items[0].sets).toBe(3);
|
||||
expect(templates[0].items[0].quantity).toBe(40);
|
||||
});
|
||||
|
||||
it('s výčtem vezme jen vybrané aktivity', async () => {
|
||||
await addActivity(LOGIN, kliky);
|
||||
await addActivity(LOGIN, beh);
|
||||
const entries = (await getActivityDay(LOGIN, DATE)).entries;
|
||||
const klikyId = entries.find(entry => entry.name === 'Kliky')!.id;
|
||||
|
||||
const templates = await createWorkoutTemplateFromActivities(LOGIN, DATE, 'Jen kliky', [klikyId]);
|
||||
|
||||
expect(templates[0].items).toHaveLength(1);
|
||||
expect(templates[0].items[0].name).toBe('Kliky');
|
||||
});
|
||||
|
||||
it('aktivity v dni zůstanou beze změny', async () => {
|
||||
await addActivity(LOGIN, kliky);
|
||||
|
||||
await createWorkoutTemplateFromActivities(LOGIN, DATE, 'Workout day 3');
|
||||
|
||||
expect((await getActivityDay(LOGIN, DATE)).entries).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('aktivita bez sazby dá položku bez odhadu kalorií', async () => {
|
||||
await addActivity(LOGIN, {
|
||||
date: DATE, name: 'Plavání', unit: ActivityUnit.MINUTY, quantity: 30, calories: 250,
|
||||
});
|
||||
|
||||
const templates = await createWorkoutTemplateFromActivities(LOGIN, DATE, 'Bazén');
|
||||
|
||||
expect(templates[0].items[0].caloriesPer100Units).toBeUndefined();
|
||||
expect(templates[0].estimatedCalories).toBe(0);
|
||||
});
|
||||
|
||||
it('odmítne den bez aktivit a den bez názvu', async () => {
|
||||
await expect(createWorkoutTemplateFromActivities(LOGIN, DATE, 'Prázdný'))
|
||||
.rejects.toThrow(/nejsou žádné aktivity/);
|
||||
|
||||
await addActivity(LOGIN, kliky);
|
||||
await expect(createWorkoutTemplateFromActivities(LOGIN, DATE, ' '))
|
||||
.rejects.toThrow(/název šablony/);
|
||||
});
|
||||
|
||||
it('vytvořená šablona jde rovnou použít', async () => {
|
||||
await addActivity(LOGIN, kliky);
|
||||
const [template] = await createWorkoutTemplateFromActivities(LOGIN, DATE, 'Silový blok');
|
||||
|
||||
const { applyWorkoutTemplate } = await import("../workouts");
|
||||
await applyWorkoutTemplate(LOGIN, template.id, '2025-03-20');
|
||||
|
||||
const [entry] = (await getActivityDay(LOGIN, '2025-03-20')).entries;
|
||||
expect(entry.name).toBe('Kliky');
|
||||
expect(entry.sets).toBe(3);
|
||||
expect(entry.calories).toBe(60);
|
||||
});
|
||||
|
||||
it('šablony jsou oddělené mezi uživateli', async () => {
|
||||
await addActivity(LOGIN, kliky);
|
||||
await createWorkoutTemplateFromActivities(LOGIN, DATE, 'Silový blok');
|
||||
|
||||
expect(await getWorkoutTemplates('nekdo-jiny')).toEqual([]);
|
||||
expect(await getMealTemplates('nekdo-jiny')).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,247 @@
|
||||
process.env.CALORIE_PROVIDER = 'none';
|
||||
|
||||
import { applyWorkoutTemplate, deleteWorkoutTemplate, getWorkoutTemplates, saveWorkoutTemplate } from "../workouts";
|
||||
import { getActivityDay } from "../activities";
|
||||
import { getSettings, saveBasalCalories, saveProfile } from "../settings";
|
||||
import { computeBasalCalories } from "../profile";
|
||||
import { getDayOverview } from "../dayOverview";
|
||||
import { saveSleep } from "../sleep";
|
||||
import { resetMemoryStorage } from "../storage/memory";
|
||||
import { ActivityUnit, Pohlavi, WorkoutItemKind } from "../../../types/gen/types.gen";
|
||||
|
||||
const LOGIN = 'tester';
|
||||
const DATE = '2025-03-03';
|
||||
|
||||
/** Cvik jako položka šablony. */
|
||||
const cvik = (name: string, quantity: number, rate: number) => ({
|
||||
kind: WorkoutItemKind.CVIK,
|
||||
name,
|
||||
unit: ActivityUnit.OPAKOVANI,
|
||||
quantity,
|
||||
caloriesPer100Units: rate,
|
||||
});
|
||||
|
||||
/** Odkaz na jinou šablonu. */
|
||||
const odkaz = (templateRefId: string) => ({ kind: WorkoutItemKind.SABLONA, templateRefId });
|
||||
|
||||
beforeEach(() => resetMemoryStorage());
|
||||
|
||||
describe('skládání šablon z jiných šablon', () => {
|
||||
|
||||
it('šablona složená z jiné sečte kalorie obou', async () => {
|
||||
const [rozcvicka] = await saveWorkoutTemplate(LOGIN, undefined, 'Rozcvička', [cvik('Dřepy', 40, 40)]);
|
||||
|
||||
const templates = await saveWorkoutTemplate(LOGIN, undefined, 'Workout day 1', [
|
||||
odkaz(rozcvicka.id),
|
||||
cvik('Kliky', 60, 50),
|
||||
]);
|
||||
|
||||
const workout = templates.find(t => t.name === 'Workout day 1')!;
|
||||
// 40 dřepů × 0,4 kcal + 60 kliků × 0,5 kcal
|
||||
expect(workout.estimatedCalories).toBe(46);
|
||||
expect(workout.hasNested).toBe(true);
|
||||
});
|
||||
|
||||
it('doplní název a kalorie vnořené šablony', async () => {
|
||||
const [rozcvicka] = await saveWorkoutTemplate(LOGIN, undefined, 'Rozcvička', [cvik('Dřepy', 40, 40)]);
|
||||
await saveWorkoutTemplate(LOGIN, undefined, 'Workout day 1', [odkaz(rozcvicka.id)]);
|
||||
|
||||
const workout = (await getWorkoutTemplates(LOGIN)).find(t => t.name === 'Workout day 1')!;
|
||||
expect(workout.items[0].templateRefName).toBe('Rozcvička');
|
||||
expect(workout.items[0].templateRefCalories).toBe(16);
|
||||
});
|
||||
|
||||
it('použití rozbalí vnořenou šablonu na její cviky', async () => {
|
||||
const [rozcvicka] = await saveWorkoutTemplate(LOGIN, undefined, 'Rozcvička', [
|
||||
cvik('Dřepy', 40, 40),
|
||||
cvik('Výpady', 20, 30),
|
||||
]);
|
||||
const templates = await saveWorkoutTemplate(LOGIN, undefined, 'Workout day 1', [
|
||||
odkaz(rozcvicka.id),
|
||||
cvik('Kliky', 60, 50),
|
||||
]);
|
||||
const workout = templates.find(t => t.name === 'Workout day 1')!;
|
||||
|
||||
await applyWorkoutTemplate(LOGIN, workout.id, DATE);
|
||||
|
||||
const day = await getActivityDay(LOGIN, DATE);
|
||||
expect(day.entries.map(e => e.name)).toEqual(['Dřepy', 'Výpady', 'Kliky']);
|
||||
expect(day.totalCalories).toBe(52);
|
||||
});
|
||||
|
||||
it('počítá, kolik jiných šablon danou šablonu používá', async () => {
|
||||
const [rozcvicka] = await saveWorkoutTemplate(LOGIN, undefined, 'Rozcvička', [cvik('Dřepy', 40, 40)]);
|
||||
await saveWorkoutTemplate(LOGIN, undefined, 'Workout day 1', [odkaz(rozcvicka.id)]);
|
||||
await saveWorkoutTemplate(LOGIN, undefined, 'Workout day 2', [odkaz(rozcvicka.id)]);
|
||||
|
||||
const updated = (await getWorkoutTemplates(LOGIN)).find(t => t.name === 'Rozcvička')!;
|
||||
expect(updated.usedByCount).toBe(2);
|
||||
});
|
||||
|
||||
it('nedovolí zanoření o dvě úrovně', async () => {
|
||||
const [zaklad] = await saveWorkoutTemplate(LOGIN, undefined, 'Základ', [cvik('Dřepy', 40, 40)]);
|
||||
const templates = await saveWorkoutTemplate(LOGIN, undefined, 'Střední', [odkaz(zaklad.id)]);
|
||||
const stredni = templates.find(t => t.name === 'Střední')!;
|
||||
|
||||
// "Střední" už sama skládá jinou šablonu, takže ji nelze vnořit dál
|
||||
await expect(saveWorkoutTemplate(LOGIN, undefined, 'Vrchní', [odkaz(stredni.id)]))
|
||||
.rejects.toThrow(/jen na jednu úroveň/);
|
||||
});
|
||||
|
||||
it('nedovolí začít vnořovat v šabloně, kterou už někdo skládá', async () => {
|
||||
const [rozcvicka] = await saveWorkoutTemplate(LOGIN, undefined, 'Rozcvička', [cvik('Dřepy', 40, 40)]);
|
||||
const [jiny] = await saveWorkoutTemplate(LOGIN, undefined, 'Jiný blok', [cvik('Kliky', 60, 50)]);
|
||||
await saveWorkoutTemplate(LOGIN, undefined, 'Workout day 1', [odkaz(rozcvicka.id)]);
|
||||
|
||||
// Rozcvičku už skládá Workout day 1, takže do ní nelze vnořit další šablonu
|
||||
await expect(saveWorkoutTemplate(LOGIN, rozcvicka.id, 'Rozcvička', [odkaz(jiny.id)]))
|
||||
.rejects.toThrow(/jen na jednu úroveň/);
|
||||
});
|
||||
|
||||
it('nedovolí odkaz sám na sebe', async () => {
|
||||
const [sablona] = await saveWorkoutTemplate(LOGIN, undefined, 'Rozcvička', [cvik('Dřepy', 40, 40)]);
|
||||
|
||||
await expect(saveWorkoutTemplate(LOGIN, sablona.id, 'Rozcvička', [odkaz(sablona.id)]))
|
||||
.rejects.toThrow(/sama na sebe/);
|
||||
});
|
||||
|
||||
it('nedovolí odkaz na neexistující šablonu', async () => {
|
||||
await expect(saveWorkoutTemplate(LOGIN, undefined, 'Workout', [odkaz('neexistuje')]))
|
||||
.rejects.toThrow(/nebyla nalezena/);
|
||||
});
|
||||
|
||||
it('nedovolí smazat šablonu, kterou skládá jiná', async () => {
|
||||
const [rozcvicka] = await saveWorkoutTemplate(LOGIN, undefined, 'Rozcvička', [cvik('Dřepy', 40, 40)]);
|
||||
await saveWorkoutTemplate(LOGIN, undefined, 'Workout day 1', [odkaz(rozcvicka.id)]);
|
||||
|
||||
await expect(deleteWorkoutTemplate(LOGIN, rozcvicka.id)).rejects.toThrow(/nelze smazat/);
|
||||
});
|
||||
|
||||
it('šablonu bez vazeb smazat lze', async () => {
|
||||
const [rozcvicka] = await saveWorkoutTemplate(LOGIN, undefined, 'Rozcvička', [cvik('Dřepy', 40, 40)]);
|
||||
|
||||
expect(await deleteWorkoutTemplate(LOGIN, rozcvicka.id)).toEqual([]);
|
||||
});
|
||||
|
||||
it('šablonám uloženým bez druhu položky doplní CVIK', async () => {
|
||||
// Podoba dat z doby, kdy skládání z jiných šablon ještě neexistovalo
|
||||
const { default: getStorage } = await import("../storage");
|
||||
await getStorage().setData('workouts:' + LOGIN, [{
|
||||
id: 'stara', name: 'Stará šablona',
|
||||
items: [{ name: 'Kliky', unit: ActivityUnit.OPAKOVANI, quantity: 60, caloriesPer100Units: 50 }],
|
||||
}]);
|
||||
|
||||
const [template] = await getWorkoutTemplates(LOGIN);
|
||||
expect(template.items[0].kind).toBe(WorkoutItemKind.CVIK);
|
||||
expect(template.estimatedCalories).toBe(30);
|
||||
expect(template.hasNested).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tělesné údaje a bazální metabolismus', () => {
|
||||
|
||||
/** Muž, 90 kg, 185 cm, ročník 1990. */
|
||||
const muz = { sex: Pohlavi.MUZ, birthYear: 1990, heightCm: 185, weightGrams: 90_000 };
|
||||
|
||||
it('spočítá klidový výdej rovnicí Mifflin–St Jeor', () => {
|
||||
const age = new Date().getFullYear() - 1990;
|
||||
// 10 × 90 + 6,25 × 185 − 5 × věk + 5
|
||||
const expected = Math.round(900 + 1156.25 - 5 * age + 5);
|
||||
|
||||
expect(computeBasalCalories(muz)).toBe(expected);
|
||||
});
|
||||
|
||||
it('u ženy odečítá 161 místo přičtení 5', () => {
|
||||
const zena = { ...muz, sex: Pohlavi.ZENA };
|
||||
|
||||
expect(computeBasalCalories(muz)! - computeBasalCalories(zena)!).toBe(166);
|
||||
});
|
||||
|
||||
it('neúplný profil klidový výdej nespočítá', () => {
|
||||
expect(computeBasalCalories(undefined)).toBeUndefined();
|
||||
expect(computeBasalCalories({ sex: Pohlavi.MUZ, heightCm: 185 })).toBeUndefined();
|
||||
});
|
||||
|
||||
it('uložený profil se propíše do nastavení i do bilance dne', async () => {
|
||||
await saveProfile(LOGIN, muz);
|
||||
|
||||
const settings = await getSettings(LOGIN);
|
||||
expect(settings.computedBasalCalories).toBe(computeBasalCalories(muz));
|
||||
expect(settings.effectiveBasalCalories).toBe(computeBasalCalories(muz));
|
||||
|
||||
const overview = await getDayOverview(LOGIN, DATE);
|
||||
expect(overview.energy.basal).toBe(computeBasalCalories(muz));
|
||||
expect(overview.energy.hasBasal).toBe(true);
|
||||
});
|
||||
|
||||
it('ručně zadaný klidový výdej má přednost před spočítaným', async () => {
|
||||
await saveProfile(LOGIN, muz);
|
||||
await saveBasalCalories(LOGIN, 1800);
|
||||
|
||||
const settings = await getSettings(LOGIN);
|
||||
expect(settings.computedBasalCalories).toBe(computeBasalCalories(muz));
|
||||
expect(settings.effectiveBasalCalories).toBe(1800);
|
||||
expect((await getDayOverview(LOGIN, DATE)).energy.basal).toBe(1800);
|
||||
});
|
||||
|
||||
it('po smazání ručního údaje se vrátí spočítaný z profilu', async () => {
|
||||
await saveProfile(LOGIN, muz);
|
||||
await saveBasalCalories(LOGIN, 1800);
|
||||
await saveBasalCalories(LOGIN, 0);
|
||||
|
||||
expect((await getSettings(LOGIN)).effectiveBasalCalories).toBe(computeBasalCalories(muz));
|
||||
});
|
||||
|
||||
it('odmítne nesmyslné tělesné údaje', async () => {
|
||||
await expect(saveProfile(LOGIN, { ...muz, heightCm: 5 })).rejects.toThrow(/mimo rozsah/);
|
||||
await expect(saveProfile(LOGIN, { ...muz, weightGrams: 5_000_000 })).rejects.toThrow(/mimo rozsah/);
|
||||
await expect(saveProfile(LOGIN, { ...muz, sex: 'JINE' })).rejects.toThrow(/pohlaví/);
|
||||
});
|
||||
|
||||
it('uložení profilu nepřepíše sazby podniků', async () => {
|
||||
const { saveSourceRate } = await import("../settings");
|
||||
await saveSourceRate(LOGIN, 'TechTower', 4400);
|
||||
|
||||
await saveProfile(LOGIN, muz);
|
||||
|
||||
expect((await getSettings(LOGIN)).sourceRates).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('spánek', () => {
|
||||
|
||||
it('uloží se ke dni a vrátí v přehledu', async () => {
|
||||
await saveSleep(LOGIN, DATE, 450);
|
||||
|
||||
expect((await getDayOverview(LOGIN, DATE)).sleepMinutes).toBe(450);
|
||||
});
|
||||
|
||||
it('nulová hodnota záznam odstraní', async () => {
|
||||
await saveSleep(LOGIN, DATE, 450);
|
||||
await saveSleep(LOGIN, DATE, 0);
|
||||
|
||||
expect((await getDayOverview(LOGIN, DATE)).sleepMinutes).toBeUndefined();
|
||||
});
|
||||
|
||||
it('do energetické bilance nevstupuje', async () => {
|
||||
await saveProfile(LOGIN, { sex: Pohlavi.MUZ, birthYear: 1990, heightCm: 185, weightGrams: 90_000 });
|
||||
const bezSpanku = await getDayOverview(LOGIN, DATE);
|
||||
|
||||
await saveSleep(LOGIN, DATE, 450);
|
||||
const seSpankem = await getDayOverview(LOGIN, DATE);
|
||||
|
||||
// Klidový výdej pokrývá celých 24 hodin včetně spánku, bilance se měnit nesmí
|
||||
expect(seSpankem.energy).toEqual(bezSpanku.energy);
|
||||
});
|
||||
|
||||
it('odmítne nesmyslnou délku', async () => {
|
||||
await expect(saveSleep(LOGIN, DATE, 2000)).rejects.toThrow(/mimo rozsah/);
|
||||
await expect(saveSleep(LOGIN, 'včera', 450)).rejects.toThrow(/platné datum/);
|
||||
});
|
||||
|
||||
it('je oddělený mezi uživateli', async () => {
|
||||
await saveSleep(LOGIN, DATE, 450);
|
||||
|
||||
expect((await getDayOverview('nekdo-jiny', DATE)).sleepMinutes).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user