diff --git a/apps/api/features/step-definitions/profile.steps.ts b/apps/api/features/step-definitions/profile.steps.ts index 028c9b8..1503d92 100644 --- a/apps/api/features/step-definitions/profile.steps.ts +++ b/apps/api/features/step-definitions/profile.steps.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { Then, When } from "@cucumber/cucumber"; -import { prisma } from "../../src/db/prisma.js"; import { getEnglishKey } from "../../src/db/catalog-en-keys.js"; +import { prisma } from "../../src/db/prisma.js"; import type { CustomWorld } from "../support/world.js"; /** Splits a comma-separated list of names from a `.feature` string into trimmed, non-empty parts. */ diff --git a/apps/api/features/step-definitions/recipe.steps.ts b/apps/api/features/step-definitions/recipe.steps.ts index d5b684f..b18ccbc 100644 --- a/apps/api/features/step-definitions/recipe.steps.ts +++ b/apps/api/features/step-definitions/recipe.steps.ts @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { Given, Then, When } from "@cucumber/cucumber"; -import { prisma } from "../../src/db/prisma.js"; import { getEnglishKey } from "../../src/db/catalog-en-keys.js"; +import { prisma } from "../../src/db/prisma.js"; import { TEST_REFERENCE_DATE } from "../../test-support/reference-date.js"; import type { CustomWorld } from "../support/world.js"; @@ -12,7 +12,9 @@ import type { CustomWorld } from "../support/world.js"; * matching. */ async function findIngredientId(name: string): Promise { - const ingredient = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey(name) } }); + const ingredient = await prisma.ingredient.findFirstOrThrow({ + where: { key: getEnglishKey(name) }, + }); return ingredient.id; } diff --git a/apps/api/features/step-definitions/reference.steps.ts b/apps/api/features/step-definitions/reference.steps.ts index 722e945..82fb3c7 100644 --- a/apps/api/features/step-definitions/reference.steps.ts +++ b/apps/api/features/step-definitions/reference.steps.ts @@ -12,6 +12,9 @@ Then( function (this: CustomWorld, name: string) { const keys = (this.response.body as Array<{ key: string }>).map((item) => item.key); const expectedKey = getEnglishKey(name); - assert.ok(keys.includes(expectedKey), `expected ${JSON.stringify(keys)} to include "${expectedKey}"`); + assert.ok( + keys.includes(expectedKey), + `expected ${JSON.stringify(keys)} to include "${expectedKey}"`, + ); }, ); diff --git a/apps/api/scripts/generate-catalog-i18n.ts b/apps/api/scripts/generate-catalog-i18n.ts index 6852241..cc1a80c 100644 --- a/apps/api/scripts/generate-catalog-i18n.ts +++ b/apps/api/scripts/generate-catalog-i18n.ts @@ -42,8 +42,8 @@ console.log( `diets: ${Object.keys(diets).length}, allergens: ${Object.keys(allergens).length}, ingredients: ${Object.keys(ingredients).length}`, ); -const localePath = here + "../../web/src/locales/fr/translation.json"; +const localePath = `${here}../../web/src/locales/fr/translation.json`; const locale = JSON.parse(readFileSync(localePath, "utf8")); locale.catalog = { diets, allergens, ingredients }; -writeFileSync(localePath, JSON.stringify(locale, null, 2) + "\n"); +writeFileSync(localePath, `${JSON.stringify(locale, null, 2)}\n`); console.log(`wrote ${localePath}`); diff --git a/apps/api/scripts/validate-catalog-en-keys.ts b/apps/api/scripts/validate-catalog-en-keys.ts index 05bab54..d7199b2 100644 --- a/apps/api/scripts/validate-catalog-en-keys.ts +++ b/apps/api/scripts/validate-catalog-en-keys.ts @@ -1,3 +1,4 @@ +import { getEnglishKey } from "../src/db/catalog-en-keys.js"; /** * One-off validation, run by hand: checks that `catalog-en-keys.ts` has an * entry for every diet/allergen/ingredient currently in @@ -6,7 +7,6 @@ * pre-flight check while building/editing the dictionary by hand. */ import { ALLERGENS, DIETS, INGREDIENT_GROUPS } from "../src/db/reference-seed-data.js"; -import { getEnglishKey } from "../src/db/catalog-en-keys.js"; function check(label: string, names: string[]) { const keys = new Map(); @@ -36,7 +36,10 @@ function check(label: string, names: string[]) { } check("Diets", DIETS); -check("Allergens", ALLERGENS.map((a) => a.name)); +check( + "Allergens", + ALLERGENS.map((a) => a.name), +); check( "Ingredients", INGREDIENT_GROUPS.flatMap((g) => g.items.map((i) => i.name)), diff --git a/apps/api/src/db/reference-seed-data.ts b/apps/api/src/db/reference-seed-data.ts index 5c9dade..1d7d63a 100644 --- a/apps/api/src/db/reference-seed-data.ts +++ b/apps/api/src/db/reference-seed-data.ts @@ -960,7 +960,10 @@ export async function seedReferenceData(prisma: PrismaClient): Promise { ); }); for (const { name, icon, category, subcategory } of changed) { - await prisma.ingredient.update({ where: { key: getEnglishKey(name) }, data: { icon, category, subcategory } }); + await prisma.ingredient.update({ + where: { key: getEnglishKey(name) }, + data: { icon, category, subcategory }, + }); } // Re-resolve every ingredient's id (existing + just-created) and every diff --git a/apps/api/test/profile.test.ts b/apps/api/test/profile.test.ts index 9a57cf0..4368267 100644 --- a/apps/api/test/profile.test.ts +++ b/apps/api/test/profile.test.ts @@ -3,8 +3,8 @@ import { faker } from "@faker-js/faker"; import { expect } from "chai"; import request from "supertest"; import { createApp } from "../src/app.js"; -import { prisma } from "../src/db/prisma.js"; import { getEnglishKey } from "../src/db/catalog-en-keys.js"; +import { prisma } from "../src/db/prisma.js"; import { resetDatabase } from "../test-support/reset-db.js"; function buildSignupPayload(): SignupInput { @@ -40,7 +40,9 @@ describe("Profile", () => { it("sets the profile's regime to a valid, seeded diet", async () => { const agent = request.agent(app); await agent.post("/auth/signup").send(buildSignupPayload()); - const diet = await prisma.diet.findFirstOrThrow({ where: { key: getEnglishKey("Végétarien") } }); + const diet = await prisma.diet.findFirstOrThrow({ + where: { key: getEnglishKey("Végétarien") }, + }); const res = await agent.patch("/profile/diet").send({ dietId: diet.id }); @@ -141,8 +143,12 @@ describe("Profile", () => { it("starts empty, then reflects a saved selection", async () => { const agent = request.agent(app); await agent.post("/auth/signup").send(buildSignupPayload()); - const tomate = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey("Tomate") } }); - const oignon = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey("Oignon") } }); + const tomate = await prisma.ingredient.findFirstOrThrow({ + where: { key: getEnglishKey("Tomate") }, + }); + const oignon = await prisma.ingredient.findFirstOrThrow({ + where: { key: getEnglishKey("Oignon") }, + }); const initial = await agent.get("/profile/disliked-ingredients"); expect(initial.body).to.deep.equal([]); @@ -160,8 +166,12 @@ describe("Profile", () => { it("replaces (not merges) the previous selection", async () => { const agent = request.agent(app); await agent.post("/auth/signup").send(buildSignupPayload()); - const tomate = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey("Tomate") } }); - const oignon = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey("Oignon") } }); + const tomate = await prisma.ingredient.findFirstOrThrow({ + where: { key: getEnglishKey("Tomate") }, + }); + const oignon = await prisma.ingredient.findFirstOrThrow({ + where: { key: getEnglishKey("Oignon") }, + }); await agent .patch("/profile/disliked-ingredients") diff --git a/apps/api/test/recipe.test.ts b/apps/api/test/recipe.test.ts index 889e2c7..5cb356e 100644 --- a/apps/api/test/recipe.test.ts +++ b/apps/api/test/recipe.test.ts @@ -4,8 +4,8 @@ import { faker } from "@faker-js/faker"; import { expect } from "chai"; import request from "supertest"; import { createApp } from "../src/app.js"; -import { prisma } from "../src/db/prisma.js"; import { getEnglishKey } from "../src/db/catalog-en-keys.js"; +import { prisma } from "../src/db/prisma.js"; import { resetDatabase } from "../test-support/reset-db.js"; /** See `auth.test.ts` — generated rather than hardcoded, no test fixture looks like a real person's data. */ @@ -22,7 +22,9 @@ function buildSignupPayload(): SignupInput { /** Resolves a reference ingredient's id by its `reference-seed-data.ts` French name (slugified to match its `key`). */ async function ingredientId(name: string): Promise { - const ingredient = await prisma.ingredient.findFirstOrThrow({ where: { key: getEnglishKey(name) } }); + const ingredient = await prisma.ingredient.findFirstOrThrow({ + where: { key: getEnglishKey(name) }, + }); return ingredient.id; } diff --git a/apps/api/test/reference.test.ts b/apps/api/test/reference.test.ts index e7ed4e0..8fb423d 100644 --- a/apps/api/test/reference.test.ts +++ b/apps/api/test/reference.test.ts @@ -1,9 +1,9 @@ import { expect } from "chai"; import request from "supertest"; import { createApp } from "../src/app.js"; +import { getEnglishKey } from "../src/db/catalog-en-keys.js"; import { prisma } from "../src/db/prisma.js"; import { resetDatabase } from "../test-support/reset-db.js"; -import { getEnglishKey } from "../src/db/catalog-en-keys.js"; describe("Reference data", () => { const app = createApp(); diff --git a/apps/web/cypress/e2e/onboarding.cy.ts b/apps/web/cypress/e2e/onboarding.cy.ts index c8cf368..93c2af8 100644 --- a/apps/web/cypress/e2e/onboarding.cy.ts +++ b/apps/web/cypress/e2e/onboarding.cy.ts @@ -32,8 +32,8 @@ describe("Onboarding wizard (regime → foyer → allergens)", () => { cy.intercept("GET", "**/reference/diets", { statusCode: 200, body: [ - { id: 1, name: "Omnivore" }, - { id: 2, name: "Végétarien" }, + { id: 1, key: "omnivore" }, + { id: 2, key: "vegetarian" }, ], }); cy.intercept("PATCH", "**/profile/diet", { @@ -48,8 +48,8 @@ describe("Onboarding wizard (regime → foyer → allergens)", () => { cy.intercept("GET", "**/reference/allergies", { statusCode: 200, body: [ - { id: 1, name: "Arachides", kind: "ALLERGY" }, - { id: 2, name: "Gluten", kind: "INTOLERANCE" }, + { id: 1, key: "peanuts", kind: "ALLERGY" }, + { id: 2, key: "gluten", kind: "INTOLERANCE" }, ], }); cy.intercept("PATCH", "**/profile/allergies", { statusCode: 200, body: [1] }).as( diff --git a/apps/web/cypress/e2e/preferences.cy.ts b/apps/web/cypress/e2e/preferences.cy.ts index fcaaa64..65f3111 100644 --- a/apps/web/cypress/e2e/preferences.cy.ts +++ b/apps/web/cypress/e2e/preferences.cy.ts @@ -16,15 +16,15 @@ describe("Dietary preferences (/parametres/preferences) — hot saving", () => { cy.intercept("GET", "**/reference/diets", { statusCode: 200, body: [ - { id: 1, name: "Omnivore" }, - { id: 2, name: "Végétarien" }, + { id: 1, key: "omnivore" }, + { id: 2, key: "vegetarian" }, ], }); cy.intercept("GET", "**/reference/allergies", { statusCode: 200, body: [ - { id: 1, name: "Arachides", kind: "ALLERGY" }, - { id: 2, name: "Gluten", kind: "INTOLERANCE" }, + { id: 1, key: "peanuts", kind: "ALLERGY" }, + { id: 2, key: "gluten", kind: "INTOLERANCE" }, ], }); cy.intercept("GET", "**/profile/allergies", { statusCode: 200, body: [2] }); diff --git a/apps/web/cypress/e2e/recipe-form.cy.ts b/apps/web/cypress/e2e/recipe-form.cy.ts index 1464c8a..44ae9fb 100644 --- a/apps/web/cypress/e2e/recipe-form.cy.ts +++ b/apps/web/cypress/e2e/recipe-form.cy.ts @@ -82,10 +82,12 @@ describe("Recipe form — associating ingredients", () => { cy.contains("button", "Enregistrer").should("not.be.disabled").click(); - cy.wait("@createRecipe").its("request.body").should("deep.include", { - name: "Salade de tomates", - ingredients: [{ ingredientId: 1, quantity: 3, unit: "unité" }], - }); + cy.wait("@createRecipe") + .its("request.body") + .should("deep.include", { + name: "Salade de tomates", + ingredients: [{ ingredientId: 1, quantity: 3, unit: "unité" }], + }); cy.url().should("include", "/recettes/42"); }); @@ -152,9 +154,7 @@ describe("Recipe form — associating ingredients", () => { steps: [{ id: 1, description: "Battre les œufs.", picture: null, order: 0 }], }; cy.intercept("GET", "**/recipes/7", { statusCode: 200, body: existingRecipe }); - cy.intercept("PATCH", "**/recipes/7", { statusCode: 200, body: { id: 7 } }).as( - "updateRecipe", - ); + cy.intercept("PATCH", "**/recipes/7", { statusCode: 200, body: { id: 7 } }).as("updateRecipe"); cy.visit("/recettes/7/modifier"); diff --git a/apps/web/cypress/e2e/recipes.cy.ts b/apps/web/cypress/e2e/recipes.cy.ts index 96556e6..318a410 100644 --- a/apps/web/cypress/e2e/recipes.cy.ts +++ b/apps/web/cypress/e2e/recipes.cy.ts @@ -12,9 +12,9 @@ const authenticatedProfile = { dietId: null, }; -const vegetarien = { id: 1, name: "Végétarien" }; -const gluten = { id: 1, name: "Gluten", kind: "INTOLERANCE" }; -const oeufs = { id: 2, name: "Œufs", kind: "ALLERGY" }; +const vegetarien = { id: 1, key: "vegetarian" }; +const gluten = { id: 1, key: "gluten", kind: "INTOLERANCE" }; +const oeufs = { id: 2, key: "eggs", kind: "ALLERGY" }; const ratatouille = { id: 1, @@ -47,7 +47,7 @@ const omeletteDetail = { { ingredient: { id: 10, - name: "Œuf", + key: "egg", icon: "EGG", category: "CREMERIE_FROMAGE", subcategory: "OEUFS", @@ -181,7 +181,10 @@ describe("Recipe catalog", () => { cy.intercept("GET", /\/recipes\?/, { statusCode: 200, body: [] }); cy.intercept("GET", "**/recipes/999", { statusCode: 404, - body: { code: 4041, message: "not found" }, + // ErrorCode.RECIPE_NOT_FOUND (packages/shared/src/errors/error-codes.ts) + // — RecipeDetailPanel only renders the "not found" message for this + // exact code, anything else falls into its generic error state. + body: { code: 4045, message: "not found" }, }); cy.visit("/recettes/999"); @@ -204,9 +207,7 @@ describe("Recipe catalog", () => { cy.wait("@favorite"); cy.get(".favorite-star-button").should("have.class", "is-favorite"); - cy.contains(".recipe-table__name", "Omelette") - .find(".recipe-table__fav-mark") - .should("exist"); + cy.contains(".recipe-table__name", "Omelette").find(".recipe-table__fav-mark").should("exist"); }); it("deletes a recipe after a two-step confirmation, then clears the selection", () => { @@ -231,7 +232,10 @@ describe("Recipe catalog", () => { cy.visit("/recettes"); - cy.contains(".recipes-page__new-button", "Nouvelle recette") - .should("have.attr", "href", "/recettes/nouvelle"); + cy.contains(".recipes-page__new-button", "Nouvelle recette").should( + "have.attr", + "href", + "/recettes/nouvelle", + ); }); });