`machine-remote-data.ts` defined a third encoding of an in-flight fetch: `LoadLifecycle`. It had three call sites, all one identical line, and the type was never imported by name. Move the mapping into `remote-data.ts` as `fromLoadLifecycle`, beside its neighbour `fromResource` — a `RemoteData` constructor, not a sixth encoding. The lowercase `loading`/`failed`/`loaded` tags on `BriefState`, `OrgTemplateState` and `StamdataEditorState` existed only because `LoadLifecycle` required them. Now that the constraint is inline and PascalCase, the three machines' load-lifecycle tags become `Loading`, `Failed` and `Loaded` — matching their own PascalCase message tags in the same file. `stamdata-editor.machine.spec.ts` no longer asserts a PascalCase message producing a lowercase state. `BriefStatus` (the letter's draft/submitted/approved/rejected/sent status, parsed off the wire from `BriefViewDto`) is a separate tag family and is untouched — its tag count stays 54 before and after this change. Delete `machine-remote-data.ts` and merge its spec into `remote-data.spec.ts`. Regenerate `behaviour-spec.mdx` (the `machineRemoteData` section heading becomes `fromLoadLifecycle`) and confirm `gen:snippets` reports no drift, since `remote-data.ts` carries a showcase region. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
53 lines
2.0 KiB
TypeScript
53 lines
2.0 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { RemoteData, fromLoadLifecycle, map2, map, successOf } from './remote-data';
|
|
import { loading, failure, empty, success } from '../testing/remote-data';
|
|
|
|
const loadingRd: RemoteData<string, number> = loading();
|
|
const failureRd: RemoteData<string, number> = failure('x');
|
|
const ok = (n: number): RemoteData<string, number> => success(n);
|
|
|
|
describe('RemoteData combinators', () => {
|
|
it('map only touches Success', () => {
|
|
const times10 = (n: number) => n * 10;
|
|
expect(map(ok(2), times10)).toEqual({ tag: 'Success', value: 20 });
|
|
expect(map(loadingRd, times10)).toEqual(loadingRd);
|
|
});
|
|
|
|
it('map2 precedence: Failure > Loading > Success', () => {
|
|
const add = (a: number, b: number) => a + b;
|
|
expect(map2(failureRd, ok(1), add)).toEqual(failureRd); // a failed
|
|
expect(map2(ok(1), failureRd, add)).toEqual(failureRd); // b failed
|
|
expect(map2(loadingRd, ok(1), add)).toEqual({ tag: 'Loading' });
|
|
expect(map2(ok(2), ok(3), add)).toEqual({ tag: 'Success', value: 5 });
|
|
});
|
|
});
|
|
|
|
describe('successOf', () => {
|
|
it('unwraps a Success value', () => {
|
|
expect(successOf(ok(2))).toBe(2);
|
|
});
|
|
|
|
it('is undefined for every other state', () => {
|
|
expect(successOf(loadingRd)).toBeUndefined();
|
|
expect(successOf(failureRd)).toBeUndefined();
|
|
expect(successOf(empty())).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('fromLoadLifecycle', () => {
|
|
it('maps Loading → Loading', () => {
|
|
expect(fromLoadLifecycle({ tag: 'Loading' })).toEqual(loading());
|
|
});
|
|
|
|
it('maps Failed → Failure carrying an Error with the reason', () => {
|
|
const rd = fromLoadLifecycle({ tag: 'Failed', reason: 'boom' });
|
|
expect(rd.tag).toBe('Failure');
|
|
if (rd.tag === 'Failure') expect(rd.error.message).toBe('boom');
|
|
});
|
|
|
|
it('maps Loaded → Success carrying the whole loaded state', () => {
|
|
const loadedState = { tag: 'Loaded', foo: 42 } as const;
|
|
expect(fromLoadLifecycle(loadedState)).toEqual(success(loadedState));
|
|
});
|
|
});
|