feat: zlepšení šablon, přidány osobní údaje a spánkový režim

This commit is contained in:
Ondřej Anděl committed 2026-09-15 14:16:42 +02:00
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([]);
});
});
+168
View File
@@ -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([]);
});
});
+247
View File
@@ -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();
});
});