fix(e2e): restore the frontend build by typing the responses findOrFail searches (#254) #255

Merged
bermudalamb merged 1 commits from fix/254-frontend-build-break into main 2026-09-01 09:33:15 -05:00
6 changed files with 42 additions and 19 deletions
Showing only changes of commit 42412e8eeb - Show all commits
@@ -33,10 +33,12 @@ test.describe('Disabling a customer account', () => {
await accountModal.open();
await expect(accountModal.emailText(customer.email)).toBeVisible();
const customers = await (await request.get('/api/admin/customers')).json();
const customers: { id: number; email: string }[] = await (
await request.get('/api/admin/customers')
).json();
const id = findOrFail(
customers,
(c: { email: string }) => c.email === customer.email,
(c) => c.email === customer.email,
`the customer ${customer.email}`
).id;
await request.post(`/api/admin/customers/${id}/disable`);
@@ -56,10 +58,12 @@ test.describe('Disabling a customer account', () => {
authModal,
header
}) => {
const customers = await (await request.get('/api/admin/customers')).json();
const customers: { id: number; email: string }[] = await (
await request.get('/api/admin/customers')
).json();
const id = findOrFail(
customers,
(c: { email: string }) => c.email === customer.email,
(c) => c.email === customer.email,
`the customer ${customer.email}`
).id;
await request.post(`/api/admin/customers/${id}/disable`);
@@ -31,12 +31,14 @@ test.describe('Inline category creation from the item form', () => {
await adminInventory.confirmButton.click();
await expect(page.getByText('Item added')).toBeVisible();
const items = await (await page.request.get('/api/admin/items')).json();
const items: { name: string; category_name: string | null }[] = await (
await page.request.get('/api/admin/items')
).json();
// findOrFail already fails with the row count when the item is missing,
// which the toBeTruthy assertion this replaces could not report.
const saved = findOrFail(
items,
(item: { name: string }) => item.name === itemName,
(item) => item.name === itemName,
`the item named ${itemName}`
);
expect(saved.category_name).toBe(categoryName);
+4 -2
View File
@@ -40,10 +40,12 @@ test.describe('Admin taxonomy', () => {
// The table paginates and the shared database holds many tags, so the new
// row is confirmed through the API rather than hunted for across pages.
const tags = await (await page.request.get('/api/admin/tags')).json();
const tags: { name: string; color: string | null }[] = await (
await page.request.get('/api/admin/tags')
).json();
const created = findOrFail(
tags,
(tag: { name: string }) => tag.name === `vintage-${RUN}`,
(tag) => tag.name === `vintage-${RUN}`,
`the tag vintage-${RUN}`
);
expect(created.color).toBeTruthy();
+16 -8
View File
@@ -50,10 +50,12 @@ test.describe('Editing the customer emails', () => {
await expect(page.getByText('Password reset saved')).toBeVisible();
// Persisted, not merely accepted by the form.
const stored = await (await page.request.get('/api/admin/email-templates')).json();
const stored: { key: string; subject: string | null; body: string | null }[] = await (
await page.request.get('/api/admin/email-templates')
).json();
const reset = findOrFail(
stored,
(t: { key: string }) => t.key === 'passwordReset',
(t) => t.key === 'passwordReset',
'the passwordReset template'
);
expect(reset.subject).toBe(subject);
@@ -72,10 +74,12 @@ test.describe('Editing the customer emails', () => {
await expect(page.getByText('the body must keep {{resetUrl}}')).toBeVisible();
// And nothing was stored.
const stored = await (await page.request.get('/api/admin/email-templates')).json();
const stored: { key: string; subject: string | null; body: string | null }[] = await (
await page.request.get('/api/admin/email-templates')
).json();
const reset = findOrFail(
stored,
(t: { key: string }) => t.key === 'passwordReset',
(t) => t.key === 'passwordReset',
'the passwordReset template'
);
expect(reset.body).toBeNull();
@@ -95,10 +99,12 @@ test.describe('Editing the customer emails', () => {
await expect(page.getByText('Password reset restored to the default')).toBeVisible();
const stored = await (await page.request.get('/api/admin/email-templates')).json();
const stored: { key: string; subject: string | null; body: string | null }[] = await (
await page.request.get('/api/admin/email-templates')
).json();
const reset = findOrFail(
stored,
(t: { key: string }) => t.key === 'passwordReset',
(t) => t.key === 'passwordReset',
'the passwordReset template'
);
expect(reset.subject).toBeNull();
@@ -124,10 +130,12 @@ test.describe('Previewing the customer emails', () => {
// point: an admin sees the effect before committing to it.
await expect(adminEmails.preview('Password reset').getByText(wording)).toBeVisible();
const stored = await (await page.request.get('/api/admin/email-templates')).json();
const stored: { key: string; subject: string | null; body: string | null }[] = await (
await page.request.get('/api/admin/email-templates')
).json();
expect(findOrFail(
stored,
(t: { key: string }) => t.key === 'passwordReset',
(t) => t.key === 'passwordReset',
'the passwordReset template'
).body).toBeNull();
});
+2 -2
View File
@@ -178,10 +178,10 @@ test.describe('Filtering the storefront by favorites', () => {
await favorite(storefront, favoritePrompt, SELLS);
const api = await createAdminContext(playwright);
const items = await (await api.get('/api/items')).json();
const items: { id: number; name: string }[] = await (await api.get('/api/items')).json();
const sells = findOrFail(
items,
(item: { name: string }) => item.name === SELLS,
(item) => item.name === SELLS,
`the item named ${SELLS}`
);
await sellItem(api, sells.id);
+8 -1
View File
@@ -19,7 +19,14 @@
*/
export function findOrFail<T>(
items: T[],
predicate: (item: T) => boolean,
// 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);