fix(auth): prevent FormData crash on successful login (#503)
## What
Removes `resetOnSuccess={['password']}` from the login form.
## Sentry
- Fixes PHP-LARAVEL-2Y — `TypeError: Failed to construct 'FormData':
parameter 1 is not of type 'HTMLFormElement'.` (6 users, on `/login`)
## Root cause
On a successful login the server redirects to the dashboard, which
**unmounts the login form**. `resetOnSuccess` then fires a form reset,
and Inertia's reset handler constructs `new FormData(ref)` from the form
ref — now stale/null — which throws.
The reset serves no purpose on login (the user navigates away
regardless), so removing it eliminates the crash.
## Tests
- Added `resources/js/pages/auth/login.test.tsx`: renders the login page
and asserts the `<Form>` is not configured to reset on success.
This commit is contained in:
parent
a69c602366
commit
f4bbbfd767
|
|
@ -0,0 +1,66 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import { type ReactNode } from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import Login from './login';
|
||||
|
||||
globalThis.ResizeObserver ??= class {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
};
|
||||
|
||||
const capturedFormProps: Record<string, unknown> = {};
|
||||
|
||||
vi.mock('@inertiajs/react', () => ({
|
||||
Form: ({
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
children: (renderProps: {
|
||||
processing: boolean;
|
||||
errors: Record<string, string>;
|
||||
}) => ReactNode;
|
||||
} & Record<string, unknown>) => {
|
||||
Object.assign(capturedFormProps, props);
|
||||
|
||||
return <form>{children({ processing: false, errors: {} })}</form>;
|
||||
},
|
||||
Head: () => null,
|
||||
Link: ({ children }: { children: ReactNode }) => <a>{children}</a>,
|
||||
usePage: () => ({ props: { demoCredentials: undefined } }),
|
||||
}));
|
||||
|
||||
vi.mock('@/layouts/auth-layout', () => ({
|
||||
default: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock('@/lib/key-storage', () => ({
|
||||
clearKey: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/utils/i18n', () => ({
|
||||
__: (key: string) => key,
|
||||
}));
|
||||
|
||||
vi.mock('@/routes', () => ({
|
||||
register: () => ({ url: '/register' }),
|
||||
}));
|
||||
|
||||
vi.mock('@/routes/login', () => ({
|
||||
store: { form: () => ({ action: '/login', method: 'post' }) },
|
||||
}));
|
||||
|
||||
vi.mock('@/routes/password', () => ({
|
||||
request: () => ({ url: '/forgot-password' }),
|
||||
}));
|
||||
|
||||
describe('Login', () => {
|
||||
it('does not reset the form on success', () => {
|
||||
// The success redirect unmounts the form; resetting it afterwards makes
|
||||
// Inertia construct a FormData from a stale ref and crash (PHP-LARAVEL-2Y).
|
||||
render(<Login canResetPassword canRegister />);
|
||||
|
||||
expect(screen.getByRole('button', { name: /log in/i })).toBeTruthy();
|
||||
expect(capturedFormProps.resetOnSuccess).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -54,7 +54,6 @@ export default function Login({
|
|||
|
||||
<Form
|
||||
{...store.form()}
|
||||
resetOnSuccess={['password']}
|
||||
onSuccess={clearKey}
|
||||
className="flex flex-col gap-6"
|
||||
>
|
||||
|
|
|
|||
Loading…
Reference in New Issue