test(frontend): correct test to fit sign up/in

This commit is contained in:
Edouard Vanbelle
2026-07-14 03:21:18 +02:00
parent 691d01a458
commit 341e354162
4 changed files with 475 additions and 449 deletions
+2 -8
View File
@@ -454,10 +454,7 @@
bind:value={username} bind:value={username}
bind:this={loginIdentifierInput} bind:this={loginIdentifierInput}
autocomplete="username" autocomplete="username"
placeholder={t( placeholder={t('auth.login_identifier_placeholder', 'Enter your username or email')}
'auth.login_identifier_placeholder',
'Enter your username or email'
)}
required required
disabled={busy} disabled={busy}
/> />
@@ -606,10 +603,7 @@
{#if passwordLoginEnabled} {#if passwordLoginEnabled}
<div class="auth-input-group"> <div class="auth-input-group">
<label class="auth-label" for="reg-password"> <label class="auth-label" for="reg-password">
{t( {t('auth.password_optional', 'Password (optional — leave blank for a sign-in link)')}
'auth.password_optional',
'Password (optional — leave blank for a sign-in link)'
)}
</label> </label>
<div class="auth-input-wrap auth-input-wrap--lock has-toggle"> <div class="auth-input-wrap auth-input-wrap--lock has-toggle">
<input <input
+18 -5
View File
@@ -41,7 +41,15 @@ beforeEach(() => {
pageState.url = new URL('http://localhost/login'); pageState.url = new URL('http://localhost/login');
session.user = null; session.user = null;
m(auth.fetchMe).mockResolvedValue(null); m(auth.fetchMe).mockResolvedValue(null);
m(auth.getOidcProviders).mockResolvedValue({ providers: [] }); // Default provider info: both password + magic-link enabled, OIDC off.
// The unified login form's magic-link submit path is only reachable
// when `magic_link_login_enabled === true` — without this pin the
// "sends a magic link" test can't reach `sendMagicLink()`.
m(auth.getOidcProviders).mockResolvedValue({
enabled: false,
password_login_enabled: true,
magic_link_login_enabled: true
});
m(auth.getAuthStatus).mockResolvedValue({ initialized: true }); m(auth.getAuthStatus).mockResolvedValue({ initialized: true });
}); });
@@ -75,16 +83,21 @@ it('enters setup mode on a fresh install', async () => {
await screen.findByTestId('login-setup-form'); await screen.findByTestId('login-setup-form');
}); });
it('sends a magic link', async () => { it('sends a magic link when the password field is left empty', async () => {
// Unified form: the same identifier input drives both flows. Filling
// the identifier and leaving password empty makes `submitAsMagicLink`
// derived resolve to true — the single submit button then dispatches
// to `sendMagicLink` instead of `login`.
m(auth.sendMagicLink).mockResolvedValue('sent'); m(auth.sendMagicLink).mockResolvedValue('sent');
render(LoginPage); render(LoginPage);
await screen.findByTestId('login-form'); await screen.findByTestId('login-form');
await fireEvent.click(screen.getByTestId('login-magic-toggle-btn')); await fireEvent.input(screen.getByTestId('login-username-input'), {
await fireEvent.input(screen.getByTestId('login-magic-email-input'), {
target: { value: 'a@b.test' } target: { value: 'a@b.test' }
}); });
await fireEvent.click(screen.getByTestId('login-magic-send-btn')); // Password intentionally NOT filled.
await fireEvent.click(screen.getByTestId('login-submit-btn'));
await waitFor(() => expect(auth.sendMagicLink).toHaveBeenCalledWith('a@b.test')); await waitFor(() => expect(auth.sendMagicLink).toHaveBeenCalledWith('a@b.test'));
expect(auth.login).not.toHaveBeenCalled();
}); });
it('registers a new account', async () => { it('registers a new account', async () => {
+15 -4
View File
@@ -29,11 +29,22 @@ test.describe('SPA · authentication', () => {
await expect(page.getByTestId('login-form')).toBeVisible(); await expect(page.getByTestId('login-form')).toBeVisible();
}); });
test('magic-link panel toggles open', async ({ page }) => { test('submit button dispatches to magic-link when password is empty', async ({ page }) => {
// Unified login form: one identifier + one optional password + one
// adaptive submit button. Filling the identifier and leaving the
// password blank flips the button label to "Send sign-in link" and
// routes to /api/auth/magic-link/send on click. The old two-form
// UX with `login-magic-toggle-btn` was retired 2026-07-14.
await page.goto('/login'); await page.goto('/login');
await page.getByTestId('login-magic-toggle-btn').click(); await expect(page.getByTestId('login-form')).toBeVisible();
await expect(page.getByTestId('login-magic-form')).toBeVisible(); await page.getByTestId('login-username-input').fill('someone@example.test');
await expect(page.getByTestId('login-magic-email-input')).toBeVisible(); // Password intentionally NOT filled — this drives the label swap.
const submit = page.getByTestId('login-submit-btn');
await expect(submit).toBeVisible();
// Label content differs per mode: password-empty → magic-link copy;
// password-filled → "Sign in". Assert the magic-link copy is what's
// shown so the dispatch is provably in the magic-link branch.
await expect(submit).toHaveText(/link|Link|Send/);
}); });
test('successful login reaches the files app shell', async ({ page }) => { test('successful login reaches the files app shell', async ({ page }) => {
+14 -6
View File
@@ -58,13 +58,21 @@ test('an oidc callback code is exchanged on load', async ({ page }) => {
}); });
test('request a magic link from the login page', async ({ page }) => { test('request a magic link from the login page', async ({ page }) => {
// Unified form: leave the password field empty and submit — the
// adaptive submit routes to /api/auth/magic-link/send with the
// identifier as-is (backend accepts email OR username via `@`
// dispatch). Old separate `login-magic-*` testids retired in the
// single-form refactor.
await page.goto('/login'); await page.goto('/login');
await page.getByTestId('login-magic-toggle-btn').click(); await expect(page.getByTestId('login-form')).toBeVisible();
await expect(page.getByTestId('login-magic-form')).toBeVisible();
await page.getByTestId('login-magic-email-input').fill('someone@example.test'); await page.getByTestId('login-username-input').fill('someone@example.test');
await page.getByTestId('login-magic-send-btn').click(); // Password intentionally NOT filled.
// A status message resolves (success or error); give the request time to run. await page.getByTestId('login-submit-btn').click();
// A status message resolves (uniform 200 anti-enum success or error);
// give the request time to run.
await page.waitForTimeout(1_000); await page.waitForTimeout(1_000);
await expect(page.getByTestId('login-magic-form').or(page.getByTestId('login-form')).first()).toBeVisible(); // Still on the login page either way — anti-enum success doesn't redirect.
await expect(page.getByTestId('login-form')).toBeVisible();
}); });