Files
Portfolio/src/app/shared/contact-briefing/contact-briefing.spec.ts
Antonio Ledebuhr cfc7580a8f content: repair visitor-facing copy and briefing accessibility
Public pages no longer describe their own pipeline, case and service
labels are real accessible names, and the contact briefing writes
localized fields without empty optional lines.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-25 17:46:32 +02:00

89 lines
3.7 KiB
TypeScript

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<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(
`#${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<HTMLAnchorElement>('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();
});
});