@cardbrother/up-fetch
Version:
Advanced fetch client builder for typescript.
338 lines (311 loc) • 10.5 kB
text/typescript
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { describe, expectTypeOf, test } from 'vitest'
import { up } from './up'
import type {
JsonifiableArray,
JsonifiableObject,
ResolvedOptions,
} from './types'
import { fallbackOptions } from './fallback-options'
import { z } from 'zod'
import * as v from 'valibot'
test('infer TData', async () => {
let upfetch = up(fetch, () => ({
parseResponse: (res, options) => Promise.resolve(1),
onSuccess(data, options) {
expectTypeOf(data).toEqualTypeOf<any>()
},
}))
let data1 = await upfetch('')
expectTypeOf(data1).toEqualTypeOf<number>()
let data2 = await upfetch('', {
parseResponse: (res, options) => Promise.resolve(''),
})
expectTypeOf(data2).toEqualTypeOf<string>()
let upfetch2 = up(fetch)
let data5 = await upfetch2('')
expectTypeOf(data5).toEqualTypeOf<any>()
let data6 = await upfetch2('', {
parseResponse: (res) => res,
})
expectTypeOf(data6).toEqualTypeOf<Response>()
let data7 = await upfetch2('', {
parseResponse: (res: any) => ({ res }),
schema: v.object({
res: v.string(),
}),
})
expectTypeOf(data7).toEqualTypeOf<{ res: string }>()
let data8 = await upfetch2('', {
schema: z.number(),
})
expectTypeOf(data8).toEqualTypeOf<number>()
let upfetch3 = up(fetch, () => ({
parseResponse: () => 1,
}))
// Using currying
let curried = () => (res: Response) => res.text()
let data9 = await upfetch3('', {
parseResponse: curried(),
schema: v.pipe(
v.string(),
v.transform(() => '!' as const),
),
})
expectTypeOf(data9).toEqualTypeOf<'!'>()
})
test('Infer body', () => {
/**
* Fallback body
*/
type FallbackOkBody =
| BodyInit
| JsonifiableArray
| JsonifiableObject
| null
| undefined
let upfetch1_1 = up(fetch)
let upfetch1_2 = up(fetch, () => ({}))
upfetch1_1('', { body: {} as FallbackOkBody })
upfetch1_2('', { body: {} as FallbackOkBody })
// @ts-expect-error illegal type
upfetch1_1('', { body: true })
// @ts-expect-error illegal type
upfetch1_2('', { body: true })
// @ts-expect-error illegal type
upfetch1_1('', { body: Symbol() })
// @ts-expect-error illegal type
upfetch1_2('', { body: Symbol() })
/**
* Default body
*/
function serializeBody2(b: DefaultOkBody) {
return ''
}
type DefaultOkBody = symbol
let upfetch2 = up(fetch, () => ({
serializeBody: serializeBody2,
// this broke body's type inference, leave it here for the test
serializeParams: (p) => '',
}))
upfetch2('', { body: {} as DefaultOkBody | null | undefined })
// @ts-expect-error illegal type
upfetch2('', { body: true })
// @ts-expect-error illegal type
upfetch2('', { body: 1 })
/**
* Fetcher body
*/
function serializeBody3(b: DefaultOkBody) {
return ''
}
type FetcherOkBody = number
let upfetch3 = up(fetch, () => ({ serializeBody: serializeBody3 }))
upfetch3('', {
serializeBody: (body: FetcherOkBody) => '',
body: {} as FetcherOkBody | null | undefined,
})
upfetch3('', {
// @ts-expect-error illegal type
body: true,
serializeBody: (body: FetcherOkBody) => '',
})
upfetch3('', {
// @ts-expect-error illegal type
body: '',
serializeBody: (body: FetcherOkBody) => '',
})
})
test('The defaultSerializer of params should expect 1 arg only (the params)', async () => {
let upfetch = up(fetch, () => ({
serializeParams(params) {
expectTypeOf(params).toEqualTypeOf<Record<string, any>>()
return fallbackOptions.serializeParams(params)
},
}))
await upfetch('', {
serializeParams(params) {
expectTypeOf(params).toEqualTypeOf<Record<string, any>>()
return fallbackOptions.serializeParams(params)
},
})
})
test('The defaultSerializer of body should expect 1 arg only (the body)', async () => {
let upfetch = up(fetch, () => ({
serializeBody(body) {
expectTypeOf(body).toEqualTypeOf<
BodyInit | JsonifiableObject | JsonifiableArray
>()
return fallbackOptions.serializeBody(body)
},
}))
await upfetch('', {
serializeBody(body) {
expectTypeOf(body).toEqualTypeOf<
BodyInit | JsonifiableObject | JsonifiableArray
>()
return fallbackOptions.serializeBody(body)
},
})
})
test('callback types', async () => {
let fetcher = (
input: Parameters<typeof fetch>[0],
init: Parameters<typeof fetch>[1] & { test?: number },
) => fetch(input, init)
let upfetch = up(fetcher, () => ({
onRequest(options) {
expectTypeOf(options.test).toEqualTypeOf<number | undefined>()
expectTypeOf(options).toEqualTypeOf<ResolvedOptions<typeof fetcher>>()
},
onError(error, options) {
expectTypeOf(options.test).toEqualTypeOf<number | undefined>()
expectTypeOf(error).toEqualTypeOf<any>()
expectTypeOf(options).toEqualTypeOf<ResolvedOptions<typeof fetcher>>()
},
onSuccess(data, options) {
expectTypeOf(options.test).toEqualTypeOf<number | undefined>()
expectTypeOf(data).toEqualTypeOf<any>()
expectTypeOf(options).toEqualTypeOf<ResolvedOptions<typeof fetcher>>()
},
parseRejected(res, options) {
expectTypeOf(options.test).toEqualTypeOf<number | undefined>()
expectTypeOf(res).toEqualTypeOf<Response>()
expectTypeOf(options).toEqualTypeOf<ResolvedOptions<typeof fetcher>>()
return Promise.resolve(true)
},
parseResponse(res, options) {
expectTypeOf(options.test).toEqualTypeOf<number | undefined>()
expectTypeOf(res).toEqualTypeOf<Response>()
expectTypeOf(options).toEqualTypeOf<ResolvedOptions<typeof fetcher>>()
return Promise.resolve(1)
},
parseResponseError(res, options) {
expectTypeOf(options.test).toEqualTypeOf<number | undefined>()
expectTypeOf(res).toEqualTypeOf<Response>()
expectTypeOf(options).toEqualTypeOf<ResolvedOptions<typeof fetcher>>()
return Promise.resolve(true)
},
serializeParams(params) {
expectTypeOf(params).toEqualTypeOf<Record<string, any>>()
return ''
},
serializeBody(body) {
expectTypeOf(body).toEqualTypeOf<
BodyInit | JsonifiableObject | JsonifiableArray
>()
return ''
},
}))
await upfetch('', {
schema: v.pipe(
v.number(),
v.transform((n) => String(n)),
),
parseResponse(res, options) {
expectTypeOf(options.test).toEqualTypeOf<number | undefined>()
expectTypeOf(res).toEqualTypeOf<Response>()
expectTypeOf(options).toEqualTypeOf<ResolvedOptions<typeof fetcher>>()
return Promise.resolve(1)
},
parseResponseError(res, options) {
expectTypeOf(options.test).toEqualTypeOf<number | undefined>()
expectTypeOf(res).toEqualTypeOf<Response>()
expectTypeOf(options).toEqualTypeOf<ResolvedOptions<typeof fetcher>>()
return Promise.resolve(true)
},
serializeParams(params) {
expectTypeOf(params).toEqualTypeOf<Record<string, any>>()
return ''
},
serializeBody(body) {
expectTypeOf(body).toEqualTypeOf<
BodyInit | JsonifiableObject | JsonifiableArray
>()
return ''
},
})
})
test('base fetch type should be extended', () => {
type CustomFetchType = (
input: RequestInfo | URL,
init?: RequestInit & { additionalOption: string },
) => Promise<Response>
let fetchFn: CustomFetchType = (() => {}) as any
let upfetch = up(fetchFn, () => ({
additionalOption: '1',
}))
upfetch('', { additionalOption: '' })
// @ts-expect-error invalid type
upfetch('', { additionalOption: 1 })
})
describe('up should accept a fetcher with', () => {
test('narrower input', () => {
type FetchFn = (
input: string,
options: Parameters<typeof fetch>[1],
) => Promise<Response>
let fetchFn: FetchFn = (() => {}) as any
let upfetch = up(fetchFn)
upfetch('')
// @ts-expect-error accept string only
upfetch(new URL(''))
})
test('wider input', () => {
type FetchFn = (
input: string | URL | Request | number,
options: Parameters<typeof fetch>[1],
) => Promise<Response>
let fetchFn: FetchFn = (() => {}) as any
let upfetch = up(fetchFn)
upfetch('')
upfetch(new Request(''))
upfetch(new URL(''))
upfetch(1)
})
test('narrower options', () => {
type FetchFn = (
input: Parameters<typeof fetch>[0],
options: Omit<NonNullable<Parameters<typeof fetch>[1]>, 'cache'>,
) => Promise<Response>
let fetchFn: FetchFn = (() => {}) as any
let upfetch = up(fetchFn)
upfetch('', { keepalive: true })
// @ts-expect-error cache is not an option
upfetch('', { cache: 'no-store' })
})
test('wider options', () => {
type FetchFn = (
input: Parameters<typeof fetch>[0],
options: Parameters<typeof fetch>[1] & { newOption: string },
) => Promise<Response>
let fetchFn: FetchFn = (() => {}) as any
let upfetch = up(fetchFn)
upfetch('', { baseUrl: '', keepalive: true, newOption: '' })
// @ts-expect-error newOption is required
upfetch('', { baseUrl: '', keepalive: true })
})
test('narrower args', () => {
type FetchFn = (input: Parameters<typeof fetch>[0]) => Promise<Response>
let fetchFn: FetchFn = (() => {}) as any
fetchFn('')
// @ts-expect-error accept one arg only
fetchFn('', {})
})
test('wider args', () => {
type FetchFn = (
input: Parameters<typeof fetch>[0],
options: Parameters<typeof fetch>[1],
ctx: Record<string, string>,
) => Promise<Response>
let fetchFn: FetchFn = (() => {}) as any
let upfetch = up(fetchFn)
upfetch('', {}, {})
upfetch('', {})
// @ts-expect-error invalid 3rd arg
upfetch('', {}, '')
// @ts-expect-error 3 args max
upfetch('', {}, {}, '')
})
})