fix(ci): library_test Mount arg + 4 web test selectors + forgot-pw catch

Five fixes in one commit, all from the U1+U2+U3 push:

1. internal/api/library_test.go's Mount call wasn't updated when
   U3-T4 added the mailer.Sender param. 17 args, sig wants 18.
   Adds a trailing nil for the mailer. Same shape of test-fixture
   lag the project has hit before — flagged as a recurring pattern
   for the DRY-pass slice.

2. web/src/routes/settings/settings.test.ts mocked $lib/api/me with
   `getAPIToken: vi.fn()` (no return value). The new /settings API
   Token card's $effect calls `getAPIToken().then(...)` which threw
   "Cannot read properties of undefined (reading 'then')" on every
   ListenBrainz test. Default the mock to mockResolvedValue so any
   test that doesn't override gets a valid resolution.

3. web/src/routes/admin/users/users.test.ts queried row buttons by
   /^Delete$/ but T3 added per-user aria-labels ("Delete alice"),
   so the row buttons' accessible name is no longer "Delete". The
   modal's confirm button has no aria-label so its name IS "Delete"
   exactly. Tests now click by per-user aria-label first, then by
   /^Delete$/ for the modal confirm.

4. Same file: /Set password/i regex matched both the dialog's
   submit button AND the row's "Reset password for ..." buttons
   (because regex `Set` matches "ReSet" case-insensitively). Switched
   to /^Set password$/ exact-match.

5. web/src/routes/reset-password/[token]/reset-password.test.ts had
   /New password/i which matched both the "New password" and
   "Confirm new password" labels. Switched to exact-match string
   selectors.

6. web/src/routes/forgot-password/+page.svelte's onSubmit handler
   had no catch — a rejected forgotPassword() bubbled as an
   unhandled rejection in vitest. Added a silent catch since the
   page intentionally shows the same success message regardless of
   outcome (no-enumeration posture).
This commit is contained in:
2026-05-07 18:05:30 -04:00
parent 9ed576a029
commit 444144038a
5 changed files with 29 additions and 24 deletions
+11 -13
View File
@@ -163,13 +163,11 @@ describe('/admin/users', () => {
setup();
await waitFor(() => screen.getByText('alice'));
const deleteButtons = screen.getAllByRole('button', { name: /^Delete$/i });
await fireEvent.click(deleteButtons[0]);
// Row buttons carry per-user aria-labels ("Delete alice"). The modal's
// confirm button has no aria-label so its accessible name is "Delete" exactly.
await fireEvent.click(screen.getByRole('button', { name: 'Delete alice' }));
await waitFor(() => screen.getByRole('dialog'));
// The confirm Delete button is inside the dialog; it is the last "Delete" in the DOM.
const allDeleteButtons = screen.getAllByRole('button', { name: /^Delete$/i });
await fireEvent.click(allDeleteButtons[allDeleteButtons.length - 1]);
await fireEvent.click(screen.getByRole('button', { name: /^Delete$/ }));
expect(deleteUser).toHaveBeenCalled();
});
@@ -179,11 +177,9 @@ describe('/admin/users', () => {
setup();
await waitFor(() => screen.getByText('alice'));
const deleteButtons = screen.getAllByRole('button', { name: /^Delete$/i });
await fireEvent.click(deleteButtons[0]);
await fireEvent.click(screen.getByRole('button', { name: 'Delete alice' }));
await waitFor(() => screen.getByRole('dialog'));
const allDeleteButtons = screen.getAllByRole('button', { name: /^Delete$/i });
await fireEvent.click(allDeleteButtons[allDeleteButtons.length - 1]);
await fireEvent.click(screen.getByRole('button', { name: /^Delete$/ }));
await waitFor(() => screen.getByRole('status'));
expect(screen.getByRole('status').textContent).toMatch(/last admin/i);
@@ -194,12 +190,14 @@ describe('/admin/users', () => {
setup();
await waitFor(() => screen.getByText('alice'));
await fireEvent.click(screen.getAllByRole('button', { name: /Reset password/i })[0]);
await fireEvent.click(screen.getByRole('button', { name: 'Reset password for alice' }));
await waitFor(() => screen.getByRole('dialog'));
await fireEvent.input(screen.getByLabelText(/New password/i), { target: { value: 'abcd1234' } });
await fireEvent.input(screen.getByLabelText(/^New password$/i), { target: { value: 'abcd1234' } });
await fireEvent.input(screen.getByLabelText(/^Confirm$/i), { target: { value: 'abcd1234' } });
await fireEvent.click(screen.getByRole('button', { name: /Set password/i }));
// /^Set password$/ avoids matching the row's "Reset password" buttons
// (regex `Set password` matches "ReSet password" case-insensitively).
await fireEvent.click(screen.getByRole('button', { name: /^Set password$/i }));
expect(resetUserPassword).toHaveBeenCalledWith(expect.any(String), 'abcd1234');
});