import { TestBed } from '@angular/core/testing'; import { describe, it, expect } from 'vitest'; import { Result, ok } from '@shared/kernel/fp'; import { BLOB_PRESENTER, BlobPresenter } from '@shared/application/blob-presenter'; import { UploadAdapter } from '@shared/infrastructure/upload.adapter'; import { UploadShellService } from '@shared/application/upload-shell.service'; import { OrgTemplate, OrgTemplateAdminView, SubOrgSummary } from '@brief/domain/org-template'; import { OrgTemplateAdapter } from '@brief/infrastructure/org-template.adapter'; import { OrgTemplateStore } from './org-template.store'; const template: OrgTemplate = { subOrgId: 'cibg-registers', orgName: 'CIBG — Registers', returnAddress: 'Postbus 00000\n2500 AA Den Haag', footerContact: 'info@voorbeeld.example', footerLegal: 'KvK 00000000', signatureName: 'A. de Vries', signatureRole: 'Hoofd Registratie', signatureClosing: 'Met vriendelijke groet,', margins: { topMm: 25, rightMm: 25, bottomMm: 25, leftMm: 25 }, version: 1, }; const view: OrgTemplateAdminView = { draft: template, publishedVersion: 1, history: [], unsentBriefs: 0, }; const subOrgs: SubOrgSummary[] = [ { subOrgId: 'cibg-registers', orgName: 'CIBG', publishedVersion: 1 }, ]; /** A recording fake of BLOB_PRESENTER (TE-006) — records every call instead of touching the DOM, so a spec can assert a command's success path directly. */ function fakeBlobPresenter() { const opened: Blob[] = []; const presenter: BlobPresenter = { open: (blob) => opened.push(blob), download: () => { throw new Error('not used by OrgTemplateStore'); }, }; return { presenter, opened }; } /** A no-op categories resource: the logo-upload sub-state is untouched by these tests, so 'idle' (never resolved) keeps the constructor effect from dispatching. */ function fakeCategoriesResource(): ReturnType { const fake = { status: () => 'idle' as const, value: () => undefined }; return fake as unknown as ReturnType; } function setup( adapter: Partial, blobPresenter: BlobPresenter, ): OrgTemplateStore { const uploadAdapter: Partial = { categoriesResource: () => fakeCategoriesResource(), }; TestBed.configureTestingModule({ providers: [ { provide: OrgTemplateAdapter, useValue: adapter }, { provide: UploadAdapter, useValue: uploadAdapter }, { provide: UploadShellService, useValue: {} }, { provide: BLOB_PRESENTER, useValue: blobPresenter }, ], }); return TestBed.inject(OrgTemplateStore); } // --- TE-006: proefbrief() ends in BLOB_PRESENTER.open, not a raw // window.open(URL.createObjectURL(...)) call, so both outcomes are assertable. --- describe('OrgTemplateStore.proefbrief', () => { it('opens the rendered proefbrief via BLOB_PRESENTER on success', async () => { // Given a loaded sub-org template. const { presenter, opened } = fakeBlobPresenter(); const blob = new Blob([''], { type: 'text/html' }); const store = setup( { list: (): Promise> => Promise.resolve(ok(subOrgs)), load: (): Promise> => Promise.resolve(ok(view)), proefbrief: (): Promise> => Promise.resolve(ok(blob)), }, presenter, ); await store.load(); // When proefbrief() is called... await store.proefbrief(); // Then the presenter receives exactly the rendered blob, and no error surfaces. expect(opened).toEqual([blob]); expect(store.lastError()).toBeNull(); }); it('surfaces the error without opening a tab on failure', async () => { // Given a loaded sub-org template whose proefbrief call fails server-side. const { presenter, opened } = fakeBlobPresenter(); const store = setup( { list: (): Promise> => Promise.resolve(ok(subOrgs)), load: (): Promise> => Promise.resolve(ok(view)), proefbrief: (): Promise> => Promise.resolve({ ok: false, error: 'mislukt' }), }, presenter, ); await store.load(); // When proefbrief() is called... await store.proefbrief(); // Then the presenter is never reached and the error is surfaced. expect(opened).toHaveLength(0); expect(store.lastError()).toBe('mislukt'); }); });