Tests: couverture Cypress pour les paramètres, l'onboarding et la sidebar (step 8/8)
- household.cy.ts scindé en preferences.cy.ts (régime/allergies) et household-settings.cy.ts (nom, créer/rejoindre/membres/suppression/ quitter, avec et sans droits admin) - onboarding.cy.ts réordonné (régime → foyer → allergènes), avec les cas skip / créer / rejoindre à l'étape foyer - account.cy.ts: identité, suppression de compte (erreur de mot de passe, succès, annulation) - sidebar.cy.ts: menu Paramètres (3 liens) et menu compte (Mon compte, déconnexion), redirection /foyer → /parametres/foyer - auth.cy.ts/home-planning.cy.ts: adaptés au nouveau menu compte et au premier écran d'onboarding (régime, plus foyer)
This commit is contained in:
parent
60ca850042
commit
26453dd757
7 changed files with 394 additions and 104 deletions
68
apps/web/cypress/e2e/account.cy.ts
Normal file
68
apps/web/cypress/e2e/account.cy.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { ErrorCode } from "@batch-cooking/shared";
|
||||
|
||||
// Mocks the API via cy.intercept — see auth.cy.ts for the rationale.
|
||||
|
||||
const authenticatedProfile = {
|
||||
id: 1,
|
||||
firstName: "Alice",
|
||||
lastName: "Martin",
|
||||
email: "alice@example.com",
|
||||
tokenVersion: 0,
|
||||
houseId: null,
|
||||
dietId: null,
|
||||
};
|
||||
|
||||
describe("Account settings (/parametres/compte)", () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
|
||||
});
|
||||
|
||||
it("shows the signed-in profile's identity", () => {
|
||||
cy.visit("/parametres/compte");
|
||||
|
||||
cy.contains("Alice").should("be.visible");
|
||||
cy.contains("Martin").should("be.visible");
|
||||
cy.contains("alice@example.com").should("be.visible");
|
||||
});
|
||||
|
||||
it("shows an error and keeps the session when the password is wrong", () => {
|
||||
cy.intercept("DELETE", "**/auth/me", {
|
||||
statusCode: 401,
|
||||
body: { code: ErrorCode.INVALID_CREDENTIALS, message: "Invalid password" },
|
||||
}).as("deleteAccount");
|
||||
|
||||
cy.visit("/parametres/compte");
|
||||
cy.contains("button", "Supprimer mon compte").click();
|
||||
cy.get("#deleteAccountPassword").type("wrong-password");
|
||||
cy.contains("button", "Confirmer la suppression").click();
|
||||
|
||||
cy.wait("@deleteAccount");
|
||||
cy.contains("Email ou mot de passe incorrect").should("be.visible");
|
||||
cy.url().should("include", "/parametres/compte");
|
||||
});
|
||||
|
||||
it("deletes the account and returns to the login page", () => {
|
||||
cy.intercept("DELETE", "**/auth/me", { statusCode: 204 }).as("deleteAccount");
|
||||
|
||||
cy.visit("/parametres/compte");
|
||||
cy.contains("button", "Supprimer mon compte").click();
|
||||
cy.get("#deleteAccountPassword").type("correct-horse-battery-staple");
|
||||
cy.contains("button", "Confirmer la suppression").click();
|
||||
|
||||
cy.wait("@deleteAccount")
|
||||
.its("request.body")
|
||||
.should("deep.equal", { password: "correct-horse-battery-staple" });
|
||||
cy.url().should("include", "/login");
|
||||
});
|
||||
|
||||
it("cancels the deletion without calling the API", () => {
|
||||
cy.intercept("DELETE", "**/auth/me").as("deleteAccount");
|
||||
|
||||
cy.visit("/parametres/compte");
|
||||
cy.contains("button", "Supprimer mon compte").click();
|
||||
cy.contains("button", "Annuler").click();
|
||||
|
||||
cy.contains("button", "Confirmer la suppression").should("not.exist");
|
||||
cy.get("@deleteAccount.all").should("have.length", 0);
|
||||
});
|
||||
});
|
||||
|
|
@ -6,14 +6,11 @@ import { ErrorCode } from "@batch-cooking/shared";
|
|||
// suites against a real database.
|
||||
|
||||
describe("Signup", () => {
|
||||
it("creates a profile and starts the onboarding wizard (household/regime/allergens)", () => {
|
||||
it("creates a profile and starts the onboarding wizard (regime/household/allergens)", () => {
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 401 });
|
||||
// The onboarding wizard's first step (see onboarding.cy.ts for the full
|
||||
// walkthrough) reads the household right away to prefill its name field.
|
||||
cy.intercept("GET", "**/house/current", {
|
||||
statusCode: 200,
|
||||
body: { id: 1, name: "Foyer de Alice" },
|
||||
});
|
||||
// walkthrough) is the regime step, which fetches the reference list.
|
||||
cy.intercept("GET", "**/reference/diets", { statusCode: 200, body: [] });
|
||||
cy.intercept("POST", "**/auth/signup", {
|
||||
statusCode: 201,
|
||||
body: {
|
||||
|
|
@ -22,7 +19,7 @@ describe("Signup", () => {
|
|||
lastName: "Martin",
|
||||
email: "alice@example.com",
|
||||
tokenVersion: 0,
|
||||
houseId: 1,
|
||||
houseId: null,
|
||||
dietId: null,
|
||||
},
|
||||
}).as("signup");
|
||||
|
|
@ -38,8 +35,8 @@ describe("Signup", () => {
|
|||
// Not the home page directly — signup hands off to the onboarding
|
||||
// wizard first (RedirectIfAuthenticated no longer applies here, it's a
|
||||
// RequireAuth-gated route of its own, see App.tsx).
|
||||
cy.url().should("include", "/onboarding/foyer");
|
||||
cy.get("#houseName").should("have.value", "Foyer de Alice");
|
||||
cy.url().should("include", "/onboarding/regime");
|
||||
cy.contains("Étape 1 sur 3").should("be.visible");
|
||||
});
|
||||
|
||||
it("shows a client-side validation error without calling the API", () => {
|
||||
|
|
@ -157,6 +154,9 @@ describe("Already authenticated", () => {
|
|||
cy.intercept("POST", "**/auth/logout", { statusCode: 204 }).as("logout");
|
||||
|
||||
cy.visit("/");
|
||||
// "Se déconnecter" lives inside the account menu, opened by clicking
|
||||
// the greeting button — see AppLayout.tsx's AccountMenu.
|
||||
cy.contains("button", "Bonjour Alice").click();
|
||||
cy.contains("button", "Se déconnecter").click();
|
||||
|
||||
cy.wait("@logout");
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ describe("Sidebar navigation", () => {
|
|||
cy.visit("/");
|
||||
});
|
||||
|
||||
// The Foyer/Compte/Préférences links — behind the sidebar's "Paramètres"
|
||||
// toggle, not the main nav tested here — are covered by sidebar.cy.ts.
|
||||
it("highlights the current section and navigates between stub pages", () => {
|
||||
cy.contains("nav a", "Planning").should("have.class", "active");
|
||||
|
||||
|
|
@ -32,19 +34,15 @@ describe("Sidebar navigation", () => {
|
|||
cy.url().should("include", "/liste-de-courses");
|
||||
cy.contains("h1", "Liste de courses").should("be.visible");
|
||||
|
||||
cy.contains("nav a", "Foyer & profil").click();
|
||||
cy.url().should("include", "/foyer");
|
||||
cy.contains("h1", "Foyer & profil").should("be.visible");
|
||||
|
||||
cy.contains("nav a", "Planning").click();
|
||||
cy.url().should("eq", `${Cypress.config().baseUrl}/`);
|
||||
cy.contains("h1", "Planning de la semaine").should("be.visible");
|
||||
});
|
||||
|
||||
it("shows the signed-in user's name and lets them log out from the sidebar", () => {
|
||||
it("shows the signed-in user's name and lets them log out from the account menu", () => {
|
||||
cy.intercept("POST", "**/auth/logout", { statusCode: 204 }).as("logout");
|
||||
|
||||
cy.contains("Bonjour Alice").should("be.visible");
|
||||
cy.contains("button", "Bonjour Alice").should("be.visible").click();
|
||||
cy.contains("button", "Se déconnecter").click();
|
||||
|
||||
cy.wait("@logout");
|
||||
|
|
|
|||
185
apps/web/cypress/e2e/household-settings.cy.ts
Normal file
185
apps/web/cypress/e2e/household-settings.cy.ts
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// Mocks the API via cy.intercept — see auth.cy.ts for the rationale.
|
||||
|
||||
const adminProfile = {
|
||||
id: 1,
|
||||
firstName: "Alice",
|
||||
lastName: "Martin",
|
||||
email: "alice@example.com",
|
||||
tokenVersion: 0,
|
||||
houseId: null as number | null,
|
||||
dietId: null,
|
||||
};
|
||||
|
||||
const houseWithTwoMembers = {
|
||||
id: 1,
|
||||
name: "Chez Alice",
|
||||
adminId: 1,
|
||||
inviteCode: "ABCD2345",
|
||||
members: [
|
||||
{ id: 1, firstName: "Alice", lastName: "Martin" },
|
||||
{ id: 2, firstName: "Bob", lastName: "Dupont" },
|
||||
],
|
||||
};
|
||||
|
||||
describe("Household settings (/parametres/foyer) — no household yet", () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: adminProfile });
|
||||
cy.intercept("GET", "**/house/current", { statusCode: 200, body: null });
|
||||
});
|
||||
|
||||
it("offers to create or join a household", () => {
|
||||
cy.visit("/parametres/foyer");
|
||||
|
||||
cy.contains("Créer un foyer").should("be.visible");
|
||||
cy.contains("Rejoindre un foyer").should("be.visible");
|
||||
});
|
||||
|
||||
it("creates a household", () => {
|
||||
const createdHouse = {
|
||||
id: 1,
|
||||
name: "Chez Alice",
|
||||
adminId: 1,
|
||||
inviteCode: "ABCD2345",
|
||||
members: [{ id: 1, firstName: "Alice", lastName: "Martin" }],
|
||||
};
|
||||
// The page reloads `GET /house/current` right after creating succeeds —
|
||||
// see the "deletes the household" test above for the same pattern.
|
||||
let created = false;
|
||||
cy.intercept("GET", "**/house/current", (req) => {
|
||||
req.reply({ statusCode: 200, body: created ? createdHouse : null });
|
||||
});
|
||||
cy.intercept("POST", "**/house", (req) => {
|
||||
created = true;
|
||||
req.reply({ statusCode: 201, body: createdHouse });
|
||||
}).as("createHouse");
|
||||
|
||||
cy.visit("/parametres/foyer");
|
||||
cy.get("#houseName").type("Chez Alice");
|
||||
cy.contains("button", "Créer").click();
|
||||
|
||||
cy.wait("@createHouse").its("request.body").should("deep.equal", { name: "Chez Alice" });
|
||||
cy.contains("ABCD2345").should("be.visible");
|
||||
});
|
||||
|
||||
it("joins a household by invite code", () => {
|
||||
let joined = false;
|
||||
cy.intercept("GET", "**/house/current", (req) => {
|
||||
req.reply({ statusCode: 200, body: joined ? houseWithTwoMembers : null });
|
||||
});
|
||||
cy.intercept("POST", "**/house/join", (req) => {
|
||||
joined = true;
|
||||
req.reply({ statusCode: 200, body: houseWithTwoMembers });
|
||||
}).as("joinHouse");
|
||||
|
||||
cy.visit("/parametres/foyer");
|
||||
cy.get("#inviteCode").type("abcd2345");
|
||||
cy.contains("button", "Rejoindre").click();
|
||||
|
||||
cy.wait("@joinHouse").its("request.body").should("deep.equal", { inviteCode: "ABCD2345" });
|
||||
cy.contains("Bob Dupont").should("be.visible");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Household settings (/parametres/foyer) — as the admin", () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: { ...adminProfile, houseId: 1 } });
|
||||
cy.intercept("GET", "**/house/current", { statusCode: 200, body: houseWithTwoMembers });
|
||||
});
|
||||
|
||||
it("shows the household's name, invite code, and members with an admin badge", () => {
|
||||
cy.visit("/parametres/foyer");
|
||||
|
||||
cy.get("#houseName").should("have.value", "Chez Alice");
|
||||
cy.contains("ABCD2345").should("be.visible");
|
||||
cy.contains("Bob Dupont").should("be.visible");
|
||||
cy.contains("Alice Martin").parent().contains("Admin");
|
||||
});
|
||||
|
||||
it("autosaves the household name", () => {
|
||||
cy.intercept("PATCH", "**/house/current", {
|
||||
statusCode: 200,
|
||||
body: { ...houseWithTwoMembers, name: "Chez les Martin" },
|
||||
}).as("renameHouse");
|
||||
|
||||
cy.visit("/parametres/foyer");
|
||||
cy.get("#houseName").clear();
|
||||
cy.get("#houseName").type("Chez les Martin");
|
||||
|
||||
cy.wait("@renameHouse").its("request.body").should("deep.equal", { name: "Chez les Martin" });
|
||||
cy.contains("Enregistré ✓").should("be.visible");
|
||||
});
|
||||
|
||||
it("removes a member", () => {
|
||||
// Same reasoning as the "deletes the household" test below — the page
|
||||
// reloads `GET /house/current` right after the removal succeeds.
|
||||
let memberRemoved = false;
|
||||
cy.intercept("GET", "**/house/current", (req) => {
|
||||
const body = memberRemoved
|
||||
? { ...houseWithTwoMembers, members: [houseWithTwoMembers.members[0]] }
|
||||
: houseWithTwoMembers;
|
||||
req.reply({ statusCode: 200, body });
|
||||
});
|
||||
cy.intercept("DELETE", "**/house/members/2", (req) => {
|
||||
memberRemoved = true;
|
||||
req.reply({
|
||||
statusCode: 200,
|
||||
body: { ...houseWithTwoMembers, members: [houseWithTwoMembers.members[0]] },
|
||||
});
|
||||
}).as("removeMember");
|
||||
|
||||
cy.visit("/parametres/foyer");
|
||||
cy.contains("li", "Bob Dupont").contains("button", "Retirer").click();
|
||||
|
||||
cy.wait("@removeMember");
|
||||
cy.contains("Bob Dupont").should("not.exist");
|
||||
});
|
||||
|
||||
it("deletes the household after confirming", () => {
|
||||
// The page reloads `GET /house/current` right after the delete
|
||||
// succeeds — this intercept needs to answer differently before/after
|
||||
// that DELETE, hence the shared mutable flag rather than two static
|
||||
// `cy.intercept` calls (the later one would just win for every request,
|
||||
// including the initial page load).
|
||||
let houseDeleted = false;
|
||||
cy.intercept("GET", "**/house/current", (req) => {
|
||||
req.reply({ statusCode: 200, body: houseDeleted ? null : houseWithTwoMembers });
|
||||
});
|
||||
cy.intercept("DELETE", "**/house/current", (req) => {
|
||||
houseDeleted = true;
|
||||
req.reply({ statusCode: 204 });
|
||||
}).as("deleteHouse");
|
||||
|
||||
cy.visit("/parametres/foyer");
|
||||
cy.contains("button", "Supprimer le foyer").click();
|
||||
cy.contains("button", "Confirmer la suppression").click();
|
||||
|
||||
cy.wait("@deleteHouse");
|
||||
cy.contains("Créer un foyer").should("be.visible");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Household settings (/parametres/foyer) — as a non-admin member", () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept("GET", "**/auth/me", {
|
||||
statusCode: 200,
|
||||
body: { ...adminProfile, id: 2, firstName: "Bob", lastName: "Dupont", houseId: 1 },
|
||||
});
|
||||
cy.intercept("GET", "**/house/current", { statusCode: 200, body: houseWithTwoMembers });
|
||||
});
|
||||
|
||||
it("offers to leave the household instead of deleting it", () => {
|
||||
cy.visit("/parametres/foyer");
|
||||
|
||||
cy.contains("button", "Quitter le foyer").should("be.visible");
|
||||
cy.contains("button", "Supprimer le foyer").should("not.exist");
|
||||
});
|
||||
|
||||
it("leaves the household", () => {
|
||||
cy.intercept("POST", "**/house/leave", { statusCode: 204 }).as("leaveHouse");
|
||||
|
||||
cy.visit("/parametres/foyer");
|
||||
cy.contains("button", "Quitter le foyer").click();
|
||||
|
||||
cy.wait("@leaveHouse");
|
||||
});
|
||||
});
|
||||
|
|
@ -8,22 +8,27 @@ const signupResponse = {
|
|||
lastName: "Martin",
|
||||
email: "alice@example.com",
|
||||
tokenVersion: 0,
|
||||
houseId: 1,
|
||||
houseId: null as number | null,
|
||||
dietId: null,
|
||||
};
|
||||
|
||||
describe("Onboarding wizard (household → regime → allergens)", () => {
|
||||
it("walks through all three steps after signup and lands on the home", () => {
|
||||
/** Signs up and lands on the wizard's first step (regime) — shared setup for every scenario below. */
|
||||
function signupAndReachOnboarding() {
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 401 });
|
||||
cy.intercept("POST", "**/auth/signup", { statusCode: 201, body: signupResponse }).as("signup");
|
||||
cy.intercept("GET", "**/house/current", {
|
||||
statusCode: 200,
|
||||
body: { id: 1, name: "Foyer de Alice" },
|
||||
});
|
||||
cy.intercept("PATCH", "**/house/current", {
|
||||
statusCode: 200,
|
||||
body: { id: 1, name: "Chez Alice" },
|
||||
}).as("renameHouse");
|
||||
cy.intercept("GET", "**/planning/current", { statusCode: 200, body: null });
|
||||
|
||||
cy.visit("/signup");
|
||||
cy.get("#firstName").type("Alice");
|
||||
cy.get("#lastName").type("Martin");
|
||||
cy.get("#email").type("alice@example.com");
|
||||
cy.get("#password").type("correct-horse-battery-staple");
|
||||
cy.contains("button", "Créer mon profil").click();
|
||||
cy.wait("@signup");
|
||||
}
|
||||
|
||||
describe("Onboarding wizard (regime → foyer → allergens)", () => {
|
||||
it("walks through all three steps, creating a household on the way, and lands on the home", () => {
|
||||
cy.intercept("GET", "**/reference/diets", {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
|
|
@ -35,6 +40,11 @@ describe("Onboarding wizard (household → regime → allergens)", () => {
|
|||
statusCode: 200,
|
||||
body: { ...signupResponse, dietId: 2 },
|
||||
}).as("updateDiet");
|
||||
cy.intercept("GET", "**/house/current", { statusCode: 200, body: null });
|
||||
cy.intercept("POST", "**/house", {
|
||||
statusCode: 201,
|
||||
body: { id: 1, name: "Chez Alice", adminId: 1, inviteCode: "ABCD2345", members: [] },
|
||||
}).as("createHouse");
|
||||
cy.intercept("GET", "**/reference/allergies", {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
|
|
@ -45,32 +55,23 @@ describe("Onboarding wizard (household → regime → allergens)", () => {
|
|||
cy.intercept("PATCH", "**/profile/allergies", { statusCode: 200, body: [1] }).as(
|
||||
"updateAllergies",
|
||||
);
|
||||
cy.intercept("GET", "**/planning/current", { statusCode: 200, body: null });
|
||||
|
||||
cy.visit("/signup");
|
||||
cy.get("#firstName").type("Alice");
|
||||
cy.get("#lastName").type("Martin");
|
||||
cy.get("#email").type("alice@example.com");
|
||||
cy.get("#password").type("correct-horse-battery-staple");
|
||||
cy.contains("button", "Créer mon profil").click();
|
||||
cy.wait("@signup");
|
||||
signupAndReachOnboarding();
|
||||
|
||||
// Step 1/3 — household name, prefilled with the auto-generated default.
|
||||
cy.url().should("include", "/onboarding/foyer");
|
||||
cy.contains("Étape 1 sur 3").should("be.visible");
|
||||
cy.get("#houseName").should("have.value", "Foyer de Alice");
|
||||
cy.get("#houseName").clear();
|
||||
cy.get("#houseName").type("Chez Alice");
|
||||
cy.contains("button", "Continuer").click();
|
||||
cy.wait("@renameHouse").its("request.body").should("deep.equal", { name: "Chez Alice" });
|
||||
|
||||
// Step 2/3 — dietary regime.
|
||||
// Step 1/3 — dietary regime.
|
||||
cy.url().should("include", "/onboarding/regime");
|
||||
cy.contains("Étape 2 sur 3").should("be.visible");
|
||||
cy.contains("Étape 1 sur 3").should("be.visible");
|
||||
cy.get("#diet").select("Végétarien");
|
||||
cy.contains("button", "Continuer").click();
|
||||
cy.wait("@updateDiet").its("request.body").should("deep.equal", { dietId: 2 });
|
||||
|
||||
// Step 2/3 — household, optional: creating one here.
|
||||
cy.url().should("include", "/onboarding/foyer");
|
||||
cy.contains("Étape 2 sur 3").should("be.visible");
|
||||
cy.get("#houseName").type("Chez Alice");
|
||||
cy.contains("button", "Créer").click();
|
||||
cy.wait("@createHouse").its("request.body").should("deep.equal", { name: "Chez Alice" });
|
||||
|
||||
// Step 3/3 — allergens (grouped into two lists) and intolerances, then finish.
|
||||
cy.url().should("include", "/onboarding/allergenes");
|
||||
cy.contains("Étape 3 sur 3").should("be.visible");
|
||||
|
|
@ -86,46 +87,62 @@ describe("Onboarding wizard (household → regime → allergens)", () => {
|
|||
cy.contains("h1", "Planning de la semaine").should("be.visible");
|
||||
});
|
||||
|
||||
it("lets every step be skipped without changing anything", () => {
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 401 });
|
||||
cy.intercept("POST", "**/auth/signup", { statusCode: 201, body: signupResponse });
|
||||
cy.intercept("GET", "**/house/current", {
|
||||
statusCode: 200,
|
||||
body: { id: 1, name: "Foyer de Alice" },
|
||||
});
|
||||
cy.intercept("PATCH", "**/house/current", {
|
||||
statusCode: 200,
|
||||
body: { id: 1, name: "Foyer de Alice" },
|
||||
}).as("renameHouse");
|
||||
it("lets the regime and allergens steps be skipped without changing anything", () => {
|
||||
cy.intercept("GET", "**/reference/diets", { statusCode: 200, body: [] });
|
||||
cy.intercept("PATCH", "**/profile/diet", { statusCode: 200, body: signupResponse }).as(
|
||||
"updateDiet",
|
||||
);
|
||||
cy.intercept("GET", "**/house/current", { statusCode: 200, body: null });
|
||||
cy.intercept("GET", "**/reference/allergies", { statusCode: 200, body: [] });
|
||||
cy.intercept("PATCH", "**/profile/allergies", { statusCode: 200, body: [] }).as(
|
||||
"updateAllergies",
|
||||
);
|
||||
cy.intercept("GET", "**/planning/current", { statusCode: 200, body: null });
|
||||
|
||||
cy.visit("/signup");
|
||||
cy.get("#firstName").type("Alice");
|
||||
cy.get("#lastName").type("Martin");
|
||||
cy.get("#email").type("alice@example.com");
|
||||
cy.get("#password").type("correct-horse-battery-staple");
|
||||
cy.contains("button", "Créer mon profil").click();
|
||||
|
||||
cy.url().should("include", "/onboarding/foyer");
|
||||
cy.contains("button", "Continuer").click();
|
||||
cy.wait("@renameHouse").its("request.body").should("deep.equal", { name: "Foyer de Alice" });
|
||||
signupAndReachOnboarding();
|
||||
|
||||
cy.url().should("include", "/onboarding/regime");
|
||||
cy.contains("button", "Continuer").click();
|
||||
cy.wait("@updateDiet").its("request.body").should("deep.equal", { dietId: null });
|
||||
|
||||
cy.url().should("include", "/onboarding/foyer");
|
||||
cy.contains("button", "Passer cette étape").click();
|
||||
|
||||
cy.url().should("include", "/onboarding/allergenes");
|
||||
cy.contains("button", "Terminer").click();
|
||||
cy.wait("@updateAllergies").its("request.body").should("deep.equal", { allergyIds: [] });
|
||||
|
||||
cy.url().should("eq", `${Cypress.config().baseUrl}/`);
|
||||
});
|
||||
|
||||
it("lets the household step be completed by joining an existing household instead of creating one", () => {
|
||||
cy.intercept("GET", "**/reference/diets", { statusCode: 200, body: [] });
|
||||
cy.intercept("PATCH", "**/profile/diet", { statusCode: 200, body: signupResponse });
|
||||
cy.intercept("GET", "**/house/current", { statusCode: 200, body: null });
|
||||
cy.intercept("POST", "**/house/join", {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
id: 1,
|
||||
name: "Chez Bob",
|
||||
adminId: 2,
|
||||
inviteCode: "ABCD2345",
|
||||
members: [
|
||||
{ id: 1, firstName: "Alice", lastName: "Martin" },
|
||||
{ id: 2, firstName: "Bob", lastName: "Dupont" },
|
||||
],
|
||||
},
|
||||
}).as("joinHouse");
|
||||
cy.intercept("GET", "**/reference/allergies", { statusCode: 200, body: [] });
|
||||
cy.intercept("PATCH", "**/profile/allergies", { statusCode: 200, body: [] });
|
||||
|
||||
signupAndReachOnboarding();
|
||||
|
||||
cy.contains("button", "Continuer").click();
|
||||
cy.url().should("include", "/onboarding/foyer");
|
||||
|
||||
cy.get("#inviteCode").type("abcd2345");
|
||||
cy.contains("button", "Rejoindre").click();
|
||||
|
||||
cy.wait("@joinHouse").its("request.body").should("deep.equal", { inviteCode: "ABCD2345" });
|
||||
cy.url().should("include", "/onboarding/allergenes");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,13 +10,9 @@ const authenticatedProfile = {
|
|||
dietId: 2,
|
||||
};
|
||||
|
||||
describe("Household & profile settings (/foyer) — hot saving", () => {
|
||||
describe("Dietary preferences (/parametres/preferences) — hot saving", () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
|
||||
cy.intercept("GET", "**/house/current", {
|
||||
statusCode: 200,
|
||||
body: { id: 1, name: "Chez Alice" },
|
||||
});
|
||||
cy.intercept("GET", "**/reference/diets", {
|
||||
statusCode: 200,
|
||||
body: [
|
||||
|
|
@ -34,10 +30,9 @@ describe("Household & profile settings (/foyer) — hot saving", () => {
|
|||
cy.intercept("GET", "**/profile/allergies", { statusCode: 200, body: [2] });
|
||||
});
|
||||
|
||||
it("loads the current household name, regime, and shows allergies/intolerances as two groups", () => {
|
||||
cy.visit("/foyer");
|
||||
it("loads the current regime, and shows allergies/intolerances as two groups", () => {
|
||||
cy.visit("/parametres/preferences");
|
||||
|
||||
cy.get("#houseName").should("have.value", "Chez Alice");
|
||||
cy.get("#diet").should("have.value", "2");
|
||||
cy.contains("legend", "Allergies").should("be.visible");
|
||||
cy.contains("legend", "Intolérances").should("be.visible");
|
||||
|
|
@ -46,41 +41,17 @@ describe("Household & profile settings (/foyer) — hot saving", () => {
|
|||
});
|
||||
|
||||
it("has no explicit save button anywhere on the page", () => {
|
||||
cy.visit("/foyer");
|
||||
cy.visit("/parametres/preferences");
|
||||
cy.contains("button", "Enregistrer").should("not.exist");
|
||||
});
|
||||
|
||||
it("autosaves the household name a short pause after typing, no button click", () => {
|
||||
cy.intercept("PATCH", "**/house/current", {
|
||||
statusCode: 200,
|
||||
body: { id: 1, name: "Chez les Martin" },
|
||||
}).as("renameHouse");
|
||||
|
||||
cy.visit("/foyer");
|
||||
cy.get("#houseName").clear();
|
||||
cy.get("#houseName").type("Chez les Martin");
|
||||
|
||||
cy.wait("@renameHouse").its("request.body").should("deep.equal", { name: "Chez les Martin" });
|
||||
cy.contains("Enregistré ✓").should("be.visible");
|
||||
});
|
||||
|
||||
it("does not autosave an empty household name — shows a validation message instead", () => {
|
||||
cy.intercept("PATCH", "**/house/current").as("renameHouse");
|
||||
|
||||
cy.visit("/foyer");
|
||||
cy.get("#houseName").clear();
|
||||
|
||||
cy.contains("Le nom du foyer est requis").should("be.visible");
|
||||
cy.get("@renameHouse.all").should("have.length", 0);
|
||||
});
|
||||
|
||||
it("autosaves the regime as soon as it's selected", () => {
|
||||
cy.intercept("PATCH", "**/profile/diet", {
|
||||
statusCode: 200,
|
||||
body: { ...authenticatedProfile, dietId: 1 },
|
||||
}).as("updateDiet");
|
||||
|
||||
cy.visit("/foyer");
|
||||
cy.visit("/parametres/preferences");
|
||||
cy.get("#diet").select("Omnivore");
|
||||
|
||||
cy.wait("@updateDiet").its("request.body").should("deep.equal", { dietId: 1 });
|
||||
|
|
@ -91,11 +62,12 @@ describe("Household & profile settings (/foyer) — hot saving", () => {
|
|||
"updateAllergies",
|
||||
);
|
||||
|
||||
cy.visit("/foyer");
|
||||
cy.visit("/parametres/preferences");
|
||||
cy.contains("label", "Arachides").find("input[type=checkbox]").check();
|
||||
|
||||
cy.wait("@updateAllergies")
|
||||
.its("request.body")
|
||||
.should("deep.equal", { allergyIds: [2, 1] });
|
||||
cy.contains("Enregistré ✓").should("be.visible");
|
||||
});
|
||||
});
|
||||
50
apps/web/cypress/e2e/sidebar.cy.ts
Normal file
50
apps/web/cypress/e2e/sidebar.cy.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Mocks the API via cy.intercept — see auth.cy.ts for the rationale.
|
||||
|
||||
const authenticatedProfile = {
|
||||
id: 1,
|
||||
firstName: "Alice",
|
||||
lastName: "Martin",
|
||||
email: "alice@example.com",
|
||||
tokenVersion: 0,
|
||||
houseId: null,
|
||||
dietId: null,
|
||||
};
|
||||
|
||||
describe("Sidebar — settings menu and account menu", () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept("GET", "**/auth/me", { statusCode: 200, body: authenticatedProfile });
|
||||
cy.intercept("GET", "**/planning/current", { statusCode: 200, body: null });
|
||||
});
|
||||
|
||||
it("no longer lists Foyer in the main nav", () => {
|
||||
cy.visit("/");
|
||||
cy.get(".app-sidebar__nav a").should("not.contain", "Foyer");
|
||||
});
|
||||
|
||||
it("reveals the three settings pages behind the Paramètres toggle", () => {
|
||||
cy.visit("/");
|
||||
|
||||
cy.contains("a", "Compte").should("not.exist");
|
||||
cy.contains("button", "Paramètres").click();
|
||||
|
||||
cy.contains("a", "Compte").should("have.attr", "href", "/parametres/compte");
|
||||
cy.contains("a", "Préférences").should("have.attr", "href", "/parametres/preferences");
|
||||
cy.contains("a", "Foyer").should("have.attr", "href", "/parametres/foyer");
|
||||
});
|
||||
|
||||
it("opens the account menu from the greeting and links to Mon compte", () => {
|
||||
cy.visit("/");
|
||||
|
||||
cy.contains("a", "Mon compte").should("not.exist");
|
||||
cy.contains("button", "Bonjour Alice").click();
|
||||
cy.contains("a", "Mon compte").click();
|
||||
|
||||
cy.url().should("include", "/parametres/compte");
|
||||
});
|
||||
|
||||
it("redirects the old /foyer path to /parametres/foyer", () => {
|
||||
cy.intercept("GET", "**/house/current", { statusCode: 200, body: null });
|
||||
cy.visit("/foyer");
|
||||
cy.url().should("include", "/parametres/foyer");
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue