diff --git a/frontend/tests/e2e/admin-disable-customer.spec.ts b/frontend/tests/e2e/admin-disable-customer.spec.ts index 937c47e..f7f93cb 100644 --- a/frontend/tests/e2e/admin-disable-customer.spec.ts +++ b/frontend/tests/e2e/admin-disable-customer.spec.ts @@ -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`); diff --git a/frontend/tests/e2e/admin-inline-category.spec.ts b/frontend/tests/e2e/admin-inline-category.spec.ts index 05997b4..e4a782b 100644 --- a/frontend/tests/e2e/admin-inline-category.spec.ts +++ b/frontend/tests/e2e/admin-inline-category.spec.ts @@ -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); diff --git a/frontend/tests/e2e/admin-taxonomy.spec.ts b/frontend/tests/e2e/admin-taxonomy.spec.ts index 6896b67..bd6ace9 100644 --- a/frontend/tests/e2e/admin-taxonomy.spec.ts +++ b/frontend/tests/e2e/admin-taxonomy.spec.ts @@ -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(); diff --git a/frontend/tests/e2e/email-templates.spec.ts b/frontend/tests/e2e/email-templates.spec.ts index 416d872..48fac51 100644 --- a/frontend/tests/e2e/email-templates.spec.ts +++ b/frontend/tests/e2e/email-templates.spec.ts @@ -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(); }); diff --git a/frontend/tests/e2e/favorites-filter.spec.ts b/frontend/tests/e2e/favorites-filter.spec.ts index ed0c700..45a6040 100644 --- a/frontend/tests/e2e/favorites-filter.spec.ts +++ b/frontend/tests/e2e/favorites-filter.spec.ts @@ -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); diff --git a/frontend/tests/e2e/support/findOrFail.ts b/frontend/tests/e2e/support/findOrFail.ts index d971086..a21de5c 100644 --- a/frontend/tests/e2e/support/findOrFail.ts +++ b/frontend/tests/e2e/support/findOrFail.ts @@ -19,7 +19,14 @@ */ export function findOrFail( 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) => boolean, description: string ): T { const found = items.find(predicate);