Files
v10/packages/store/test/guard.test.ts
T

155 lines
4.0 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import { StoreError } from '../src/errors';
import { all, any, timeout } from '../src/guard';
describe('guard', () => {
const createContext = () => ({
target: {},
signal: new AbortController().signal,
});
describe('all', () => {
it('passes when all guards return true', async () => {
const guard = all(
() => true,
() => true,
() => Promise.resolve(true),
);
expect(await guard(createContext())).toBe(true);
});
it('fails on first falsy guard', async () => {
const thirdGuard = vi.fn(() => true);
const guard = all(
() => true,
() => false,
thirdGuard,
);
expect(await guard(createContext())).toBe(false);
expect(thirdGuard).not.toHaveBeenCalled();
});
it('handles async guards', async () => {
const guard = all(
() => Promise.resolve(true),
async () => {
await new Promise(r => setTimeout(r, 10));
return true;
},
);
expect(await guard(createContext())).toBe(true);
});
it('fails on async falsy result', async () => {
const guard = all(
() => true,
() => Promise.resolve(false),
);
expect(await guard(createContext())).toBe(false);
});
});
describe('any', () => {
it('passes on first truthy sync result', () => {
const guard = any(
() => false,
() => true,
() => false,
);
expect(guard(createContext())).toBe(true);
});
it('returns false when all sync guards fail', () => {
const guard = any(
() => false,
() => false,
);
expect(guard(createContext())).toBe(false);
});
it('races async guards - first truthy wins', async () => {
const guard = any(
() => new Promise(r => setTimeout(() => r(false), 50)),
() => new Promise(r => setTimeout(() => r(true), 10)),
() => new Promise(r => setTimeout(() => r(false), 30)),
);
expect(await guard(createContext())).toBe(true);
});
it('returns false if all async guards resolve falsy', async () => {
const guard = any(
() => Promise.resolve(false),
() => Promise.resolve(0),
() => Promise.resolve(null),
);
expect(await guard(createContext())).toBe(false);
});
it('prefers sync truthy over pending async', () => {
const guard = any(
() => new Promise(() => {}), // never resolves
() => true,
);
expect(guard(createContext())).toBe(true);
});
});
describe('timeout', () => {
it('passes sync truthy immediately', async () => {
const guard = timeout(() => true, 1000);
expect(await guard(createContext())).toBe(true);
});
it('fails sync falsy immediately', async () => {
const guard = timeout(() => false, 1000);
expect(await guard(createContext())).toBe(false);
});
it('passes async within timeout', async () => {
const guard = timeout(
() => new Promise(r => setTimeout(() => r(true), 10)),
1000,
);
expect(await guard(createContext())).toBe(true);
});
it('throws StoreError on timeout', async () => {
vi.useFakeTimers();
const guard = timeout(
() => new Promise(() => {}), // never resolves
100,
'waitForReady',
);
const promise = guard(createContext());
vi.advanceTimersByTime(100);
await expect(promise).rejects.toThrow(StoreError);
await expect(promise).rejects.toMatchObject({
message: 'Timeout: waitForReady',
});
vi.useRealTimers();
});
it('clears timeout on abort', async () => {
vi.useFakeTimers();
const controller = new AbortController();
const guard = timeout(
() => new Promise(() => {}),
100,
);
guard({ target: {}, signal: controller.signal });
controller.abort();
// Should not throw - timeout was cleared
vi.advanceTimersByTime(200);
vi.useRealTimers();
});
});
});