feat: split jídel
feat: graf příjmů
This commit is contained in:
1 parent
bc85a37fc0
commit
b880b36edd
32 files changed
+2048
-262
No files matched your search
@@ -0,0 +1,90 @@
|
||||
process.env.CALORIE_PROVIDER = 'none';
|
||||
|
||||
import { addMeal } from "../meals";
|
||||
import { addActivity } from "../activities";
|
||||
import { saveBasalCalories, saveGoals } from "../settings";
|
||||
import { getIntakeStats } from "../statsService";
|
||||
import { resetMemoryStorage } from "../storage/memory";
|
||||
import { ActivityUnit, MealType } from "../../../types/gen/types.gen";
|
||||
|
||||
const LOGIN = 'tester';
|
||||
|
||||
const stats = () => getIntakeStats(LOGIN, '2025-03-01', '2025-03-31');
|
||||
|
||||
beforeEach(() => resetMemoryStorage());
|
||||
|
||||
describe('statistiky příjmu', () => {
|
||||
|
||||
it('sečte příjem po dnech a chodech včetně násobku, jídla bez kalorií jen spočítá', async () => {
|
||||
await addMeal(LOGIN, { date: '2025-03-03', mealType: MealType.SNIDANE, name: 'Kaše', calories: 300 });
|
||||
await addMeal(LOGIN, {
|
||||
date: '2025-03-03', mealType: MealType.OBED, name: 'Čína', source: 'Chefie', calories: 600, multiplier: 0.5,
|
||||
});
|
||||
await addMeal(LOGIN, { date: '2025-03-04', mealType: MealType.OBED, name: 'Čína', source: 'Chefie', calories: 600 });
|
||||
await addMeal(LOGIN, { date: '2025-03-04', mealType: MealType.VECERE, name: 'Neznámo', price: 9900 });
|
||||
|
||||
const result = await stats();
|
||||
|
||||
expect(result).toMatchObject({
|
||||
totalCalories: 1200, entryCount: 4, missingCaloriesCount: 1, dayCount: 2, caloriesDayCount: 2,
|
||||
});
|
||||
expect(result.byDay.map(day => [day.period, day.calories])).toEqual([['2025-03-03', 600], ['2025-03-04', 600]]);
|
||||
expect(result.byMonth).toEqual([expect.objectContaining({ period: '2025-03', calories: 1200, dayCount: 2 })]);
|
||||
expect(result.byMealType).toEqual([
|
||||
{ key: MealType.OBED, calories: 900, count: 2 },
|
||||
{ key: MealType.SNIDANE, calories: 300, count: 1 },
|
||||
]);
|
||||
expect(result.bySource).toEqual([
|
||||
{ key: 'Chefie', calories: 900, count: 2 },
|
||||
{ key: 'Neuvedeno', calories: 300, count: 1 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('výdej počítá jen za dny, kdy má nějaké jídlo kalorie', async () => {
|
||||
await saveBasalCalories(LOGIN, 1800);
|
||||
await addMeal(LOGIN, { date: '2025-03-03', mealType: MealType.OBED, name: 'Čína', calories: 700 });
|
||||
await addActivity(LOGIN, {
|
||||
date: '2025-03-03', name: 'Chůze', unit: ActivityUnit.KROKY, quantity: 5000, caloriesPer100Units: 4,
|
||||
});
|
||||
// Pohyb v den bez jídla do výdeje nevstoupí
|
||||
await addActivity(LOGIN, {
|
||||
date: '2025-03-05', name: 'Chůze', unit: ActivityUnit.KROKY, quantity: 5000, caloriesPer100Units: 4,
|
||||
});
|
||||
// Den, kde žádné jídlo kalorie nemá (import), by jinak vyšel jako deficit celého výdeje
|
||||
await addMeal(LOGIN, { date: '2025-03-06', mealType: MealType.OBED, name: 'Neznámo', price: 9900 });
|
||||
|
||||
const result = await stats();
|
||||
|
||||
expect(result.hasBasal).toBe(true);
|
||||
expect(result.burnCalories).toBe(2000);
|
||||
expect(result).toMatchObject({ dayCount: 2, caloriesDayCount: 1 });
|
||||
expect(result.byDay).toEqual([
|
||||
expect.objectContaining({ period: '2025-03-03', burnCalories: 2000, caloriesDayCount: 1 }),
|
||||
expect.objectContaining({ period: '2025-03-06', burnCalories: 0, caloriesDayCount: 0 }),
|
||||
]);
|
||||
});
|
||||
|
||||
it('bez klidového výdeje to pozná a cíl vrátí, jen když je nastavený', async () => {
|
||||
await addMeal(LOGIN, { date: '2025-03-03', mealType: MealType.OBED, name: 'Čína', calories: 700 });
|
||||
|
||||
const before = await stats();
|
||||
expect(before.hasBasal).toBe(false);
|
||||
expect(before.targetDailyBalance).toBeUndefined();
|
||||
|
||||
await saveGoals(LOGIN, { weeklyChangeGrams: -500 });
|
||||
expect((await stats()).targetDailyBalance).toBe(-550);
|
||||
});
|
||||
|
||||
it('živiny sečte jen ze dnů, kde je jídlo má', async () => {
|
||||
await addMeal(LOGIN, {
|
||||
date: '2025-03-03', mealType: MealType.OBED, name: 'Kuře', weight: 200, caloriesPer100g: 150,
|
||||
nutrientsPer100g: { protein: 25 },
|
||||
});
|
||||
await addMeal(LOGIN, { date: '2025-03-04', mealType: MealType.OBED, name: 'Čína', calories: 700 });
|
||||
|
||||
const result = await stats();
|
||||
|
||||
expect(result.totalNutrients).toEqual({ protein: 50 });
|
||||
expect(result.nutrientsDayCount).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -25,10 +25,29 @@ describe('skupiny jídel podle názvu a zdroje', () => {
|
||||
name: 'Čína', source: 'Chefie', count: 3, lastDate: '2025-03-03',
|
||||
missingCaloriesCount: 2, missingWeightCount: 2, missingNutrientsCount: 3,
|
||||
values: { weight: 400, caloriesPer100g: 150 },
|
||||
days: [
|
||||
{ date: '2025-03-03', incomplete: true },
|
||||
{ date: '2025-03-02', incomplete: true },
|
||||
{ date: '2025-03-01', incomplete: true },
|
||||
],
|
||||
});
|
||||
expect(groups[1]).toMatchObject({ source: 'doma', count: 1 });
|
||||
});
|
||||
|
||||
it('u dnů skupiny označí ty, kde jídlu něco chybí', async () => {
|
||||
await addMeal(LOGIN, {
|
||||
...cina, date: '2025-03-01', weight: 400, caloriesPer100g: 150, nutrientsPer100g: { protein: 8 },
|
||||
});
|
||||
await addMeal(LOGIN, { ...cina, date: '2025-03-02', price: 13900 });
|
||||
|
||||
const [group] = await getMealGroups(LOGIN);
|
||||
|
||||
expect(group.days).toEqual([
|
||||
{ date: '2025-03-02', incomplete: true },
|
||||
{ date: '2025-03-01', incomplete: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it('energii a živiny předvyplní i z jídla stejného názvu odjinud, sazbu ne', async () => {
|
||||
await addMeal(LOGIN, {
|
||||
...cina, date: '2025-03-01', source: 'doma', pricePer100g: 2000, caloriesPer100g: 140,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
process.env.CALORIE_PROVIDER = 'none';
|
||||
|
||||
import { addEntries, addMeal, getDay, splitMeal } from "../meals";
|
||||
import { IMPORT_SOURCE } from "../luncherImport";
|
||||
import { resetMemoryStorage } from "../storage/memory";
|
||||
import { MealType } from "../../../types/gen/types.gen";
|
||||
|
||||
const LOGIN = 'tester';
|
||||
const DATE = '2025-03-03';
|
||||
|
||||
beforeEach(() => resetMemoryStorage());
|
||||
|
||||
describe('rozdělení jídla', () => {
|
||||
|
||||
it('upraví původní jídlo a hned za něj založí nové ve stejném dni', async () => {
|
||||
await addMeal(LOGIN, { date: DATE, mealType: MealType.SNIDANE, name: 'Kaše', calories: 300 });
|
||||
const day = await addMeal(LOGIN, {
|
||||
date: DATE, mealType: MealType.OBED, name: 'Tikka masala+garlic naan', source: 'Everest', price: 19900,
|
||||
nutrientsPer100g: { protein: 9 }, multiplier: 0.5,
|
||||
});
|
||||
const original = day.entries.find(entry => entry.mealType === MealType.OBED)!;
|
||||
|
||||
const result = await splitMeal(LOGIN, original.id, DATE,
|
||||
{
|
||||
mealType: MealType.OBED, name: 'Tikka masala', source: 'Everest', price: 19900,
|
||||
weight: 400, caloriesPer100g: 150, nutrientsPer100g: { protein: 9 }, multiplier: 0.5,
|
||||
},
|
||||
// Datum části se ignoruje, obě zůstávají v rozdělovaném dni
|
||||
{ date: '2025-03-10', mealType: MealType.OBED, name: 'Garlic naan', source: 'Everest', calories: 280 });
|
||||
|
||||
expect(result.entries.map(entry => entry.name)).toEqual(['Kaše', 'Tikka masala', 'Garlic naan']);
|
||||
const [, updated, created] = result.entries;
|
||||
expect(updated).toMatchObject({
|
||||
id: original.id, createdAt: original.createdAt, price: 19900, calories: 600, multiplier: 0.5,
|
||||
nutrientsPer100g: { protein: 9 },
|
||||
});
|
||||
expect(created.id).not.toBe(original.id);
|
||||
expect(created).toMatchObject({ date: DATE, price: undefined, calories: 280 });
|
||||
expect(result.totalCalories).toBe(300 + 300 + 280);
|
||||
expect((await getDay(LOGIN, '2025-03-10')).entries).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('odštěpená část převezme příznak importu, ale ne jeho klíč', async () => {
|
||||
await addEntries(LOGIN, [{
|
||||
id: 'imp-1', createdAt: '2025-03-03T10:00:00.000Z', date: DATE, mealType: MealType.OBED,
|
||||
name: 'Tikka masala+garlic naan', price: 19900, importSource: IMPORT_SOURCE, importKey: 'klic-1',
|
||||
}]);
|
||||
|
||||
const result = await splitMeal(LOGIN, 'imp-1', DATE,
|
||||
{ mealType: MealType.OBED, name: 'Tikka masala', price: 19900 },
|
||||
{ mealType: MealType.OBED, name: 'Garlic naan' });
|
||||
|
||||
const [updated, created] = result.entries;
|
||||
expect(updated).toMatchObject({ importSource: IMPORT_SOURCE, importKey: 'klic-1' });
|
||||
expect(created.importSource).toBe(IMPORT_SOURCE);
|
||||
expect(created.importKey).toBeUndefined();
|
||||
});
|
||||
|
||||
it('neexistující jídlo ani chybějící název nerozdělí', async () => {
|
||||
const day = await addMeal(LOGIN, { date: DATE, mealType: MealType.OBED, name: 'Čína' });
|
||||
const { id } = day.entries[0];
|
||||
|
||||
await expect(splitMeal(LOGIN, 'neni', DATE, { mealType: MealType.OBED, name: 'A' }, { mealType: MealType.OBED, name: 'B' }))
|
||||
.rejects.toThrow('nebyl nalezen');
|
||||
await expect(splitMeal(LOGIN, id, DATE, { mealType: MealType.OBED, name: 'A' }, { mealType: MealType.OBED, name: ' ' }))
|
||||
.rejects.toThrow('název');
|
||||
expect((await getDay(LOGIN, DATE)).entries).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user