Shared form for writeThrough and owned modes. Shows the ADR-002 disclaimer only in writeThrough mode (no client-side validation stricter than legacy provides). Maps 400 ErrorsResponse to per-field inline errors, 422/409 to a banner with the server's own text - never a client-side switch over invariant names. Embedded in case-detail with reload() on save so the actions block refreshes.
119 lines
4.8 KiB
TypeScript
119 lines
4.8 KiB
TypeScript
import { HttpErrorResponse } from '@angular/common/http';
|
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
import { of, throwError } from 'rxjs';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import { CaseDetailService } from '../case-detail.service';
|
|
import { ActionLink, ApplicantDetailsRequest } from '../case-detail.types';
|
|
import { EditApplicantDetails } from './edit-applicant-details';
|
|
|
|
const initial: ApplicantDetailsRequest = {
|
|
surname: 'de Vries',
|
|
initials: 'A.',
|
|
address: { street: 'Kerkweg', number: '12', postalCode: '3512JK', city: 'Utrecht' },
|
|
email: 'anna@example.nl',
|
|
phone: null,
|
|
preferredChannel: 'Post',
|
|
};
|
|
|
|
describe('EditApplicantDetails', () => {
|
|
let fixture: ComponentFixture<EditApplicantDetails>;
|
|
let updateDetails: ReturnType<typeof vi.fn>;
|
|
|
|
function render(actionLink: ActionLink): HTMLElement {
|
|
fixture = TestBed.createComponent(EditApplicantDetails);
|
|
fixture.componentRef.setInput('actionLink', actionLink);
|
|
fixture.componentRef.setInput('initial', initial);
|
|
fixture.detectChanges();
|
|
return fixture.nativeElement as HTMLElement;
|
|
}
|
|
|
|
function submitForm(el: HTMLElement): void {
|
|
el.querySelector('form')!.dispatchEvent(new Event('submit', { cancelable: true }));
|
|
fixture.detectChanges();
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
updateDetails = vi.fn();
|
|
await TestBed.configureTestingModule({
|
|
imports: [EditApplicantDetails],
|
|
providers: [{ provide: CaseDetailService, useValue: { updateDetails } }],
|
|
}).compileComponents();
|
|
});
|
|
|
|
it('shows the write-through disclaimer only in writeThrough mode', () => {
|
|
const writeThroughEl = render({ mode: 'writeThrough', href: '/api/worklist/legacy/1001/details' });
|
|
expect(writeThroughEl.querySelector('[data-testid="write-through-disclaimer"]')).toBeTruthy();
|
|
|
|
const ownedEl = render({ mode: 'owned', href: '/api/worklist/owned/abc/details' });
|
|
expect(ownedEl.querySelector('[data-testid="write-through-disclaimer"]')).toBeFalsy();
|
|
});
|
|
|
|
it('submits the form body to the given href and emits saved on success', () => {
|
|
updateDetails.mockReturnValue(of(undefined));
|
|
const el = render({ mode: 'owned', href: '/api/worklist/owned/abc/details' });
|
|
const savedSpy = vi.fn();
|
|
fixture.componentInstance.saved.subscribe(savedSpy);
|
|
|
|
submitForm(el);
|
|
|
|
expect(updateDetails).toHaveBeenCalledWith('/api/worklist/owned/abc/details', {
|
|
surname: 'de Vries',
|
|
initials: 'A.',
|
|
address: { street: 'Kerkweg', number: '12', postalCode: '3512JK', city: 'Utrecht' },
|
|
email: 'anna@example.nl',
|
|
phone: null,
|
|
preferredChannel: 'Post',
|
|
});
|
|
expect(savedSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('maps a 400 ErrorsResponse to per-field inline errors', () => {
|
|
updateDetails.mockReturnValue(
|
|
throwError(
|
|
() =>
|
|
new HttpErrorResponse({
|
|
status: 400,
|
|
error: {
|
|
errors: [
|
|
{ field: 'surname', message: 'Achternaam is verplicht.' },
|
|
{ field: 'address.number', message: 'Huisnummer is verplicht.' },
|
|
{ field: 'address.postalCode', message: 'Ongeldige postcode.' },
|
|
],
|
|
},
|
|
}),
|
|
),
|
|
);
|
|
const el = render({ mode: 'writeThrough', href: '/api/worklist/legacy/1001/details' });
|
|
|
|
submitForm(el);
|
|
|
|
expect(el.querySelector('[data-testid="field-error-surname"]')?.textContent).toContain('Achternaam is verplicht.');
|
|
expect(el.querySelector('[data-testid="field-error-address.number"]')?.textContent).toContain('Huisnummer is verplicht.');
|
|
expect(el.querySelector('[data-testid="field-error-address.postalCode"]')?.textContent).toContain('Ongeldige postcode.');
|
|
});
|
|
|
|
it('shows a 422 InvariantViolationResponse as an invariant + message banner', () => {
|
|
updateDetails.mockReturnValue(
|
|
throwError(() => new HttpErrorResponse({ status: 422, error: { invariant: 'Address.AllPartsRequired', message: 'All address parts are required.' } })),
|
|
);
|
|
const el = render({ mode: 'owned', href: '/api/worklist/owned/abc/details' });
|
|
|
|
submitForm(el);
|
|
|
|
expect(el.querySelector('[data-testid="edit-details-banner"]')?.textContent).toContain('Address.AllPartsRequired');
|
|
expect(el.querySelector('[data-testid="edit-details-banner"]')?.textContent).toContain('All address parts are required.');
|
|
});
|
|
|
|
it('shows a 409 MessageResponse as a banner', () => {
|
|
updateDetails.mockReturnValue(
|
|
throwError(() => new HttpErrorResponse({ status: 409, error: { message: 'This aanvraag has already been migrated.' } })),
|
|
);
|
|
const el = render({ mode: 'writeThrough', href: '/api/worklist/legacy/1002/details' });
|
|
|
|
submitForm(el);
|
|
|
|
expect(el.querySelector('[data-testid="edit-details-banner"]')?.textContent).toContain('This aanvraag has already been migrated.');
|
|
});
|
|
});
|