From a07a4d18a4bb7c4f96f6e7dee00566a06eacf1df Mon Sep 17 00:00:00 2001 From: Daniel Zyto Date: Fri, 21 Aug 2026 12:13:21 +0200 Subject: [PATCH] test(runner): pin the owner filter's case-folding at both seams (DEV-2519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DEV-2519 suites were green but two deliberate behaviors had no test that fails when they go away (docs/TESTING.md bar), both on the two-writer created_by casing split (DEV-2501): - ownerOptions folding two casings into one option: every unit fixture was lowercase, so keying options on the raw local part — same person twice, counts split — passed all 9 tests. Verified by that exact mutation: only the new test catches it. - the URL seed's toLowerCase in MyDemos (a pasted ?owner=SOMEONE.ELSE link): removing it kept every test green while the grid filtered under an "Everyone" label. The new e2e asserts the cards AND the picker label; against that mutation it fails on the label, for the right reason. Test-only change; both files run in the existing CI globs. --- runner/e2e/all-demos.spec.ts | 16 ++++++++++++++++ runner/pipeline/demo-owners.test.mjs | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/runner/e2e/all-demos.spec.ts b/runner/e2e/all-demos.spec.ts index d53b31ff5..9eee91874 100644 --- a/runner/e2e/all-demos.spec.ts +++ b/runner/e2e/all-demos.spec.ts @@ -195,6 +195,22 @@ test("the owner filter shows one person's demos, and the view is a link", async await expect(page).not.toHaveURL(/owner=/); }); +test("a pasted filter link filters regardless of its case", async ({ page }) => { + // The URL is the shareable artefact, and pasted links arrive hand-edited or + // autocapitalised. The slug has to match case-insensitively end to end: the + // grid filters AND the picker names the person — a filtered grid under an + // "Everyone" label would read as the whole team having two demos. + await stubShell(page); + await signIn(page); + await stubDemos(page); + await page.goto("/all-demos?owner=SOMEONE.ELSE"); + + await expect(card(page, "Their grid")).toBeVisible(); + await expect(card(page, "Their second grid")).toBeVisible(); + await expect(card(page, "My grid")).toHaveCount(0); + await expect(page.getByRole("button", { name: "Filter demos by owner" })).toContainText("Someone Else (2)"); +}); + test("a filter that matches nobody says whose it was", async ({ page }) => { await stubShell(page); await signIn(page); diff --git a/runner/pipeline/demo-owners.test.mjs b/runner/pipeline/demo-owners.test.mjs index 195ce54c2..4ff0184ce 100644 --- a/runner/pipeline/demo-owners.test.mjs +++ b/runner/pipeline/demo-owners.test.mjs @@ -72,6 +72,18 @@ test("a row with no owner is counted in the total but gets no option", () => { assert.deepEqual(options.slice(1).map((o) => o.value), ["dev"]); }); +test("two casings of one address are one owner, not two", () => { + // `created_by` has two writers (DEV-2501): the browser stores the broker's + // casing, the MCP path a normalised one. Options keyed on the raw value would + // list the same person twice, each with half their demos. + const options = ownerOptions( + [demo("Dev@Handsontable.com", "a"), demo("dev@handsontable.com", "b")], + displayNameFromEmail, + ); + assert.deepEqual(options.map((o) => o.label), ["Everyone (2)", "Dev (2)"]); + assert.equal(options[1].value, "dev"); +}); + test("filtering matches on the slug, case-insensitively", () => { assert.deepEqual(filterByOwner(LIST, "marek.martuszewski").map((d) => d.id), ["a", "c", "e"]); assert.deepEqual(filterByOwner(LIST, "MAREK.MARTUSZEWSKI").map((d) => d.id), ["a", "c", "e"]);