Model the multi-step form as one tagged union: step/errors exist only while Editing, and Submitting/Submitted/Failed carry a parsed Valid payload. So "submitting while a field is invalid" and "success screen with errors set" are unrepresentable by construction. Pure transitions (next/back/submit/resolve) with a spec covering the key invariants; illegal events are no-ops. The page becomes pure composition. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
33 lines
1.5 KiB
TypeScript
33 lines
1.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { ok, err } from '../../core/fp';
|
|
import { initial, next, back, submit, resolve, WizardState } from './wizard.machine';
|
|
|
|
const editing1 = (uren: string, punten = ''): WizardState => ({ tag: 'Editing', step: 1, draft: { uren, punten }, errors: {} });
|
|
const editing2 = (uren: string, punten: string): WizardState => ({ tag: 'Editing', step: 2, draft: { uren, punten }, errors: {} });
|
|
|
|
describe('wizard.machine', () => {
|
|
it('next advances only when step 1 parses', () => {
|
|
expect(next(initial).tag).toBe('Editing'); // empty uren -> stays, with error
|
|
expect((next(initial) as any).errors.uren).toBeTruthy();
|
|
expect((next(editing1('4160')) as any).step).toBe(2);
|
|
});
|
|
|
|
it('submit reaches Submitting ONLY with fully valid data', () => {
|
|
expect(submit(editing2('4160', 'x')).tag).toBe('Editing'); // invalid punten -> no Submitting
|
|
const good = submit(editing2('4160', '200'));
|
|
expect(good.tag).toBe('Submitting');
|
|
expect((good as any).data).toEqual({ uren: 4160, punten: 200 });
|
|
});
|
|
|
|
it('back / resolve are no-ops from illegal states', () => {
|
|
expect(back(initial)).toBe(initial); // step 1, nothing to go back to
|
|
expect(resolve(initial, ok(undefined))).toBe(initial); // not Submitting
|
|
});
|
|
|
|
it('resolve maps Submitting to Submitted / Failed', () => {
|
|
const submitting = submit(editing2('4160', '200'));
|
|
expect(resolve(submitting, ok(undefined)).tag).toBe('Submitted');
|
|
expect(resolve(submitting, err('boom')).tag).toBe('Failed');
|
|
});
|
|
});
|