Remplace les 18 catégories plates (mélangeant rayons génériques et cuisines d'origine — un ingrédient pouvait finir dans "légumes" ou "cuisine italienne" selon l'angle choisi) par une hiérarchie à 2 niveaux sur le modèle d'un supermarché français : - 🥦 Produits frais (légumes, fruits, herbes fraîches) - 🥩 Boucherie & poissonnerie (viandes, volailles, poissons, crustacés & fruits de mer) - 🥫 Épicerie sèche (féculents, légumineuses, graines & fruits secs, autres produits secs/conserves) - 🍞 Boulangerie (pains, pâtes à cuire — 4 nouveaux ingrédients : pâte feuilletée/brisée/à pizza/à tarte sablée) - 🧈 Crémerie & fromage (produits laitiers, œufs, alternatives végétales) - 🧂 Condiments & épices (épices, sauces, assaisonnements) - 🍳 Aides culinaires (bases, épaississants, sucres) - Schéma : nouvel enum IngredientCategory (7 valeurs) + nouvel enum IngredientSubcategory (22 valeurs) + colonne Ingredient.subcategory. Migration appliquée via reset (données de référence, aucune perte réelle) car les anciennes valeurs d'enum n'existent plus dans les nouvelles. - reference-seed-data.ts entièrement réorganisé par (catégorie, sous- catégorie), tous les 437 ingrédients recatégorisés un par un. - packages/shared : INGREDIENT_CATEGORIES (7), INGREDIENT_SUBCATEGORIES (22) et INGREDIENT_CATEGORY_SUBCATEGORIES (mapping catégorie → sous-catégories, pour piloter le picker) remplacent l'ancienne liste plate. - IngredientPicker : drill-down à 2 niveaux — la ligne de sous- catégories (couleur --color-tag, visuellement subordonnée) apparaît une fois une catégorie choisie, et se réinitialise au changement de catégorie. - i18n : nouvelles clés recipes.form.category.* (7) et recipes.form.subcategory.* (22). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
import { expect } from "chai";
|
|
import request from "supertest";
|
|
import { createApp } from "../src/app.js";
|
|
import { prisma } from "../src/db/prisma.js";
|
|
import { resetDatabase } from "../test-support/reset-db.js";
|
|
|
|
describe("Reference data", () => {
|
|
const app = createApp();
|
|
|
|
beforeEach(async () => {
|
|
await resetDatabase();
|
|
});
|
|
|
|
after(async () => {
|
|
await prisma.$disconnect();
|
|
});
|
|
|
|
describe("GET /reference/diets", () => {
|
|
it("returns the seeded regimes, no session required", async () => {
|
|
const res = await request(app).get("/reference/diets");
|
|
|
|
expect(res.status).to.equal(200);
|
|
expect(res.body).to.have.length(5);
|
|
expect(res.body.map((d: { name: string }) => d.name)).to.include("Végétarien");
|
|
expect(res.body[0]).to.have.keys(["id", "name"]);
|
|
});
|
|
});
|
|
|
|
describe("GET /reference/allergies", () => {
|
|
it("returns the seeded allergens with their name resolved, no session required", async () => {
|
|
const res = await request(app).get("/reference/allergies");
|
|
|
|
expect(res.status).to.equal(200);
|
|
expect(res.body).to.have.length(14);
|
|
expect(res.body.map((a: { name: string }) => a.name)).to.include("Arachides");
|
|
expect(res.body[0]).to.have.keys(["id", "name", "kind"]);
|
|
});
|
|
|
|
it("classifies Gluten and Sulfites as intolerances, the rest as allergies", async () => {
|
|
const res = await request(app).get("/reference/allergies");
|
|
|
|
const byName = (name: string) => res.body.find((a: { name: string }) => a.name === name);
|
|
expect(byName("Gluten").kind).to.equal("INTOLERANCE");
|
|
expect(byName("Sulfites").kind).to.equal("INTOLERANCE");
|
|
expect(byName("Arachides").kind).to.equal("ALLERGY");
|
|
expect(res.body.filter((a: { kind: string }) => a.kind === "INTOLERANCE")).to.have.length(2);
|
|
});
|
|
});
|
|
|
|
describe("GET /reference/ingredients", () => {
|
|
it("returns the seeded ingredients, no session required", async () => {
|
|
const res = await request(app).get("/reference/ingredients");
|
|
|
|
expect(res.status).to.equal(200);
|
|
expect(res.body.length).to.be.greaterThan(0);
|
|
expect(res.body.map((i: { name: string }) => i.name)).to.include("Tomate");
|
|
expect(res.body[0]).to.have.keys([
|
|
"id",
|
|
"name",
|
|
"icon",
|
|
"category",
|
|
"subcategory",
|
|
"allergens",
|
|
"diets",
|
|
]);
|
|
});
|
|
|
|
it("resolves each ingredient's linked allergens, empty for one with none", async () => {
|
|
const res = await request(app).get("/reference/ingredients");
|
|
|
|
const byName = (name: string) => res.body.find((i: { name: string }) => i.name === name);
|
|
expect(byName("Œuf").allergens.map((a: { name: string }) => a.name)).to.include("Œufs");
|
|
expect(byName("Tomate").allergens).to.deep.equal([]);
|
|
});
|
|
});
|
|
});
|