From d8f171b8cc646bd1629aa73e1a44a92989c4e2d7 Mon Sep 17 00:00:00 2001 From: Florian Sylvain Date: Mon, 5 Oct 2026 16:04:24 +0200 Subject: [PATCH] fix: more complete log in username error messages --- web/src/i18n/messages.ts | 3 +++ web/src/views/LoginView.vue | 16 +++++++++++++++- web/src/views/__tests__/LoginView.spec.ts | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/web/src/i18n/messages.ts b/web/src/i18n/messages.ts index 9ca44af..f51c56a 100644 --- a/web/src/i18n/messages.ts +++ b/web/src/i18n/messages.ts @@ -54,6 +54,9 @@ export const messages = { 'Logging in…': 'Connexion…', 'Try again in {0}': 'Réessayez dans {0}', 'Use at least 3 characters.': 'Utilisez au moins 3 caractères.', + 'Use your username, not your email address.': + "Utilisez votre nom d'utilisateur, pas votre adresse e-mail.", + 'Use 20 characters or fewer.': 'Utilisez 20 caractères au maximum.', 'Enter your password.': 'Saisissez votre mot de passe.', 'Hide password': 'Masquer le mot de passe', 'Show password': 'Afficher le mot de passe', diff --git a/web/src/views/LoginView.vue b/web/src/views/LoginView.vue index 6de514d..0208aa5 100644 --- a/web/src/views/LoginView.vue +++ b/web/src/views/LoginView.vue @@ -134,8 +134,22 @@ function focusFirstInvalid() { } } +function usernameProblem(value: string) { + const trimmed = value.trim() + if (trimmed.includes('@')) { + return 'Use your username, not your email address.' + } + if (trimmed.length < 3) { + return 'Use at least 3 characters.' + } + if (trimmed.length > 20) { + return 'Use 20 characters or fewer.' + } + return '' +} + function validate() { - usernameError.value = username.value.trim().length < 3 ? 'Use at least 3 characters.' : '' + usernameError.value = usernameProblem(username.value) passwordError.value = password.value.length === 0 ? 'Enter your password.' : '' return !usernameError.value && !passwordError.value } diff --git a/web/src/views/__tests__/LoginView.spec.ts b/web/src/views/__tests__/LoginView.spec.ts index 08878ff..8724aad 100644 --- a/web/src/views/__tests__/LoginView.spec.ts +++ b/web/src/views/__tests__/LoginView.spec.ts @@ -133,6 +133,25 @@ describe('Validation errors — nothing is sent for a short username and an empt }) }) +describe('Validation errors — specific username messages', () => { + it.each([ + ['marta@example.com', 'Use your username, not your email address.'], + ['a'.repeat(21), 'Use 20 characters or fewer.'], + ])('explains why %s is rejected without calling the API', async (value, message) => { + const login = vi.fn(() => mockResponse(200)) + const wrapper = await mountLogin('/login', { + 'POST /v1/auth/login': login, + }) + + await wrapper.find('input[placeholder="Your username"]').setValue(value) + await wrapper.find('form').trigger('submit') + await flushPromises() + + expect(login).not.toHaveBeenCalled() + expect(wrapper.text()).toContain(message) + }) +}) + describe('Submitting — fields and button are disabled while the request is in flight', () => { it('disables the fields and shows the loading label', async () => { let resolveLogin: (() => void) | null = null