go-go-try
Version:
Tries to execute a sync/async function, returns a result tuple
117 lines (99 loc) • 3.48 kB
text/typescript
import { describe, test, expect } from 'vitest'
import { ensure } from './ensure.js'
import { taggedError } from './tagged-error.js'
import { UnknownError } from './unknown-error.js'
describe('ensure', () => {
test('returns value when predicate passes', () => {
const result = ensure(42, (n) => n > 0, () => new Error('negative'))
expect(result).toBe(42)
})
test('uses UnknownError when error parameter is omitted', () => {
expect(() => ensure(-1, (n) => n > 0)).toThrow(UnknownError)
})
test('uses UnknownError with async when error parameter is omitted', async () => {
await expect(ensure(Promise.resolve(-1), (n) => n > 0)).rejects.toThrow(UnknownError)
})
test('returns value when predicate passes with error class', () => {
const MyError = taggedError('MyError')
const result = ensure(42, (n) => n > 0, MyError)
expect(result).toBe(42)
})
test('returns value with complex predicate', () => {
const result = ensure([1, 2, 3], (arr) => arr.length > 0, () => new Error('empty'))
expect(result).toEqual([1, 2, 3])
})
test('throws error when predicate fails', () => {
expect(() => ensure(-1, (n) => n > 0, (n) => new Error(`${n} is negative`))).toThrow('-1 is negative')
})
test('throws with access to value in error factory', () => {
const obj = { status: 404 }
expect(() =>
ensure(
obj,
(o) => o.status === 200,
(o) => new Error(`HTTP ${o.status}`)
)
).toThrow('HTTP 404')
})
test('throws with error class constructor', () => {
const RequestFailed = taggedError('RequestFailed')
const obj = { status: 404 }
expect(() =>
ensure(
obj,
(o: { status: number }) => o.status === 200,
RequestFailed
)
).toThrow()
try {
ensure(obj, (o: { status: number }) => o.status === 200, RequestFailed)
} catch (e) {
expect(e).toBeInstanceOf(RequestFailed)
expect((e as Error & { cause?: unknown }).cause).toBe(obj)
}
})
test('narrows type when predicate is type guard', () => {
interface User {
name: string
}
const maybeUser: unknown = { name: 'Alice' }
const user = ensure(
maybeUser as unknown,
(u): u is User => typeof u === 'object' && u !== null && 'name' in u,
() => new Error('Not a user')
)
// TypeScript knows this is User, not unknown
expect((user as User).name).toBe('Alice')
})
test('works with async values (promise passed directly)', async () => {
const result = await ensure(Promise.resolve(42), (n) => n > 0, () => new Error('negative'))
expect(result).toBe(42)
})
test('throws with async values when predicate fails', async () => {
await expect(
ensure(Promise.resolve(-1), (n) => n > 0, (n) => new Error(`${n} is negative`))
).rejects.toThrow('-1 is negative')
})
test('works with sync functions', () => {
const result = ensure(() => 42, (n) => n > 0, () => new Error('negative'))
expect(result).toBe(42)
})
test('throws with sync functions when predicate fails', () => {
expect(() =>
ensure(() => -1, (n) => n > 0, (n) => new Error(`${n} is negative`))
).toThrow('-1 is negative')
})
test('works with async functions', async () => {
const result = await ensure(
() => Promise.resolve(42),
(n) => n > 0,
() => new Error('negative')
)
expect(result).toBe(42)
})
test('throws with async functions when predicate fails', async () => {
await expect(
ensure(() => Promise.resolve(-1), (n) => n > 0, (n) => new Error(`${n} is negative`))
).rejects.toThrow('-1 is negative')
})
})