f0736f4d35
Standardizes the getX() / __resetX() pattern hand-rolled identically across every store: lazy construction on first get(), memoized thereafter, and a reset() that runs an optional teardown (e.g. destroy()) and clears so the next get() rebuilds. Lazy by construction, so owning modules stay inert at import. Covered by unit tests (laziness, memoization, rebuild-after-reset, teardown-once-with-live-instance, reset-before-get no-op, falsy-value caching). Not yet adopted by the stores — that migration is a separate step.
87 lines
2.5 KiB
TypeScript
87 lines
2.5 KiB
TypeScript
import {
|
|
describe,
|
|
expect,
|
|
it,
|
|
vi,
|
|
} from 'vitest';
|
|
import { createSingleton } from './createSingleton';
|
|
|
|
describe('createSingleton', () => {
|
|
it('does not call the factory until the first get (lazy)', () => {
|
|
const factory = vi.fn(() => ({ id: 1 }));
|
|
createSingleton(factory);
|
|
expect(factory).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('constructs on first get and memoizes the instance', () => {
|
|
const factory = vi.fn(() => ({ id: 1 }));
|
|
const singleton = createSingleton(factory);
|
|
|
|
const a = singleton.get();
|
|
const b = singleton.get();
|
|
|
|
expect(factory).toHaveBeenCalledTimes(1);
|
|
expect(a).toBe(b);
|
|
});
|
|
|
|
it('rebuilds a fresh instance after reset', () => {
|
|
let count = 0;
|
|
const singleton = createSingleton(() => ({ id: ++count }));
|
|
|
|
const first = singleton.get();
|
|
singleton.reset();
|
|
const second = singleton.get();
|
|
|
|
expect(first).not.toBe(second);
|
|
expect(second.id).toBe(2);
|
|
});
|
|
|
|
it('runs teardown once, with the live instance, on reset', () => {
|
|
const teardown = vi.fn();
|
|
const singleton = createSingleton(() => ({ id: 1 }), teardown);
|
|
|
|
const instance = singleton.get();
|
|
singleton.reset();
|
|
|
|
expect(teardown).toHaveBeenCalledTimes(1);
|
|
expect(teardown).toHaveBeenCalledWith(instance);
|
|
});
|
|
|
|
it('treats reset before any get as a no-op (no teardown, no throw)', () => {
|
|
const teardown = vi.fn();
|
|
const singleton = createSingleton(() => ({ id: 1 }), teardown);
|
|
|
|
expect(() => singleton.reset()).not.toThrow();
|
|
expect(teardown).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('does not run teardown again on a second consecutive reset', () => {
|
|
const teardown = vi.fn();
|
|
const singleton = createSingleton(() => ({ id: 1 }), teardown);
|
|
|
|
singleton.get();
|
|
singleton.reset();
|
|
singleton.reset();
|
|
|
|
expect(teardown).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('works without a teardown', () => {
|
|
const singleton = createSingleton(() => ({ id: 1 }));
|
|
|
|
singleton.get();
|
|
expect(() => singleton.reset()).not.toThrow();
|
|
expect(singleton.get().id).toBe(1);
|
|
});
|
|
|
|
it('caches a falsy instance value without re-running the factory', () => {
|
|
const factory = vi.fn(() => undefined);
|
|
const singleton = createSingleton<undefined>(factory);
|
|
|
|
singleton.get();
|
|
singleton.get();
|
|
|
|
expect(factory).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|