feat: předělání historie a profilu
This commit is contained in:
1 parent
9e72f431a1
commit
8fa7f601f2
97 files changed
+7502
-1034
No files matched your search
@@ -0,0 +1,157 @@
|
||||
process.env.CALORIE_PROVIDER = 'none';
|
||||
|
||||
import { computeBmi, computeBodyFat, computeBodyMetrics, getBmiCategory, normalizeGoals } from "../bodyMetrics";
|
||||
import { getSettings, saveBasalCalories, saveGoals, saveProfile } from "../settings";
|
||||
import { getWeights, saveWeight } from "../weights";
|
||||
import { getDayOverview } from "../dayOverview";
|
||||
import { addMeal } from "../meals";
|
||||
import { resetMemoryStorage } from "../storage/memory";
|
||||
import { BmiKategorie, MealType, Pohlavi } from "../../../types/gen/types.gen";
|
||||
|
||||
const LOGIN = 'tester';
|
||||
|
||||
const muz = { sex: Pohlavi.MUZ, birthYear: 1990, heightCm: 185 };
|
||||
|
||||
beforeEach(() => resetMemoryStorage());
|
||||
|
||||
describe('BMI a zdravá hmotnost', () => {
|
||||
|
||||
it('spočítá BMI na desetiny', () => {
|
||||
// 90 / 1,85² = 26,296…
|
||||
expect(computeBmi(90_000, 185)).toBe(26.3);
|
||||
});
|
||||
|
||||
it('zařadí BMI do pásem WHO včetně hranic', () => {
|
||||
expect(getBmiCategory(18.4)).toBe(BmiKategorie.PODVAHA);
|
||||
expect(getBmiCategory(18.5)).toBe(BmiKategorie.NORMA);
|
||||
expect(getBmiCategory(24.9)).toBe(BmiKategorie.NORMA);
|
||||
expect(getBmiCategory(25)).toBe(BmiKategorie.NADVAHA);
|
||||
expect(getBmiCategory(30)).toBe(BmiKategorie.OBEZITA);
|
||||
});
|
||||
|
||||
it('rozmezí zdravé hmotnosti odpovídá BMI 18,5–25 pro výšku', () => {
|
||||
const metrics = computeBodyMetrics(muz);
|
||||
|
||||
// 18,5 × 1,85² = 63,3 kg, 25 × 1,85² = 85,6 kg
|
||||
expect(metrics.healthyWeightMinGrams).toBe(63_300);
|
||||
expect(metrics.healthyWeightMaxGrams).toBe(85_600);
|
||||
expect(metrics.bmi).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('podíl tuku (US Navy)', () => {
|
||||
|
||||
it('u muže počítá z pasu a krku', () => {
|
||||
expect(computeBodyFat({ ...muz, waistCm: 90, neckCm: 40 })).toBe(17.6);
|
||||
});
|
||||
|
||||
it('u ženy potřebuje i boky', () => {
|
||||
const zena = { sex: Pohlavi.ZENA, heightCm: 168, waistCm: 72, neckCm: 33 };
|
||||
|
||||
expect(computeBodyFat(zena)).toBeUndefined();
|
||||
// 495 / (1,29579 − 0,35004 × log10(137) + 0,221 × log10(168)) − 450
|
||||
expect(computeBodyFat({ ...zena, hipCm: 98 })).toBe(26.1);
|
||||
});
|
||||
|
||||
it('nesmyslné obvody nedají žádný odhad', () => {
|
||||
expect(computeBodyFat({ ...muz, waistCm: 40, neckCm: 45 })).toBeUndefined();
|
||||
expect(computeBodyFat({ ...muz, waistCm: 90 })).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cíle', () => {
|
||||
|
||||
it('půl kila týdně dolů je denní bilance −550 kcal', () => {
|
||||
expect(computeBodyMetrics(muz, { weeklyChangeGrams: -500 }).targetDailyBalance).toBe(-550);
|
||||
});
|
||||
|
||||
it('bílkoviny počítá z hmotnosti a cílový termín jen ve směru tempa', () => {
|
||||
const metrics = computeBodyMetrics(
|
||||
muz, { weeklyChangeGrams: -500, targetWeightGrams: 85_000, proteinPerKg: 1.6 },
|
||||
{ date: '2026-10-01', weightGrams: 90_000 });
|
||||
|
||||
expect(metrics.proteinTargetGrams).toBe(144);
|
||||
expect(metrics.weeksToTarget).toBe(10);
|
||||
expect(computeBodyMetrics(muz, { weeklyChangeGrams: 250, targetWeightGrams: 85_000 },
|
||||
{ date: '2026-10-01', weightGrams: 90_000 }).weeksToTarget).toBeUndefined();
|
||||
});
|
||||
|
||||
it('cíle se ukládají znormalizované a prázdné se odstraní', async () => {
|
||||
const settings = await saveGoals(LOGIN, { weeklyChangeGrams: -500, proteinPerKg: 1.64 });
|
||||
expect(settings.goals).toEqual({ weeklyChangeGrams: -500, proteinPerKg: 1.6 });
|
||||
|
||||
expect((await saveGoals(LOGIN, {})).goals).toBeUndefined();
|
||||
});
|
||||
|
||||
it('odmítne cíl mimo rozsah', () => {
|
||||
expect(() => normalizeGoals({ weeklyChangeGrams: -5000 })).toThrow(/mimo rozsah/);
|
||||
expect(() => normalizeGoals({ proteinPerKg: 10 })).toThrow(/mimo rozsah/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('vážení po dnech', () => {
|
||||
|
||||
it('ukládá, přepisuje a maže vážení dne', async () => {
|
||||
await saveWeight(LOGIN, '2026-10-02', 89_500);
|
||||
await saveWeight(LOGIN, '2026-10-01', 90_000);
|
||||
await saveWeight(LOGIN, '2026-10-02', 89_400);
|
||||
|
||||
expect(await getWeights(LOGIN)).toEqual([
|
||||
{ date: '2026-10-01', weightGrams: 90_000 },
|
||||
{ date: '2026-10-02', weightGrams: 89_400 },
|
||||
]);
|
||||
|
||||
await saveWeight(LOGIN, '2026-10-02', null);
|
||||
expect(await getWeights(LOGIN)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('odmítne hmotnost mimo rozsah', async () => {
|
||||
await expect(saveWeight(LOGIN, '2026-10-01', 900)).rejects.toThrow(/mimo rozsah/);
|
||||
});
|
||||
|
||||
it('ukazatele v nastavení počítají s posledním vážením místo hmotnosti z profilu', async () => {
|
||||
await saveProfile(LOGIN, { ...muz, weightGrams: 95_000 });
|
||||
await saveWeight(LOGIN, '2026-10-01', 90_000);
|
||||
|
||||
const fromWeighing = await getSettings(LOGIN);
|
||||
await saveWeight(LOGIN, '2026-10-01', null);
|
||||
const fromProfile = await getSettings(LOGIN);
|
||||
|
||||
expect(fromWeighing.bodyMetrics).toMatchObject({ weightGrams: 90_000, weightDate: '2026-10-01', bmi: 26.3 });
|
||||
expect(fromProfile.bodyMetrics?.weightDate).toBeUndefined();
|
||||
// 10 kcal na kilogram rozdílu
|
||||
expect(fromProfile.computedBasalCalories! - fromWeighing.computedBasalCalories!).toBe(50);
|
||||
});
|
||||
|
||||
it('bilance dne bere hmotnost platnou k tomu dni, ne poslední', async () => {
|
||||
await saveProfile(LOGIN, muz);
|
||||
await saveWeight(LOGIN, '2026-09-01', 95_000);
|
||||
await saveWeight(LOGIN, '2026-10-01', 90_000);
|
||||
|
||||
const september = await getDayOverview(LOGIN, '2026-09-15');
|
||||
const october = await getDayOverview(LOGIN, '2026-10-05');
|
||||
|
||||
expect(september.energy.basal - october.energy.basal).toBe(50);
|
||||
expect(september.weightGrams).toBeUndefined();
|
||||
expect((await getDayOverview(LOGIN, '2026-10-01')).weightGrams).toBe(90_000);
|
||||
});
|
||||
|
||||
it('ruční klidový výdej má přednost i před vážením', async () => {
|
||||
await saveProfile(LOGIN, muz);
|
||||
await saveWeight(LOGIN, '2026-10-01', 90_000);
|
||||
await saveBasalCalories(LOGIN, 1500);
|
||||
|
||||
expect((await getDayOverview(LOGIN, '2026-10-01')).energy.basal).toBe(1500);
|
||||
});
|
||||
|
||||
it('přehled dne nese cílovou bilanci a cíl bílkovin', async () => {
|
||||
await saveWeight(LOGIN, '2026-10-01', 90_000);
|
||||
await saveGoals(LOGIN, { weeklyChangeGrams: -500, proteinPerKg: 2 });
|
||||
await addMeal(LOGIN, { date: '2026-10-01', mealType: MealType.OBED, name: 'Guláš', calories: 700 });
|
||||
|
||||
const overview = await getDayOverview(LOGIN, '2026-10-01');
|
||||
|
||||
expect(overview.energy.targetBalance).toBe(-550);
|
||||
expect(overview.proteinTargetGrams).toBe(180);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user