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>
46 lines
2.1 KiB
TypeScript
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;
|
|
}
|