import { TestBed } from '@angular/core/testing'; import { SITE_CONTENT_DATA } from '../../core/content/site-content'; import { SITE_CONFIG } from '../../core/content/site-config'; import { ContactBriefing } from './contact-briefing'; function setControl(root: HTMLElement, id: string, value: string): void { const control = root.querySelector( `#${id}`, ); expect(control).toBeTruthy(); control!.value = value; control!.dispatchEvent(new Event('input', { bubbles: true })); } describe('ContactBriefing', () => { it('binds labels, builds an encoded mailto link and never talks to a server', async () => { const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response()); const xhrSpy = vi.spyOn(XMLHttpRequest.prototype, 'send').mockImplementation(() => undefined); await TestBed.configureTestingModule({ imports: [ContactBriefing], }).compileComponents(); const fixture = TestBed.createComponent(ContactBriefing); const copy = SITE_CONTENT_DATA.de.contact; fixture.componentRef.setInput('copy', copy); await fixture.whenStable(); const root = fixture.nativeElement as HTMLElement; for (const field of copy.fields) { const controlId = `contact-${field.id}`; const label = root.querySelector(`label[for="${controlId}"]`); const control = root.querySelector(`#${controlId}`); expect(label?.textContent).toContain(field.label); expect(control).toBeTruthy(); } expect(root.querySelector('form[action]')).toBeNull(); expect(root.textContent).toContain(copy.incompleteHint); expect(root.querySelector('[aria-live="polite"]')).toBeTruthy(); expect(root.querySelector(`a[href="${SITE_CONFIG.calendarUrl}"]`)).toBeNull(); expect(root.textContent).not.toContain(copy.calendarLabel); const special = 'Äpfel & Birnen?\nPreis=100'; setControl(root, 'contact-name', special); setControl(root, 'contact-email', 'team@example.com'); setControl(root, 'contact-projectType', 'software'); setControl(root, 'contact-situation', special); setControl(root, 'contact-timeframe', 'Q3'); fixture.detectChanges(); expect(root.textContent).not.toContain(copy.incompleteHint); expect(root.querySelector('[aria-live="polite"]')).toBeTruthy(); const submit = root.querySelector('a.submit'); expect(submit?.getAttribute('href')?.startsWith('mailto:info@antoniolede.de?subject=')).toBe( true, ); const href = submit?.getAttribute('href') ?? ''; expect(href.includes(' ')).toBe(false); expect(href.includes('\n')).toBe(false); const query = href.slice(href.indexOf('?') + 1); const [subjectPart, bodyPart] = query.split('&body='); expect(subjectPart.startsWith('subject=')).toBe(true); expect(subjectPart.includes('&')).toBe(false); expect(bodyPart.includes('&')).toBe(false); const decodedBody = decodeURIComponent(bodyPart); expect(decodedBody).toContain(special); expect(decodedBody).toContain('team@example.com'); const projectType = copy.fields.find((field) => field.id === 'projectType'); const projectTypeLabel = projectType?.options?.find( (option) => option.value === 'software', )?.label; expect(projectTypeLabel).toBeTruthy(); expect(decodedBody).toContain(`${projectType!.label}: ${projectTypeLabel}`); const company = copy.fields.find((field) => field.id === 'company'); expect(company).toBeTruthy(); expect(decodedBody).not.toContain(`${company!.label}:`); expect(decodeURIComponent(subjectPart.slice('subject='.length))).toBe(copy.mailSubject); expect(fetchSpy).not.toHaveBeenCalled(); expect(xhrSpy).not.toHaveBeenCalled(); }); });