diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/index.test.ts | 391 | ||||
| -rw-r--r-- | src/index.ts | 25 |
2 files changed, 17 insertions, 399 deletions
diff --git a/src/index.test.ts b/src/index.test.ts deleted file mode 100644 index 8d73700..0000000 --- a/src/index.test.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { Factory, Response } from './' - -describe('HTTP Client', () => { - beforeEach(() => { - // @ts-expect-error: TypeScript complaints because 'wx' is not defined on - // the global object in Node.js. We're here to mock the global 'wx' object - // for testing purposes. - global.wx = { - request: jest.fn(), - uploadFile: jest.fn() - } - }) - - afterEach(() => { - // @ts-expect-error: Clean up the mocked global 'wx' object - delete global.wx - }) - - describe('Response', () => { - it.each([[200], [400]])( - 'retrieves the status code', - (statusCode: number) => { - const response = new Response({ - data: {}, - statusCode, - header: {}, - cookies: [] - } as unknown as WechatMiniprogram.RequestSuccessCallbackResult) - expect(response.status()).toBe(statusCode) - } - ) - - it.each([ - [200, true], - [201, false], - [299, false], - [300, false], - [304, false], - [400, false], - [500, false] - ])('should correctly determine if response is OK', (statusCode, ok) => { - const response = new Response({ - data: '', - statusCode, - header: {}, - cookies: [] - } as unknown as WechatMiniprogram.RequestSuccessCallbackResult) - expect(response.ok()).toBe(ok) - }) - - it.each([ - [200, true], - [201, true], - [299, true], - [300, false], - [304, false], - [400, false], - [500, false] - ])( - 'should correctly determine if response is successful', - (statusCode, successful) => { - const response = new Response({ - data: '', - statusCode, - header: {}, - cookies: [] - } as unknown as WechatMiniprogram.RequestSuccessCallbackResult) - expect(response.successful()).toBe(successful) - } - ) - - it.each([ - [200, false], - [201, false], - [300, false], - [304, false], - [400, true], - [401, true], - [402, true], - [499, true], - [500, false] - ])( - 'should correctly determine if response is a client error', - (statusCode, clientError) => { - const response = new Response({ - data: '', - statusCode, - header: {}, - cookies: [] - } as unknown as WechatMiniprogram.RequestSuccessCallbackResult) - expect(response.clientError()).toBe(clientError) - } - ) - - it.each([ - [200, false], - [201, false], - [300, false], - [304, false], - [400, false], - [500, true], - [503, true] - ])( - 'should correctly determine if response is a server error', - (statusCode, serverError) => { - const response = new Response({ - data: '', - statusCode, - header: {}, - cookies: [] - } as unknown as WechatMiniprogram.RequestSuccessCallbackResult) - expect(response.serverError()).toBe(serverError) - } - ) - - it.each([ - [{ foo: 'bar' }, { foo: 'bar' }], - ['foo', 'foo'] - ])('retrieves the data', (data, expected) => { - const response = new Response({ - data, - statusCode: 200, - header: {}, - cookies: [] - } as unknown as WechatMiniprogram.RequestSuccessCallbackResult) - expect(response.data()).toStrictEqual(expected) - }) - - it.each([ - ['{"foo":"bar"}', { foo: 'bar' }], - [{ foo: 'bar' }, { foo: 'bar' }] - ])('decodes the data as JSON string', (data, expected) => { - const response = new Response({ - data, - statusCode: 200, - header: {}, - cookies: [] - } as unknown as WechatMiniprogram.RequestSuccessCallbackResult) - expect(response.json()).toStrictEqual(expected) - }) - - it('returns an empty object when it fails to decode the data as JSON string', () => { - const response = new Response({ - data: '-', - statusCode: 200, - header: {}, - cookies: [] - } as unknown as WechatMiniprogram.RequestSuccessCallbackResult) - expect(response.json()).toStrictEqual({}) - }) - - it('retrieves the raw response', () => { - const rawResponse = { - data: '', - statusCode: 200, - header: {}, - cookies: [] - } as unknown as WechatMiniprogram.RequestSuccessCallbackResult - const response = new Response(rawResponse) - expect(response.raw()).toStrictEqual(rawResponse) - }) - }) - - describe('PendingRequest', () => { - it.each([ - ['get', 'GET'], - ['post', 'POST'], - ['put', 'PUT'], - ['delete', 'DELETE'] - ] as const)('sends HTTP request', (method, httpMethod: string) => { - const factory = new Factory() - factory.new()[method]('http://example.com') - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ - url: 'http://example.com', - method: httpMethod - }) - ) - }) - - it.each([ - [{ foo: 'bar' }, '?foo=bar'], - [{ foo: 'bar', baz: 'qux' }, '?foo=bar&baz=qux'], - [{}, ''] - ])('sends HTTP request with query string', (query, expected) => { - const factory = new Factory() - factory.new().get('http://example.com', query) - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ - url: `http://example.com${expected}`, - method: 'GET', - data: {} - }) - ) - }) - - it('sends HTTP request with body', () => { - const factory = new Factory() - factory.new().post('http://example.com', { foo: 'bar' }) - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ - url: 'http://example.com', - method: 'POST', - data: { foo: 'bar' }, - dataType: 'json' - }) - ) - }) - - it('sends a POST request with query string', () => { - const factory = new Factory() - factory - .new() - .withQuery({ greeting: 'world' }) - .post('http://example.com', { foo: 'bar' }) - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ - url: 'http://example.com?greeting=world', - method: 'POST', - data: { foo: 'bar' } - }) - ) - }) - - it('uploads a file with POST request', () => { - const factory = new Factory() - factory.new().upload({ - url: 'http://example.com', - filePath: '/tmp/foo.jpg', - name: 'file' - }) - expect(wx.uploadFile).toHaveBeenCalledWith( - expect.objectContaining({ - url: 'http://example.com', - filePath: '/tmp/foo.jpg', - name: 'file', - header: expect.objectContaining({ - 'content-type': 'multipart/form-data' - }) - }) - ) - }) - - it('can customize HTTP header', () => { - const factory = new Factory() - factory.new().withHeader('X-Foo', 'bar').get('http://example.com') - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ - header: expect.objectContaining({ - 'X-Foo': 'bar' - }) - }) - ) - }) - - it('can retrieve the request task', () => { - ;(wx.request as jest.Mock).mockImplementationOnce(() => ({ - abort: jest.fn() - })) - const factory = new Factory() - const { task } = factory.new().get('http://example.com') - expect(task.abort).toBeDefined() - }) - - it('can retrieve the HTTP response', async () => { - ;(wx.request as jest.Mock).mockImplementationOnce(({ success }) => { - success({ - data: {}, - statusCode: 200, - header: {}, - cookies: [] - }) - return { abort: jest.fn() } - }) - const factory = new Factory() - const response = await factory.new().get('http://example.com') - expect(response).toBeInstanceOf(Response) - }) - - it.each([ - ['http://example.com', '/', 'http://example.com'], - ['http://example.com/', '/', 'http://example.com'], - ['http://example.com/', '', 'http://example.com'], - ['http://example.com', '/api', 'http://example.com/api'], - ['http://example.com', 'http://other.com', 'http://other.com'], - ['http://example.com', 'https://other.com', 'https://other.com'], - ['http://example.com', 'http://other.com/api', 'http://other.com/api'] - ])('can customize base url', (baseUrl, path, url) => { - const factory = new Factory() - factory.new().baseUrl(baseUrl).get(path) - expect(wx.request).toHaveBeenCalledWith(expect.objectContaining({ url })) - }) - - test('the request builder is immutable', () => { - const factory = new Factory() - const request = factory.new() - request.withHeader('X-Foo', 'bar') - request.get('/') - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ - header: expect.not.objectContaining({ - 'X-Foo': 'bar' - }) - }) - ) - }) - - it('sends HTTP request with HTML forms', () => { - const factory = new Factory() - factory.new().asForm().post('http://example.com', { foo: 'bar' }) - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ - data: { foo: 'bar' }, - header: expect.objectContaining({ - 'content-type': 'application/x-www-form-urlencoded' - }) - }) - ) - }) - - describe('middleware', () => { - it('should be applied', () => { - const middleware = jest.fn((request, next) => next(request)) - const factory = new Factory() - factory.new().use(middleware).get('http://example.com') - expect(middleware).toHaveBeenCalled() - }) - - it('can modify HTTP request', () => { - const middleware = (request: any, next: any) => - next(request.withHeader('X-Foo', 'bar')) - const factory = new Factory() - factory.new().use(middleware).get('http://example.com') - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ - header: expect.objectContaining({ 'X-Foo': 'bar' }) - }) - ) - }) - - it('can retrieve HTTP response', () => { - ;(wx.request as jest.Mock).mockImplementationOnce(({ success }) => { - success({ - data: {}, - statusCode: 200, - header: {}, - cookies: [] - }) - return { abort: jest.fn() } - }) - const middleware = (request: any, next: any) => { - return next(request.withHeader('X-Foo', 'bar')).then( - (response: any) => { - expect(response).toBeInstanceOf(Response) - } - ) - } - const factory = new Factory() - factory.new().use(middleware).get('http://example.com') - }) - }) - }) - - describe('Factory', () => { - it('sets global base url', () => { - const factory = new Factory() - factory.baseUrl('http://example.com') - factory.new().get('/') - factory.new().get('/api') - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ url: 'http://example.com' }) - ) - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ url: 'http://example.com/api' }) - ) - }) - - it('crafts a JSON request by default', () => { - const factory = new Factory() - factory.baseUrl('http://example.com') - factory.new().get('/') - expect(wx.request).toHaveBeenCalledWith( - expect.objectContaining({ - header: expect.objectContaining({ - 'content-type': 'application/json' - }) - }) - ) - }) - }) -}) diff --git a/src/index.ts b/src/index.ts index 04a1870..de0ad68 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,9 @@ type QueryString = Record<string, string> type HttpMethod = WechatMiniprogram.RequestOption['method'] type RequestTask = WechatMiniprogram.RequestTask | WechatMiniprogram.UploadTask type RequestData = Record<string, unknown> -type ResponseData = WechatMiniprogram.RequestSuccessCallbackResult['data'] +type ResponseData = + | WechatMiniprogram.RequestSuccessCallbackResult['data'] + | WechatMiniprogram.UploadFileSuccessCallbackResult['data'] type MiddlewareNext<T extends ResponseData> = ( request: PendingRequest ) => RequestPromise<Response<SuccessCallbackResult<T>, T>> @@ -87,7 +89,7 @@ class PendingRequest { #data: RequestData = {} - #middlewares: Middleware<any>[] = [] + #middlewares: Middleware<ResponseData>[] = [] method(method: HttpMethod) { const request = this.#clone() @@ -192,7 +194,12 @@ class PendingRequest { return this.#throughMiddlewares(request)( this.#dispatchUpload(option.name, option.filePath) - ) + ) as RequestPromise< + Response< + WechatMiniprogram.UploadFileSuccessCallbackResult, + WechatMiniprogram.UploadFileSuccessCallbackResult['data'] + > + > } #dispatchUpload(name: string, filePath: string) { @@ -232,10 +239,12 @@ class PendingRequest { } } - #send<T extends ResponseData>(): RequestPromise< - Response<SuccessCallbackResult<T>, T> - > { - return this.#throughMiddlewares<T>(this)(this.#dispatchRequest<T>) + #send<T extends ResponseData>() { + return this.#throughMiddlewares<T>(this)( + this.#dispatchRequest<T> + ) as RequestPromise< + Response<WechatMiniprogram.RequestSuccessCallbackResult<T>, T> + > } #dispatchRequest<T extends ResponseData>(request: PendingRequest) { @@ -274,7 +283,7 @@ class PendingRequest { return (handler: MiddlewareNext<T>) => this.#middlewares.reduceRight( (next: MiddlewareNext<T>, middleware) => (request: PendingRequest) => - middleware(request, next), + (middleware as Middleware<T>)(request, next), handler )(request) } |
