fix: Jen s chybějícímí údaji -> k doplnění
feat: zobrazení aktuálního počtu jídel feat: chuť a typ receptu
This commit is contained in:
1 parent
b637e243d5
commit
35cbc5a9ca
11 files changed
+206
-21
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import { MealTemplate, MealType, RecipeIngredient } from "../../types/gen/types.gen";
|
||||
import { MealTemplate, MealType, RecipeFlavor, RecipeIngredient } from "../../types/gen/types.gen";
|
||||
import getStorage from "./storage";
|
||||
import { addMeal, getNameSourceKey, isMealType } from "./meals";
|
||||
import { rememberCalories } from "./calories";
|
||||
@@ -22,8 +22,9 @@ const getKey = (login: string): string => `mealTemplates:${login}`;
|
||||
* i šablony hotových jídel (druh `HOTOVE`, nebo bez druhu), recepty nesly
|
||||
* `kind: 'RECEPT'` a měly i chod.
|
||||
*/
|
||||
type StoredMealTemplate = Omit<MealTemplate, 'ingredients'> & {
|
||||
type StoredMealTemplate = Omit<MealTemplate, 'ingredients' | 'flavor'> & {
|
||||
ingredients?: RecipeIngredient[];
|
||||
flavor?: RecipeFlavor;
|
||||
kind?: string;
|
||||
pricePer100g?: number;
|
||||
mealType?: MealType;
|
||||
@@ -37,12 +38,28 @@ type StoredRecipe = StoredMealTemplate & { ingredients: RecipeIngredient[] };
|
||||
*/
|
||||
const isRecipe = (stored: StoredMealTemplate): stored is StoredRecipe => !!stored.ingredients?.length;
|
||||
|
||||
/** Odstraní z receptu pole, která patřila jen šablonám hotových jídel, a zaniklý chod. */
|
||||
/**
|
||||
* Odstraní z receptu pole, která patřila jen šablonám hotových jídel, a zaniklý chod.
|
||||
* Slaný recept je výchozí stav a v úložišti chuť nemá, doplní se tady.
|
||||
*/
|
||||
const toMealTemplate = (stored: StoredRecipe): MealTemplate => {
|
||||
const { kind, pricePer100g, mealType, ...result } = stored;
|
||||
return result;
|
||||
const { kind, pricePer100g, mealType, ...rest } = stored;
|
||||
return { ...rest, flavor: rest.flavor ?? RecipeFlavor.SLANE };
|
||||
};
|
||||
|
||||
/** Chuť receptu ze vstupu; bez ní slaný. */
|
||||
const normalizeFlavor = (value: unknown): RecipeFlavor => {
|
||||
if (value == null) return RecipeFlavor.SLANE;
|
||||
if (value !== RecipeFlavor.SLANE && value !== RecipeFlavor.SLADKE) {
|
||||
throw new BadRequestError("Nebyla předána platná chuť receptu");
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/** Recept tak, jak se ukládá — výchozí slanou chuť neukládá. */
|
||||
const toStored = ({ flavor, ...rest }: MealTemplate): StoredMealTemplate =>
|
||||
flavor === RecipeFlavor.SLANE ? rest : { ...rest, flavor };
|
||||
|
||||
/** Načte recepty z úložiště; šablony hotových jídel vynechá. */
|
||||
const loadRecipes = (storedList: StoredMealTemplate[] | undefined): MealTemplate[] =>
|
||||
(storedList ?? []).filter(isRecipe).map(toMealTemplate);
|
||||
@@ -214,6 +231,8 @@ export const saveMealTemplate = async (login: string, input: unknown): Promise<M
|
||||
id: typeof value.id === 'string' && value.id.length ? value.id : randomUUID(),
|
||||
name,
|
||||
source: normalizeText(value.source, 'source'),
|
||||
category: normalizeText(value.category, 'category'),
|
||||
flavor: normalizeFlavor(value.flavor),
|
||||
note: normalizeText(value.note, 'note', MAX_NOTE_LENGTH),
|
||||
ingredients,
|
||||
// Jedna porce je výchozí stav, ukládat ji zvlášť nemá smysl
|
||||
@@ -236,8 +255,8 @@ export const saveMealTemplate = async (login: string, input: unknown): Promise<M
|
||||
}
|
||||
|
||||
await storage.updateData<StoredMealTemplate[]>(getKey(login), current => [
|
||||
...loadRecipes(current).filter(item => item.id !== template.id),
|
||||
template,
|
||||
...loadRecipes(current).filter(item => item.id !== template.id).map(toStored),
|
||||
toStored(template),
|
||||
]);
|
||||
return getMealTemplates(login);
|
||||
};
|
||||
@@ -256,7 +275,7 @@ export const deleteMealTemplate = async (login: string, id: unknown): Promise<Me
|
||||
await storage.updateData<StoredMealTemplate[]>(getKey(login), current => {
|
||||
const recipeList = loadRecipes(current);
|
||||
found = recipeList.some(item => item.id === id);
|
||||
return recipeList.filter(item => item.id !== id);
|
||||
return recipeList.filter(item => item.id !== id).map(toStored);
|
||||
});
|
||||
if (!found) {
|
||||
throw new NotFoundError("Recept nebyl nalezen");
|
||||
@@ -329,5 +348,5 @@ export const renameMealTemplate = async (
|
||||
if (!recipe) return;
|
||||
const renamed: MealTemplate = definedOnly({ ...recipe, name, source });
|
||||
await storage.updateData<StoredMealTemplate[]>(getKey(login), current =>
|
||||
loadRecipes(current).map(item => item.id === recipe.id ? renamed : item));
|
||||
loadRecipes(current).map(item => toStored(item.id === recipe.id ? renamed : item)));
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import { getMealGroups, renameMealGroup } from "../mealGroups";
|
||||
import { getMealTemplates, saveMealTemplate } from "../mealTemplates";
|
||||
import { addMealPhoto, deleteMealPhoto, getMealPhotos } from "../mealPhotos";
|
||||
import { resetMemoryStorage } from "../storage/memory";
|
||||
import { MealType } from "../../../types/gen/types.gen";
|
||||
import { MealType, RecipeFlavor } from "../../../types/gen/types.gen";
|
||||
|
||||
const LOGIN = 'tester';
|
||||
const IMAGE = 'data:image/jpeg;base64,AAAA';
|
||||
@@ -62,6 +62,25 @@ describe('recept jídla v seznamu jídel', () => {
|
||||
expect(await getMealPhotos(LOGIN, 'Rizoto', 'doma')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('recept je bez zadané chuti slaný a typ i chuť přežijí přejmenování', async () => {
|
||||
const [created] = await saveMealTemplate(LOGIN, rizoto);
|
||||
expect(created).toMatchObject({ flavor: RecipeFlavor.SLANE });
|
||||
expect(created).not.toHaveProperty('category');
|
||||
|
||||
const [saved] = await saveMealTemplate(LOGIN, {
|
||||
...rizoto, id: created.id, category: ' Pečení ', flavor: RecipeFlavor.SLADKE,
|
||||
});
|
||||
await renameMealGroup(LOGIN, 'Rizoto', 'doma', 'Sladké rizoto', 'doma');
|
||||
|
||||
expect(await getMealTemplates(LOGIN)).toMatchObject([
|
||||
{ id: saved.id, name: 'Sladké rizoto', category: 'Pečení', flavor: RecipeFlavor.SLADKE },
|
||||
]);
|
||||
});
|
||||
|
||||
it('odmítne neznámou chuť receptu', async () => {
|
||||
await expect(saveMealTemplate(LOGIN, { ...rizoto, flavor: 'KYSELE' })).rejects.toThrow(/chuť/);
|
||||
});
|
||||
|
||||
it('dvě jídla s recepty přejmenováním nesloučí', async () => {
|
||||
await saveMealTemplate(LOGIN, rizoto);
|
||||
await saveMealTemplate(LOGIN, { ...rizoto, name: 'Rizoto 2' });
|
||||
|
||||
Reference in new issue
Block a user