fix: allow bare domains in URL validation and update related error messages

This commit is contained in:
Sascha 2026-07-30 10:42:44 +02:00
parent 776c3bc96e
commit 3565503da4
5 changed files with 14 additions and 18 deletions

View File

@ -22,9 +22,11 @@ const validateAuditRequest = [
body('name').trim().isLength({ min: 1, max: 100 }).escape(),
body('company').trim().isLength({ min: 1, max: 150 }).escape(),
body('email').trim().isEmail().normalizeEmail().isLength({ max: 254 }),
// No require_protocol: people paste bare domains ("example.com") too.
// protocols still pins an explicit scheme, if given, to http/https.
body('url')
.trim()
.isURL({ protocols: ['http', 'https'], require_protocol: true })
.isURL({ protocols: ['http', 'https'] })
.isLength({ max: 2048 }),
body('role').isIn(['agency', 'client']),
body('motivation').trim().isLength({ min: 1, max: 2000 }).escape(),

View File

@ -73,28 +73,25 @@ describe('AuditForm', () => {
});
});
it('rejects a URL without a protocol and wires the message to the field', async () => {
it('accepts a bare domain without a protocol', async () => {
const user = userEvent.setup();
const fetchSpy = vi
.spyOn(globalThis, 'fetch')
.mockResolvedValue(new Response(null, { status: 200 }));
renderForm();
await fillValidForm(user);
const urlField = screen.getByLabelText('Welche Seite soll ich prüfen?');
await user.clear(urlField);
await user.type(urlField, 'example.de');
await user.click(screen.getByRole('button', { name: 'Audit anfragen' }));
await waitFor(() => expect(urlField).toHaveAttribute('aria-invalid', 'true'));
await waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(1));
expect(urlField).not.toHaveAttribute('aria-invalid', 'true');
// The message deliberately appears twice - once in the error summary and
// once at the field. Assert the one a screen reader reads out for the
// field, reached through aria-describedby.
const describedBy = urlField.getAttribute('aria-describedby') ?? '';
const errorNode = describedBy
.split(' ')
.map((id) => document.getElementById(id))
.find((node) => node?.className.includes('audit-form__error'));
expect(errorNode).toHaveTextContent(
'Bitte gib die vollständige Adresse an, inklusive https://'
);
const [, init] = fetchSpy.mock.calls[0];
const payload = JSON.parse(String(init?.body));
expect(payload.url).toBe('example.de');
});
it('posts to the configured endpoint and reports success', async () => {

View File

@ -96,7 +96,6 @@ export const audit = {
urlLabel: 'Welche Seite soll ich prüfen?',
urlHint: 'Die vollständige Adresse einer einzelnen Seite, zum Beispiel https://deine-firma.de/kontakt',
urlErrorRequired: 'Bitte nenn mir die Seite, die ich prüfen soll.',
urlErrorInvalid: 'Bitte gib die vollständige Adresse an, inklusive https://',
roleLegend: 'Wer bist du?',
roleAgency: 'Agentur oder Dienstleister',

View File

@ -376,7 +376,6 @@ export interface TextConfig {
urlLabel: string;
urlHint: string;
urlErrorRequired: string;
urlErrorInvalid: string;
roleLegend: string;
roleAgency: string;
roleClient: string;

View File

@ -96,7 +96,6 @@ export const audit = {
urlLabel: 'Which page should I review?',
urlHint: 'The full address of a single page, for example https://your-company.com/contact',
urlErrorRequired: 'Please tell me which page to review.',
urlErrorInvalid: 'Please give the full address, including https://',
roleLegend: 'Who are you?',
roleAgency: 'Agency or service provider',