Files
atomic-design-poc/apps/ssp/src/app/registratie/application/find-concept.spec.ts
T
ehoandClaude Opus 5 d518a1466c refactor(registratie): extract the read half of createDraftSync (RB-21)
createDraftSync mixed a read path (findConcept, load, the read half of
resume) with its write path (ensureId, flush, submit, reset) in one
187-line function -- CQ-001's finding. Move findConcept and loadConcept
into a new application/find-concept.ts as free functions that take the
adapter, so they get a direct spec with no Angular TestBed.

createDraftSync keeps the closure state (id, ensuring, resumeGate) and
the whole write path unchanged -- this is a move, not a redesign. The
resumeGate coupling that lets the write path wait for the read path
stays exactly where it was.

createDraftSync shrinks from 187 to 169 lines. draft-sync.spec.ts is
unchanged -- it never called resume()/load() directly, and its 409
recovery test for submit() still exercises the extracted findConcept
through ensureId's catch branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:23:13 +02:00

109 lines
3.4 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { ApplicationsAdapter } from '@registratie/infrastructure/applications.adapter';
import { findConcept, loadConcept } from './find-concept';
// Free functions taking the adapter as a parameter (no inject()) — a plain fake
// object is enough, no Angular TestBed needed.
function fakeAdapter(overrides: Partial<ApplicationsAdapter>): ApplicationsAdapter {
return overrides as ApplicationsAdapter;
}
describe('findConcept', () => {
it('returns the id of the existing Concept of the given type', async () => {
const adapter = fakeAdapter({
list: async () => [
{
id: 'a1',
type: 'registratie',
status: { tag: 'Concept', stepIndex: 0, stepCount: 3 },
createdAt: '2026-01-01T00:00:00Z',
updatedAt: '2026-01-01T00:00:00Z',
},
],
});
await expect(findConcept(adapter, 'registratie')).resolves.toBe('a1');
});
it('returns undefined when the list has no application of the given type', async () => {
const adapter = fakeAdapter({ list: async () => [] });
await expect(findConcept(adapter, 'registratie')).resolves.toBeUndefined();
});
it('returns undefined when the matching type is not a Concept', async () => {
const adapter = fakeAdapter({
list: async () => [
{
id: 'a1',
type: 'registratie',
status: { tag: 'Ingediend', referentie: 'R1' },
createdAt: '2026-01-01T00:00:00Z',
updatedAt: '2026-01-01T00:00:00Z',
},
],
});
await expect(findConcept(adapter, 'registratie')).resolves.toBeUndefined();
});
it('returns undefined when adapter.list() resolves with an unparsable shape', async () => {
const adapter = fakeAdapter({ list: async () => 'not-an-array' as unknown as [] });
await expect(findConcept(adapter, 'registratie')).resolves.toBeUndefined();
});
it('returns undefined when adapter.list() rejects', async () => {
const adapter = fakeAdapter({
list: async () => {
throw new Error('network down');
},
});
await expect(findConcept(adapter, 'registratie')).resolves.toBeUndefined();
});
});
describe('loadConcept', () => {
it('reads the draft off a Concept', async () => {
const adapter = fakeAdapter({
detail: async () => ({
id: 'a1',
status: { tag: 'Concept', stepIndex: 1, stepCount: 3 },
draft: { step: 1 },
}),
});
await expect(loadConcept(adapter, 'a1')).resolves.toEqual({
tag: 'concept',
draft: { step: 1 },
});
});
it('reports a missing draft as null', async () => {
const adapter = fakeAdapter({
detail: async () => ({ id: 'a1', status: { tag: 'Concept', stepIndex: 0, stepCount: 3 } }),
});
await expect(loadConcept(adapter, 'a1')).resolves.toEqual({ tag: 'concept', draft: null });
});
it('reports not-concept when the id has moved past Concept (submitted)', async () => {
const adapter = fakeAdapter({
detail: async () => ({ id: 'a1', status: { tag: 'Ingediend', referentie: 'R1' } }),
});
await expect(loadConcept(adapter, 'a1')).resolves.toEqual({ tag: 'not-concept' });
});
it('reports not-concept when the id is unknown or deleted (detail rejects)', async () => {
const adapter = fakeAdapter({
detail: async () => {
throw new Error('404');
},
});
await expect(loadConcept(adapter, 'gone')).resolves.toEqual({ tag: 'not-concept' });
});
});