fix: allow bare domains in URL validation and update related error messages
This commit is contained in:
parent
776c3bc96e
commit
3565503da4
|
|
@ -22,9 +22,11 @@ const validateAuditRequest = [
|
||||||
body('name').trim().isLength({ min: 1, max: 100 }).escape(),
|
body('name').trim().isLength({ min: 1, max: 100 }).escape(),
|
||||||
body('company').trim().isLength({ min: 1, max: 150 }).escape(),
|
body('company').trim().isLength({ min: 1, max: 150 }).escape(),
|
||||||
body('email').trim().isEmail().normalizeEmail().isLength({ max: 254 }),
|
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')
|
body('url')
|
||||||
.trim()
|
.trim()
|
||||||
.isURL({ protocols: ['http', 'https'], require_protocol: true })
|
.isURL({ protocols: ['http', 'https'] })
|
||||||
.isLength({ max: 2048 }),
|
.isLength({ max: 2048 }),
|
||||||
body('role').isIn(['agency', 'client']),
|
body('role').isIn(['agency', 'client']),
|
||||||
body('motivation').trim().isLength({ min: 1, max: 2000 }).escape(),
|
body('motivation').trim().isLength({ min: 1, max: 2000 }).escape(),
|
||||||
|
|
|
||||||
|
|
@ -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 user = userEvent.setup();
|
||||||
|
const fetchSpy = vi
|
||||||
|
.spyOn(globalThis, 'fetch')
|
||||||
|
.mockResolvedValue(new Response(null, { status: 200 }));
|
||||||
renderForm();
|
renderForm();
|
||||||
|
|
||||||
|
await fillValidForm(user);
|
||||||
const urlField = screen.getByLabelText('Welche Seite soll ich prüfen?');
|
const urlField = screen.getByLabelText('Welche Seite soll ich prüfen?');
|
||||||
|
await user.clear(urlField);
|
||||||
await user.type(urlField, 'example.de');
|
await user.type(urlField, 'example.de');
|
||||||
await user.click(screen.getByRole('button', { name: 'Audit anfragen' }));
|
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
|
const [, init] = fetchSpy.mock.calls[0];
|
||||||
// once at the field. Assert the one a screen reader reads out for the
|
const payload = JSON.parse(String(init?.body));
|
||||||
// field, reached through aria-describedby.
|
expect(payload.url).toBe('example.de');
|
||||||
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://'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('posts to the configured endpoint and reports success', async () => {
|
it('posts to the configured endpoint and reports success', async () => {
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,6 @@ export const audit = {
|
||||||
urlLabel: 'Welche Seite soll ich prüfen?',
|
urlLabel: 'Welche Seite soll ich prüfen?',
|
||||||
urlHint: 'Die vollständige Adresse einer einzelnen Seite, zum Beispiel https://deine-firma.de/kontakt',
|
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.',
|
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?',
|
roleLegend: 'Wer bist du?',
|
||||||
roleAgency: 'Agentur oder Dienstleister',
|
roleAgency: 'Agentur oder Dienstleister',
|
||||||
|
|
|
||||||
|
|
@ -376,7 +376,6 @@ export interface TextConfig {
|
||||||
urlLabel: string;
|
urlLabel: string;
|
||||||
urlHint: string;
|
urlHint: string;
|
||||||
urlErrorRequired: string;
|
urlErrorRequired: string;
|
||||||
urlErrorInvalid: string;
|
|
||||||
roleLegend: string;
|
roleLegend: string;
|
||||||
roleAgency: string;
|
roleAgency: string;
|
||||||
roleClient: string;
|
roleClient: string;
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,6 @@ export const audit = {
|
||||||
urlLabel: 'Which page should I review?',
|
urlLabel: 'Which page should I review?',
|
||||||
urlHint: 'The full address of a single page, for example https://your-company.com/contact',
|
urlHint: 'The full address of a single page, for example https://your-company.com/contact',
|
||||||
urlErrorRequired: 'Please tell me which page to review.',
|
urlErrorRequired: 'Please tell me which page to review.',
|
||||||
urlErrorInvalid: 'Please give the full address, including https://',
|
|
||||||
|
|
||||||
roleLegend: 'Who are you?',
|
roleLegend: 'Who are you?',
|
||||||
roleAgency: 'Agency or service provider',
|
roleAgency: 'Agency or service provider',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue