Files
redefined-designs/frontend/tests/e2e/support/findOrFail.ts
bermudalambandClaude Opus 5 42412e8eeb
Linting / lint (pull_request) Successful in 2m10s
SonarQube Analysis / sonarqube (pull_request) Successful in 28m5s
fix(e2e): restore the frontend build by typing the responses findOrFail searches (#254)
main did not build, so every Portainer deploy failed with `npm run build` exit code 2. My regression from #241.

findOrFail<T>(items: T[], predicate: (item: T) => boolean) infers T from both parameters. The call sites annotate the predicate as documentation, and the arrays come from .json(), which is any and offers no competing candidate — so T became the one-field shape written in the lambda and every caller failed on the field it actually wanted. Array.prototype.find has no such problem, which is why the code this replaced type-checked.

The four responses are now typed at their call sites, so T is inferred from real data and the predicates need no annotation. findOrFail additionally takes NoInfer<T> on its predicate, so a stray annotation can never drive the element type again. The specs are better typed than before this change: `.json()` was plain any, and the annotations only ever documented a shape nothing enforced.

I did not catch this because I verified with a bare `npx tsc --noEmit`, and tsconfig.json is `"include": ["src"]` — it structurally cannot see tests/. The specs are checked by the second command in `npm run build`, which is the step Docker runs and the step that failed. Verified this time with `npm run build` itself, plus lint, the unit suite, and two consecutive full e2e passes.

Closes #254

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 09:09:01 -05:00

46 lines
2.1 KiB
TypeScript

/**
* Picks one row out of a collection, failing loudly when it is not there.
*
* The pattern this replaces is `collection.find(...)` dereferenced straight
* away — `customers.find(c => c.email === x).id`. When the row is missing the
* test dies with "Cannot read properties of undefined" pointing at a line of
* test plumbing, which says nothing about what was expected. The suite runs
* fullyParallel against one shared database, so a lookup over a collection
* other specs also write to can miss for reasons that have nothing to do with
* the behaviour under test (#241).
*
* Deliberately imports nothing. It lives apart from `api.ts` so the Vitest
* unit suite can cover it without pulling `@playwright/test` — and therefore a
* browser harness — into a run configured for `environment: 'node'`.
*
* Throws rather than returning null: every caller wants the row, and an
* Error at the point of the miss beats a null threaded through three more
* lines before something else fails.
*/
export function findOrFail<T>(
items: T[],
// NoInfer so the element type comes from `items` alone. Callers annotate the
// predicate parameter as documentation — `(c: { email: string }) => ...` — and
// without this the compiler would infer T from that annotation instead, so
// findOrFail would return the one-field shape written in the lambda rather
// than the row. Every caller then failed on the field it actually wanted:
// `.id does not exist on type { email: string }`. Array.prototype.find has no
// such problem, which is why the code this replaced type-checked.
predicate: (item: NoInfer<T>) => boolean,
description: string
): T {
const found = items.find(predicate);
if (found === undefined) {
// The count matters as much as the description: "0 rows" means the
// fixture never landed, while "37 rows" means it landed and the predicate
// is wrong. Those are different bugs and the message should tell them
// apart without a re-run.
throw new Error(
`expected to find ${description}, but none of the ${items.length} rows matched`
);
}
return found;
}