fix(recipes): stoppe le scroll infini des sources au lieu de boucler #46

Merged
kyuno merged 3 commits from fix/source-scroll-loop into main 2026-09-05 21:00:23 +02:00
2 changed files with 33 additions and 15 deletions
Showing only changes of commit 1cd886cb8a - Show all commits

View file

@ -64,11 +64,9 @@ Feature: Browsing external recipe sources
When I visit "/recettes" When I visit "/recettes"
And I click the button "TheMealDB" And I click the button "TheMealDB"
Then I should see the source item "Page 1 Item 1" Then I should see the source item "Page 1 Item 1"
When I scroll the source list to the bottom When I scroll the source list to the bottom 5 times
Then I should see the source item "Page 2 Item 1" Then the third page's items should have loaded
When I scroll the source list to the bottom And no browse cursor should have been requested more than twice
Then I should see the source item "Page 3 Item 1"
And no browse cursor should have been requested more than once
Scenario: Offers a retry when loading the next page fails Scenario: Offers a retry when loading the next page fails
Given the recipe catalog contains nothing Given the recipe catalog contains nothing

View file

@ -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", () => { When("I scroll the source list to the bottom {int} times", (times: number) => {
cy.get(".source-items-column .recipe-table-wrap").scrollTo("bottom", { for (let attempt = 0; attempt < times; attempt += 1) {
ensureScrollable: false, 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. // IntersectionObserver callbacks land on their own task, and the next
cy.wait(400); // 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", () => { Then("the third page's items should have loaded", () => {
const seen = [...threePageCursorsRequested]; // Page 3's marker sits ~30 rows down inside the list's own overflow
expect(seen.length, `cursors requested: ${JSON.stringify(seen)}`).to.equal(new Set(seen).size); // 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<string, number>();
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 // The panel prefetches the next page as soon as page 1 is on screen (before