- onboarding.cy.ts / preferences.cy.ts / recipes.cy.ts mockaient encore
GET /reference/diets|allergies avec l'ancienne forme {id, name}. Depuis
les deux derniers commits l'API renvoie {id, key} (uid anglais) et le
composant résout le libellé via i18n (t(`catalog.diets.${key}`)) — avec
key manquant, ça affichait littéralement "catalog.diets.undefined" au
lieu de "Végétarien"/"Omnivore"/etc., faisant échouer cy.select()/
cy.contains() dans ces 3 specs. Corrigé pour mocker {key: "vegetarian"},
{key: "peanuts"}, etc.
- recipes.cy.ts : le test "shows a not-found message" utilisait le
mauvais code d'erreur (4041 au lieu de ErrorCode.RECIPE_NOT_FOUND =
4045), donc RecipeDetailPanel tombait dans son état d'erreur générique
au lieu du message "Cette recette n'existe pas." — bug dans mon propre
test, sans rapport avec le refactor.
- pnpm lint (biome) : les fichiers touchés par le refactor précédent
avaient quelques soucis de formatage/tri d'imports (des sed multi-
fichiers, pas d'édition via l'outil habituel) — corrigés par
`biome check --write`.
Vérifié : ces 3 specs + recipe-form.cy.ts passent maintenant dans le job
CI GitHub Actions (Linux, Cypress s'y exécute réellement — contrairement
à cet environnement Windows sandboxé, voir les commits précédents) ; 102
tests Mocha + 32 scénarios Cucumber toujours au vert en local.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
168 lines
6.7 KiB
TypeScript
168 lines
6.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { Given, Then, When } from "@cucumber/cucumber";
|
|
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";
|
|
|
|
/**
|
|
* Resolves a reference ingredient by its seeded French name — every
|
|
* scenario below names an ingredient by its `reference-seed-data.ts` name,
|
|
* never a raw id or its slug `key` directly, so this slugifies before
|
|
* matching.
|
|
*/
|
|
async function findIngredientId(name: string): Promise<number> {
|
|
const ingredient = await prisma.ingredient.findFirstOrThrow({
|
|
where: { key: getEnglishKey(name) },
|
|
});
|
|
return ingredient.id;
|
|
}
|
|
|
|
When("I request the recipe catalog", async function (this: CustomWorld) {
|
|
this.response = await this.agent.get("/recipes");
|
|
});
|
|
|
|
When("I request the recipe catalog tab {string}", async function (this: CustomWorld, tab: string) {
|
|
this.response = await this.agent.get("/recipes").query({ tab });
|
|
});
|
|
|
|
Then(
|
|
"the recipe catalog response should include {string}",
|
|
function (this: CustomWorld, name: string) {
|
|
const names = (this.response.body as Array<{ name: string }>).map((recipe) => recipe.name);
|
|
assert.ok(names.includes(name), `expected ${JSON.stringify(names)} to include "${name}"`);
|
|
},
|
|
);
|
|
|
|
When(
|
|
"I create a recipe named {string} with ingredient {string} and step {string}",
|
|
async function (this: CustomWorld, name: string, ingredientName: string, step: string) {
|
|
const ingredientId = await findIngredientId(ingredientName);
|
|
this.response = await this.agent.post("/recipes").send({
|
|
name,
|
|
dietIds: [],
|
|
ingredients: [{ ingredientId, quantity: 1, unit: "unité" }],
|
|
steps: [{ description: step }],
|
|
});
|
|
},
|
|
);
|
|
|
|
When(
|
|
"I create a recipe named {string} with unknown ingredient id {int} and step {string}",
|
|
async function (this: CustomWorld, name: string, unknownIngredientId: number, step: string) {
|
|
this.response = await this.agent.post("/recipes").send({
|
|
name,
|
|
dietIds: [],
|
|
ingredients: [{ ingredientId: unknownIngredientId, quantity: 1, unit: "unité" }],
|
|
steps: [{ description: step }],
|
|
});
|
|
},
|
|
);
|
|
|
|
Then(
|
|
"the created recipe should have ingredient {string} and step {string}",
|
|
function (this: CustomWorld, ingredientName: string, step: string) {
|
|
const body = this.response.body as {
|
|
ingredients: Array<{ ingredient: { key: string } }>;
|
|
steps: Array<{ description: string }>;
|
|
};
|
|
const expectedKey = getEnglishKey(ingredientName);
|
|
assert.ok(body.ingredients.some((line) => line.ingredient.key === expectedKey));
|
|
assert.ok(body.steps.some((s) => s.description === step));
|
|
},
|
|
);
|
|
|
|
// Created directly via Prisma (with a nested ingredient + step), not through
|
|
// the API — same rationale as `planning.steps.ts`'s equivalent "already
|
|
// exists" step: this is background state the scenario needs in place before
|
|
// its actual `When`, not the behavior under test. `authorId` is the
|
|
// currently-logged-in agent's own profile — `visibility` defaults to
|
|
// `PERSONAL` (schema.prisma), matching a recipe this agent just created for
|
|
// themselves.
|
|
Given(
|
|
"a recipe named {string} already exists with ingredient {string} and step {string}",
|
|
async function (this: CustomWorld, name: string, ingredientName: string, step: string) {
|
|
const ingredientId = await findIngredientId(ingredientName);
|
|
const me = await this.agent.get("/auth/me");
|
|
await prisma.recipe.create({
|
|
data: {
|
|
name,
|
|
authorId: me.body.id,
|
|
ingredients: { create: [{ ingredientId, quantity: 1, unit: "unité" }] },
|
|
steps: { create: [{ description: step, order: 0 }] },
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
// Same as above but `visibility: PUBLIC` — needed for scenarios where a
|
|
// *second* user must be able to see (though not necessarily edit) the
|
|
// recipe, e.g. the "only the author can edit" scenario: a `PERSONAL`
|
|
// recipe would 404 for anyone else before the authorship check even runs
|
|
// (see `recipe.service.ts`'s `canView`).
|
|
Given(
|
|
"a public recipe named {string} already exists with ingredient {string} and step {string}",
|
|
async function (this: CustomWorld, name: string, ingredientName: string, step: string) {
|
|
const ingredientId = await findIngredientId(ingredientName);
|
|
const me = await this.agent.get("/auth/me");
|
|
await prisma.recipe.create({
|
|
data: {
|
|
name,
|
|
authorId: me.body.id,
|
|
visibility: "PUBLIC",
|
|
ingredients: { create: [{ ingredientId, quantity: 1, unit: "unité" }] },
|
|
steps: { create: [{ description: step, order: 0 }] },
|
|
},
|
|
});
|
|
},
|
|
);
|
|
|
|
// Distinct from `planning.steps.ts`'s "my household has a planning covering
|
|
// today with recipe {string}..." — that step always creates a *new* recipe
|
|
// row with the given name, which wouldn't exercise the actual `RECIPE_IN_USE`
|
|
// check against a recipe this feature already created. This step instead
|
|
// looks up the already-existing recipe by name and points the planning item
|
|
// at its real id.
|
|
Given(
|
|
"my household has a planning that uses the recipe named {string}",
|
|
async function (this: CustomWorld, recipeName: string) {
|
|
const houseRes = await this.agent.post("/house").send({ name: "Foyer de test" });
|
|
const houseId: number = houseRes.body.id;
|
|
const recipe = await prisma.recipe.findFirstOrThrow({ where: { name: recipeName } });
|
|
|
|
const planning = await prisma.planning.create({
|
|
data: {
|
|
houseId,
|
|
startDate: TEST_REFERENCE_DATE.minus({ days: 2 }).toJSDate(),
|
|
finishDate: TEST_REFERENCE_DATE.plus({ days: 2 }).toJSDate(),
|
|
},
|
|
});
|
|
await prisma.planningItem.create({
|
|
data: { planningId: planning.id, weekDay: "lundi", meal: "diner", recipeId: recipe.id },
|
|
});
|
|
},
|
|
);
|
|
|
|
When("I delete the recipe named {string}", async function (this: CustomWorld, name: string) {
|
|
const recipe = await prisma.recipe.findFirstOrThrow({ where: { name } });
|
|
this.response = await this.agent.delete(`/recipes/${recipe.id}`);
|
|
});
|
|
|
|
When("I favorite the recipe named {string}", async function (this: CustomWorld, name: string) {
|
|
const recipe = await prisma.recipe.findFirstOrThrow({ where: { name } });
|
|
this.response = await this.agent.post(`/recipes/${recipe.id}/favorite`);
|
|
});
|
|
|
|
When(
|
|
"the second user tries to modify the recipe named {string}",
|
|
async function (this: CustomWorld, name: string) {
|
|
const ingredientId = await findIngredientId("Tomate");
|
|
const recipe = await prisma.recipe.findFirstOrThrow({ where: { name } });
|
|
this.secondResponse = await this.secondAgent.patch(`/recipes/${recipe.id}`).send({
|
|
name,
|
|
dietIds: [],
|
|
ingredients: [{ ingredientId, quantity: 1, unit: "unité" }],
|
|
steps: [{ description: "Hack" }],
|
|
});
|
|
},
|
|
);
|