import { signal } from '@angular/core'; import { fireEvent, render, screen } from '@testing-library/angular'; import { of, throwError } from 'rxjs'; import { AuthService } from 'auth'; import { BffApiV1Service } from 'api-client'; import { axe } from 'vitest-axe'; import { RegistrationPage } from './registration-page'; class FakeAuth extends AuthService { readonly isAuthenticated = signal(true); readonly bsn = signal('123456782'); login(): void { /* noop */ } logout(): void { /* noop */ } } function providers( post = vi.fn().mockReturnValue(of({ registrationId: 'reg-9', status: 'Ingediend' })), withdraw = vi.fn().mockReturnValue(of(undefined)), ) { return { post, withdraw, providers: [ { provide: AuthService, useClass: FakeAuth }, { provide: BffApiV1Service, useValue: { postSelfServiceRegistrations: post, postSelfServiceRegistrationsIdWithdraw: withdraw, }, }, ], }; } describe('RegistrationPage', () => { it('shows the signed-in BSN', async () => { await render(RegistrationPage, { providers: providers().providers }); expect(screen.getByText(/123456782/)).toBeTruthy(); }); it('submits the registration and confirms', async () => { const { post, providers: p } = providers(); await render(RegistrationPage, { providers: p }); fireEvent.click(screen.getByRole('button', { name: /indienen/i })); expect(post).toHaveBeenCalledTimes(1); expect(await screen.findByText(/ontvangen/i)).toBeTruthy(); }); it('shows an error and keeps the submit available when the BFF call fails', async () => { const { post, providers: p } = providers(vi.fn().mockReturnValue(throwError(() => new Error('BFF rejected')))); await render(RegistrationPage, { providers: p }); fireEvent.click(screen.getByRole('button', { name: /indienen/i })); expect(post).toHaveBeenCalledTimes(1); // The failure is surfaced (not swallowed), the confirmation is not shown, and the user can retry. expect(await screen.findByRole('alert')).toBeTruthy(); expect(screen.queryByText(/ontvangen/i)).toBeNull(); expect(screen.getByRole('button', { name: /indienen/i })).toBeTruthy(); }); it('offers to withdraw after submitting, and withdrawing confirms', async () => { const { withdraw, providers: p } = providers(); await render(RegistrationPage, { providers: p }); fireEvent.click(screen.getByRole('button', { name: /indienen/i })); await screen.findByText(/ontvangen/i); fireEvent.click(await screen.findByRole('button', { name: /trek aanvraag in/i })); // The withdrawal is keyed by the reference the submit returned, and the page confirms it. expect(withdraw).toHaveBeenCalledWith('reg-9'); expect(await screen.findByText(/ingetrokken/i)).toBeTruthy(); }); it('surfaces a withdraw failure and keeps the action available', async () => { const { providers: p } = providers( vi.fn().mockReturnValue(of({ registrationId: 'reg-9', status: 'Ingediend' })), vi.fn().mockReturnValue(throwError(() => new Error('withdraw rejected'))), ); await render(RegistrationPage, { providers: p }); fireEvent.click(screen.getByRole('button', { name: /indienen/i })); await screen.findByText(/ontvangen/i); fireEvent.click(await screen.findByRole('button', { name: /trek aanvraag in/i })); expect(await screen.findByRole('alert')).toBeTruthy(); expect(screen.queryByText(/is ingetrokken/i)).toBeNull(); expect(screen.getByRole('button', { name: /trek aanvraag in/i })).toBeTruthy(); }); it('has no WCAG 2.1 AA violations on the submit page', async () => { // The portal is Dutch; the real index.html sets lang. Set it here so the document-level // html-has-lang rule reflects the app, not the bare jsdom document. document.documentElement.lang = 'nl'; const { container } = await render(RegistrationPage, { providers: providers().providers }); const results = await axe(container, { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'] }, }); expect(results.violations).toEqual([]); }); });