From 1cd886cb8acff74521e01b2355e360d9353e437a Mon Sep 17 00:00:00 2001 From: Nicolas Date: Sat, 5 Sep 2026 20:45:58 +0200 Subject: [PATCH] =?UTF-8?q?test(recipes):=20fiabilise=20le=20sc=C3=A9nario?= =?UTF-8?q?=203=20pages=20(scroll=20r=C3=A9el=20+=20log=20de=20curseurs)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le scénario échouait en CI sur deux points de fragilité, pas sur le fond : 1. `IntersectionObserver` ne se redéclenche qu'à une *transition* d'intersection — avec des pages courtes la sentinelle restait visible en continu et seule la 2e page se chargeait. Les pages font maintenant 15 éléments (la liste déborde son conteneur) et chaque page suivante est tirée par un `scrollTo("bottom")` explicite, répété 5 fois. 2. Une fois la liste débordante, `should("be.visible")` sur un élément sorti du conteneur `overflow:auto` échoue (même classe de bug que #1) — l'assertion "3e page chargée" fait maintenant `.scrollIntoView()` avant. L'assertion anti-boucle porte sur le journal des curseurs demandés (tolérance 2× pour le double-effet React StrictMode du serveur e2e), pas sur un `Set.size` strict. Vérifié localement : 7/7 avec le correctif, "Page 3 Item 1" jamais chargée sans lui. Co-Authored-By: Claude Sonnet 5 --- apps/web/cypress/e2e/recipe-sources.feature | 8 ++--- apps/web/cypress/e2e/recipe-sources.ts | 40 +++++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/apps/web/cypress/e2e/recipe-sources.feature b/apps/web/cypress/e2e/recipe-sources.feature index 0225ac3..fc03862 100644 --- a/apps/web/cypress/e2e/recipe-sources.feature +++ b/apps/web/cypress/e2e/recipe-sources.feature @@ -64,11 +64,9 @@ Feature: Browsing external recipe sources When I visit "/recettes" And I click the button "TheMealDB" Then I should see the source item "Page 1 Item 1" - When I scroll the source list to the bottom - Then I should see the source item "Page 2 Item 1" - When I scroll the source list to the bottom - Then I should see the source item "Page 3 Item 1" - And no browse cursor should have been requested more than once + When I scroll the source list to the bottom 5 times + Then the third page's items should have loaded + And no browse cursor should have been requested more than twice Scenario: Offers a retry when loading the next page fails Given the recipe catalog contains nothing diff --git a/apps/web/cypress/e2e/recipe-sources.ts b/apps/web/cypress/e2e/recipe-sources.ts index fea1b6d..54af571 100644 --- a/apps/web/cypress/e2e/recipe-sources.ts +++ b/apps/web/cypress/e2e/recipe-sources.ts @@ -174,18 +174,38 @@ Given("browsing TheMealDB returns three pages, each addressed by its own cursor" }); }); -When("I scroll the source list to the bottom", () => { - cy.get(".source-items-column .recipe-table-wrap").scrollTo("bottom", { - ensureScrollable: false, - }); - // IntersectionObserver callbacks are delivered on their own task, and the - // next page's fetch + merge then needs a render — give both a beat. - cy.wait(400); +When("I scroll the source list to the bottom {int} times", (times: number) => { + for (let attempt = 0; attempt < times; attempt += 1) { + cy.get(".source-items-column .recipe-table-wrap").scrollTo("bottom", { + ensureScrollable: false, + }); + // IntersectionObserver callbacks land on their own task, and the next + // page's fetch + merge then needs a render — give both a beat before + // the next scroll (which needs fresh content below it to have anything + // new to reveal). + cy.wait(350); + } }); -Then("no browse cursor should have been requested more than once", () => { - const seen = [...threePageCursorsRequested]; - expect(seen.length, `cursors requested: ${JSON.stringify(seen)}`).to.equal(new Set(seen).size); +Then("the third page's items should have loaded", () => { + // Page 3's marker sits ~30 rows down inside the list's own overflow + // container — scroll it in before asserting, like `common.steps.ts`'s + // ingredient-row check does. With the stale-closure loop it never loads + // at all: `handleLoadMore` keeps re-requesting cursor-2. + cy.contains(".recipe-table__name", "Page 3 Item 1").scrollIntoView().should("be.visible"); +}); + +Then("no browse cursor should have been requested more than twice", () => { + const counts = new Map(); + for (const cursor of threePageCursorsRequested) { + counts.set(cursor, (counts.get(cursor) ?? 0) + 1); + } + // React StrictMode double-invokes effects in the e2e dev server (see + // recipes.cy.ts), so the panel's initial fetch + one-page-ahead prefetch + // can each fire twice — hence "twice", not "once". The issue #38 loop + // blows well past that: every scroll re-requests the same stale cursor. + const offenders = [...counts.entries()].filter(([, n]) => n > 2); + expect(offenders, `cursor request counts: ${JSON.stringify([...counts])}`).to.have.length(0); }); // The panel prefetches the next page as soon as page 1 is on screen (before