From 4c75fa3d32f57d7a8730a6613eb3378aedaba07c Mon Sep 17 00:00:00 2001 From: zjx0905 <954270063@qq.com> Date: Sun, 9 Apr 2023 15:20:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E5=BB=BA=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + scripts/test.utils.ts | 47 +++++----- src/adapter.ts | 15 ++- src/axios.ts | 2 +- src/core/Axios.ts | 44 +++++---- src/core/AxiosDomain.ts | 17 ++-- src/core/InterceptorManager.ts | 16 ++-- src/core/createError.ts | 2 +- src/core/dispatchRequest.ts | 31 ++++--- src/core/generateType.ts | 4 +- src/core/mergeConfig.ts | 2 +- src/core/request.ts | 16 ++-- src/core/transformData.ts | 35 +++---- src/core/transformURL.ts | 2 +- test/adapter.platform.test.ts | 2 +- test/adapter.test.ts | 15 ++- test/axios.api.test.ts | 133 ++------------------------- test/axios.instance.test.ts | 125 +++++++++++++++++++++++++ test/axios.test.ts | 2 +- test/core/Axios.test.ts | 124 +++++++++++++++++++++++-- test/core/AxiosDomain.test.ts | 62 +++++++++++-- test/core/InterceptorManager.test.ts | 2 +- test/core/cancel.test.ts | 7 +- test/core/createError.test.ts | 2 +- test/core/dispatchRequest.test.ts | 8 ++ test/core/flattenHeaders.test.ts | 4 +- test/core/generateType.test.ts | 4 +- test/core/mergeConfig.test.ts | 6 +- test/core/request.test.ts | 107 +++++++++++++++------ test/core/transformData.test.ts | 2 +- test/core/transformURL.test.ts | 2 +- tsconfig.json | 5 +- vitest.config.ts | 3 + 33 files changed, 544 insertions(+), 305 deletions(-) create mode 100644 test/axios.instance.test.ts create mode 100644 test/core/dispatchRequest.test.ts diff --git a/package.json b/package.json index c5271bb..069733f 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "cz": "czg", "build": "esno scripts/build.ts", "build:asset": "esno scripts/build-asset.ts", + "watch": "pnpm build -a -w", "release": "esno scripts/release.ts", "publish:ci": "esno scripts/publish.ts", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", diff --git a/scripts/test.utils.ts b/scripts/test.utils.ts index 7c1161d..0c2e0c9 100644 --- a/scripts/test.utils.ts +++ b/scripts/test.utils.ts @@ -28,7 +28,7 @@ export function noop() { return; } -export function mockResponseBase( +export function mockResponse( status: number, statusText: string, headers: AnyObject, @@ -42,17 +42,6 @@ export function mockResponseBase( }; } -export function mockResponse(headers: AnyObject = {}, data: AnyObject = {}) { - return mockResponseBase(200, 'OK', headers, data); -} - -export function mockResponseError( - headers: AnyObject = {}, - data: AnyObject = {}, -) { - return mockResponseBase(400, 'FAIL', headers, data); -} - export interface MockAdapterOptions { headers?: AnyObject; data?: AnyObject; @@ -68,21 +57,33 @@ export function mockAdapterBase( const { headers = {}, data = {}, delay = 0, before, after } = options; return (config: AxiosAdapterRequestConfig) => { + let canceled = false; + before?.(config); + setTimeout(() => { - switch (type) { - case 'success': - config.success(mockResponse(headers, data)); - break; - case 'error': - config.success(mockResponseError(headers, data)); - break; - case 'fail': - config.fail(mockResponseError(headers)); - break; + if (!canceled) { + switch (type) { + case 'success': + config.success(mockResponse(200, 'OK', headers, data)); + break; + case 'error': + config.success(mockResponse(500, 'ERROR', headers, data)); + break; + case 'fail': + config.fail(mockResponse(400, 'FAIL', headers, data)); + break; + } + + after?.(); } - after?.(); }, delay); + + return { + abort() { + canceled = true; + }, + }; }; } diff --git a/src/adapter.ts b/src/adapter.ts index aae32b8..df5fc9a 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -9,7 +9,6 @@ import { AxiosProgressCallback, AxiosRequestFormData, AxiosRequestHeaders, - AxiosResponse, } from './core/Axios'; export type AxiosAdapterRequestType = 'request' | 'download' | 'upload'; @@ -24,7 +23,7 @@ export type AxiosAdapterRequestMethod = | 'TRACE' | 'CONNECT'; -export interface AxiosAdapterResponse extends AnyObject { +export interface AxiosAdapterResponse extends AnyObject { /** * 状态码 */ @@ -40,7 +39,7 @@ export interface AxiosAdapterResponse extends AnyObject { /** * 响应数据 */ - data: TData; + data: string | ArrayBuffer | AnyObject; } export interface AxiosAdapterResponseError extends AnyObject { @@ -107,8 +106,8 @@ export interface AxiosAdapterRequestConfig extends AnyObject { export interface AxiosAdapterBaseOptions extends AxiosAdapterRequestConfig { header?: AxiosRequestHeaders; - success(response: unknown): void; - fail(error: unknown): void; + success(response: AxiosAdapterResponse): void; + fail(error: AxiosAdapterResponseError): void; } export interface AxiosAdapterUploadOptions @@ -239,10 +238,10 @@ export function createAdapter(platform: AxiosPlatform): AxiosAdapter { download: AxiosAdapterDownload, baseOptions: AxiosAdapterBaseOptions, ): AxiosAdapterTask { - const options = { + const options: AxiosAdapterDownloadOptions = { ...baseOptions, filePath: baseOptions.params?.filePath, - success(response: AnyObject): void { + success(response): void { injectDownloadData(response); baseOptions.success(response); }, @@ -275,7 +274,7 @@ export function createAdapter(platform: AxiosPlatform): AxiosAdapter { return { ...config, header: config.headers, - success(response: AxiosResponse): void { + success(response): void { transformResult(response); config.success(response); }, diff --git a/src/axios.ts b/src/axios.ts index 365f6ce..8edbf22 100644 --- a/src/axios.ts +++ b/src/axios.ts @@ -61,7 +61,7 @@ function createInstance(defaults: AxiosRequestConfig) { Object.assign(instance, context, { // instance.fork 内部调用了 context 的私有方法 - // 所以直接调用 instance.fork 会导致程序会抛出无法访问私有方法的异常 + // 所以直接调用 instance.fork 会导致程序抛出无法访问私有方法的异常 // instance.fork 调用时 this 重新指向 context,解决此问题 fork: context.fork.bind(context), }); diff --git a/src/core/Axios.ts b/src/core/Axios.ts index 65bb6e4..dca506e 100644 --- a/src/core/Axios.ts +++ b/src/core/Axios.ts @@ -11,7 +11,7 @@ import { AxiosAdapterResponseError, } from '../adapter'; import { CancelToken } from './cancel'; -import dispatchRequest from './dispatchRequest'; +import { dispatchRequest } from './dispatchRequest'; import InterceptorManager from './InterceptorManager'; import { AxiosTransformer } from './transformData'; import AxiosDomain from './AxiosDomain'; @@ -79,6 +79,13 @@ export interface AxiosRequestFormData extends AnyObject { export type AxiosRequestData = AnyObject | AxiosRequestFormData; +export type AxiosResponseData = + | undefined + | number + | string + | ArrayBuffer + | AnyObject; + export interface AxiosProgressEvent { progress: number; totalBytesSent: number; @@ -90,9 +97,8 @@ export interface AxiosProgressCallback { } export interface AxiosRequestConfig - extends Omit< - Partial, - 'type' | 'method' | 'success' | 'fail' + extends Partial< + Omit > { /** * 请求适配器 @@ -102,6 +108,10 @@ export interface AxiosRequestConfig * 基础路径 */ baseURL?: string; + /** + * 请求的 URL + */ + url?: string; /** * 请求参数 */ @@ -133,11 +143,11 @@ export interface AxiosRequestConfig /** * 转换请求数据 */ - transformRequest?: AxiosTransformer | AxiosTransformer[]; + transformRequest?: AxiosTransformer; /** * 转换响应数据 */ - transformResponse?: AxiosTransformer | AxiosTransformer[]; + transformResponse?: AxiosTransformer; /** * 异常处理 */ @@ -160,8 +170,9 @@ export interface AxiosRequestConfig validateStatus?: (status: number) => boolean; } -export interface AxiosResponse - extends AxiosAdapterResponse { +export interface AxiosResponse< + TData extends AxiosResponseData = AxiosResponseData, +> extends Omit { /** * 请求配置 */ @@ -170,6 +181,10 @@ export interface AxiosResponse * 请求任务 */ request?: AxiosAdapterTask; + /** + * 响应数据 + */ + data: TData; } export interface AxiosResponseError extends AxiosAdapterResponseError { @@ -233,22 +248,17 @@ export default class Axios extends AxiosDomain { ); } - #processRequest(config: AxiosRequestConfig) { + #processRequest(config: AxiosRequestConfig) { const { request, response } = this.interceptors; let promiseRequest = Promise.resolve(config); request.forEach(({ resolved, rejected }) => { - promiseRequest = promiseRequest.then( - resolved, - rejected, - ) as Promise; + promiseRequest = promiseRequest.then(resolved, rejected); }, true); let promiseResponse = promiseRequest.then(dispatchRequest); response.forEach(({ resolved, rejected }) => { - promiseResponse = promiseResponse.then(resolved, rejected) as Promise< - AxiosResponse - >; + promiseResponse = promiseResponse.then(resolved, rejected); }); - return promiseResponse as Promise>; + return promiseResponse; } } diff --git a/src/core/AxiosDomain.ts b/src/core/AxiosDomain.ts index 39a77e5..b07de45 100644 --- a/src/core/AxiosDomain.ts +++ b/src/core/AxiosDomain.ts @@ -1,16 +1,21 @@ import { isString, isUndefined } from '../helpers/isTypes'; import { deepMerge } from '../helpers/deepMerge'; import { mergeConfig } from './mergeConfig'; -import { AxiosRequestConfig, AxiosRequestData, AxiosResponse } from './Axios'; +import { + AxiosRequestConfig, + AxiosRequestData, + AxiosResponse, + AxiosResponseData, +} from './Axios'; export interface AxiosDomainRequest { - ( + ( /** * 请求配置 */ config: AxiosRequestConfig, ): Promise>; - ( + ( /** * 请求地址 */ @@ -23,7 +28,7 @@ export interface AxiosDomainRequest { } export interface AxiosDomainAsRequest { - ( + ( /** * 请求地址 */ @@ -36,7 +41,7 @@ export interface AxiosDomainAsRequest { } export interface AxiosDomainAsRequestWithParams { - ( + ( /** * 请求地址 */ @@ -53,7 +58,7 @@ export interface AxiosDomainAsRequestWithParams { } export interface AxiosDomainAsRequestWithData { - ( + ( /** * 请求地址 */ diff --git a/src/core/InterceptorManager.ts b/src/core/InterceptorManager.ts index 3c3ce65..56c9785 100644 --- a/src/core/InterceptorManager.ts +++ b/src/core/InterceptorManager.ts @@ -2,17 +2,17 @@ export interface InterceptorResolved { (value: T): T | Promise; } -export interface InterceptorRejected { - (error: unknown): unknown | Promise; +export interface InterceptorRejected { + (error: unknown): T | Promise; } export interface Interceptor { resolved: InterceptorResolved; - rejected?: InterceptorRejected; + rejected?: InterceptorRejected; } -export interface InterceptorExecutor { - (interceptor: Interceptor): void; +export interface InterceptorExecutor { + (interceptor: Interceptor): void; } export default class InterceptorManager { @@ -22,7 +22,7 @@ export default class InterceptorManager { use( resolved: InterceptorResolved, - rejected?: InterceptorRejected, + rejected?: InterceptorRejected, ): number { this.#interceptors[++this.#id] = { resolved, @@ -36,8 +36,8 @@ export default class InterceptorManager { delete this.#interceptors[id]; } - forEach(executor: InterceptorExecutor, reverse?: boolean): void { - let interceptors: Interceptor[] = Object.values(this.#interceptors); + forEach(executor: InterceptorExecutor, reverse?: boolean): void { + let interceptors: Interceptor[] = Object.values(this.#interceptors); if (reverse) interceptors = interceptors.reverse(); interceptors.forEach(executor); } diff --git a/src/core/createError.ts b/src/core/createError.ts index 235944c..09d1fe7 100644 --- a/src/core/createError.ts +++ b/src/core/createError.ts @@ -30,7 +30,7 @@ export function createError( config: AxiosRequestConfig, response: AxiosErrorResponse, request: AxiosAdapterTask, -): AxiosError { +) { const axiosError = new AxiosError(message, config, response, request); cleanStack(axiosError); return axiosError; diff --git a/src/core/dispatchRequest.ts b/src/core/dispatchRequest.ts index 89b5912..d3eb913 100644 --- a/src/core/dispatchRequest.ts +++ b/src/core/dispatchRequest.ts @@ -14,29 +14,30 @@ function throwIfCancellationRequested(config: AxiosRequestConfig) { } } -export default function dispatchRequest( - config: AxiosRequestConfig, -): Promise { +export function dispatchRequest(config: AxiosRequestConfig) { throwIfCancellationRequested(config); + const { transformRequest, transformResponse } = config; config.url = transformURL(config); config.method = config.method ?? 'get'; config.headers = flattenHeaders(config); - transform(config, transformRequest); + transformer(config, transformRequest); - function onSuccess(response: AxiosResponse) { + function onSuccess(response: AxiosResponse) { throwIfCancellationRequested(config); - transform(response, transformResponse); + + transformer(response, transformResponse); return response; } function onError(reason: unknown) { if (!isCancel(reason)) { throwIfCancellationRequested(config); + if (isAxiosError(reason)) { - transform(reason.response as AxiosResponse, transformResponse); + transformer(reason.response as AxiosResponse, transformResponse); } } @@ -47,16 +48,16 @@ export default function dispatchRequest( return Promise.reject(reason); } - function transform( - target: AxiosRequestConfig | AxiosResponse, - transformer?: AxiosTransformer | AxiosTransformer[], + function transformer( + targetObject: { data?: TData; headers?: AnyObject }, + transformer?: AxiosTransformer, ) { - target.data = transformData( - target.data as AnyObject, - target.headers, + targetObject.data = transformData( + targetObject.data, + targetObject.headers, transformer, - ) as TData; + ); } - return request(config).then(onSuccess).catch(onError); + return request(config).then(onSuccess).catch(onError); } diff --git a/src/core/generateType.ts b/src/core/generateType.ts index a2a5f1a..bd7ab5b 100644 --- a/src/core/generateType.ts +++ b/src/core/generateType.ts @@ -4,9 +4,7 @@ import { AxiosRequestConfig } from './Axios'; const postRE = /^POST$/i; const getRE = /^GET$/i; -export function generateType( - config: AxiosRequestConfig, -): AxiosAdapterRequestType { +export function generateType(config: AxiosRequestConfig) { let requestType: AxiosAdapterRequestType = 'request'; if (config.upload && postRE.test(config.method!)) { diff --git a/src/core/mergeConfig.ts b/src/core/mergeConfig.ts index 099ecb6..2b2d03d 100644 --- a/src/core/mergeConfig.ts +++ b/src/core/mergeConfig.ts @@ -17,7 +17,7 @@ const deepMergeConfigMap: Record = { export function mergeConfig( config1: AxiosRequestConfig = {}, config2: AxiosRequestConfig = {}, -): AxiosRequestConfig { +) { const config: AxiosRequestConfig = {}; // 所有已知键名 diff --git a/src/core/request.ts b/src/core/request.ts index 70682c1..9e68bde 100644 --- a/src/core/request.ts +++ b/src/core/request.ts @@ -10,6 +10,7 @@ import { AxiosProgressCallback, AxiosRequestConfig, AxiosResponse, + AxiosResponseData, AxiosResponseError, } from './Axios'; import { isCancelToken } from './cancel'; @@ -38,16 +39,14 @@ function tryToggleProgressUpdate( } } -export function request( - config: AxiosRequestConfig, -): Promise> { - return new Promise((resolve, reject) => { +export function request(config: AxiosRequestConfig) { + return new Promise((resolve, reject) => { assert(isFunction(config.adapter), 'adapter 不是一个 function'); assert(isString(config.url), 'url 不是一个 string'); const adapterConfig: AxiosAdapterRequestConfig = { ...config, - url: config.url, + url: config.url!, type: generateType(config), method: config.method!.toUpperCase() as AxiosAdapterRequestMethod, success, @@ -56,10 +55,11 @@ export function request( const adapterTask = config.adapter!(adapterConfig); - function success(_: AxiosAdapterResponse): void { - const response = _ as AxiosResponse; + function success(_: AxiosAdapterResponse): void { + const response = _ as AxiosResponse; response.config = config; response.request = adapterTask; + if (config.validateStatus?.(response.status) ?? true) { resolve(response); } else { @@ -72,7 +72,7 @@ export function request( responseError.isFail = true; responseError.config = config; responseError.request = adapterTask; - catchError(responseError.statusText, responseError); + catchError('request fail', responseError); } function catchError( diff --git a/src/core/transformData.ts b/src/core/transformData.ts index 11dc13f..ed677e2 100644 --- a/src/core/transformData.ts +++ b/src/core/transformData.ts @@ -1,35 +1,38 @@ -import { isArray, isUndefined } from '../helpers/isTypes'; -import { AxiosRequestData } from './Axios'; +import { isArray, isFunction } from '../helpers/isTypes'; -export interface AxiosTransformer { +export interface AxiosTransformCallback { ( /** * 数据 */ - data?: AxiosRequestData, + data?: TData, /** * 头信息 */ headers?: AnyObject, - ): AxiosRequestData; + ): TData | undefined; } -export function transformData( - data?: AxiosRequestData, +export type AxiosTransformer = + | AxiosTransformCallback + | AxiosTransformCallback[]; + +export function transformData( + data?: TData, headers?: AnyObject, - transforms?: AxiosTransformer | AxiosTransformer[], -): AxiosRequestData | undefined { - if (isUndefined(transforms)) { - return data; - } - + transforms?: AxiosTransformer, +) { if (!isArray(transforms)) { - transforms = [transforms]; + if (isFunction(transforms)) { + transforms = [transforms]; + } else { + transforms = []; + } } - transforms.forEach((transform: AxiosTransformer) => { + transforms.forEach((transform) => { data = transform(data, headers); }); - return data; + return data as TData; } diff --git a/src/core/transformURL.ts b/src/core/transformURL.ts index c96c872..1c77b9a 100644 --- a/src/core/transformURL.ts +++ b/src/core/transformURL.ts @@ -4,7 +4,7 @@ import { dynamicURL } from '../helpers/dynamicURL'; import { isAbsoluteURL } from '../helpers/isAbsoluteURL'; import { AxiosRequestConfig } from './Axios'; -export function transformURL(config: AxiosRequestConfig): string { +export function transformURL(config: AxiosRequestConfig) { let url = config.url ?? ''; if (!isAbsoluteURL(url)) url = combineURL(config.baseURL ?? '', url); diff --git a/test/adapter.platform.test.ts b/test/adapter.platform.test.ts index c07ac63..32879db 100644 --- a/test/adapter.platform.test.ts +++ b/test/adapter.platform.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest'; -import { getAdapterDefault } from 'src/adapter'; +import { getAdapterDefault } from '@/adapter'; const platforms = [ 'uni', diff --git a/test/adapter.test.ts b/test/adapter.test.ts index dc961e8..609e6e0 100644 --- a/test/adapter.test.ts +++ b/test/adapter.test.ts @@ -1,24 +1,29 @@ import { describe, test, expect, vi } from 'vitest'; -import { createAdapter } from 'src/adapter'; import { noop } from 'scripts/test.utils'; +import { AxiosPlatform, createAdapter } from '@/adapter'; describe('src/adapter.ts', () => { test('应该抛出异常', () => { expect(() => - createAdapter(undefined as any), + createAdapter(undefined as unknown as AxiosPlatform), ).toThrowErrorMatchingInlineSnapshot( '"[axios-miniprogram]: platform 不是一个 object"', ); - expect(() => createAdapter({} as any)).toThrowErrorMatchingInlineSnapshot( + expect(() => + createAdapter({} as unknown as AxiosPlatform), + ).toThrowErrorMatchingInlineSnapshot( '"[axios-miniprogram]: request 不是一个 function"', ); expect(() => - createAdapter({ request: vi.fn() } as any), + createAdapter({ request: vi.fn() } as unknown as AxiosPlatform), ).toThrowErrorMatchingInlineSnapshot( '"[axios-miniprogram]: upload 不是一个 function"', ); expect(() => - createAdapter({ request: vi.fn(), upload: vi.fn() } as any), + createAdapter({ + request: vi.fn(), + upload: vi.fn(), + } as unknown as AxiosPlatform), ).toThrowErrorMatchingInlineSnapshot( '"[axios-miniprogram]: download 不是一个 function"', ); diff --git a/test/axios.api.test.ts b/test/axios.api.test.ts index fba7fb6..c84fca0 100644 --- a/test/axios.api.test.ts +++ b/test/axios.api.test.ts @@ -1,136 +1,17 @@ -import { describe, test, expect, beforeAll, afterAll } from 'vitest'; -import { mockAdapter } from 'scripts/test.utils'; -import Axios from '../src/core/Axios'; -import { CancelToken, isCancel } from '../src/core/cancel'; -import { isAxiosError } from '../src/core/createError'; -import { createAdapter } from '../src/adapter'; -import defaults from '../src/defaults'; -import axios from '../src/axios'; +import { describe, test, expect } from 'vitest'; +import Axios from '@/core/Axios'; +import { CancelToken, isCancel } from '@/core/cancel'; +import { isAxiosError } from '@/core/createError'; +import { createAdapter } from '@/adapter'; +import axios from '@/axios'; describe('src/axios.ts', () => { - const data = { - result: null, - }; - - beforeAll(() => { - axios.defaults.baseURL = 'http://api.com'; - }); - - afterAll(() => { - axios.defaults.baseURL = undefined; - }); - test('应该有这些静态属性', () => { - expect(axios.defaults).toBe(defaults); expect(axios.Axios).toBe(Axios); expect(axios.CancelToken).toBe(CancelToken); + expect(axios.create).toBeTypeOf('function'); expect(axios.createAdapter).toBe(createAdapter); expect(axios.isCancel).toBe(isCancel); expect(axios.isAxiosError).toBe(isAxiosError); - - expect(axios.interceptors).toBeTypeOf('object'); - expect(axios.create).toBeTypeOf('function'); - expect(axios.request).toBeTypeOf('function'); - - expect(axios.getUri).toBeTypeOf('function'); - expect(axios.fork).toBeTypeOf('function'); - - [...Axios.as, ...Axios.asp, ...Axios.asd].forEach((k) => { - expect(axios[k]).toBeTypeOf('function'); - }); - }); - - test('应该可以发送普通别名请求', () => { - const c = { - adapter: mockAdapter({ - before: (config) => { - expect(config.url).toBe('http://api.com/test'); - }, - data, - }), - }; - - Axios.as.forEach((a) => { - axios[a]('test', c).then((res) => { - expect(res.data).toEqual(data); - }); - }); - }); - - test('应该可以发送带参数的别名请求', () => { - const p = { id: 1 }; - const c1 = { - adapter: mockAdapter({ - before: (config) => { - expect(config.url).toBe('http://api.com/test?id=1'); - expect(config.params).toEqual(p); - }, - data, - }), - }; - const c2 = { - adapter: mockAdapter({ - before: (config) => { - expect(config.url).toBe('http://api.com/test/1?id=1'); - expect(config.params).toEqual(p); - }, - data, - }), - }; - - Axios.asp.forEach((a) => { - axios[a]('test', p, c1).then((res) => { - expect(res.data).toEqual(data); - }); - axios[a]('test/:id', p, c2).then((res) => { - expect(res.data).toEqual(data); - }); - }); - }); - - test('应该可以发送带数据的别名请求', () => { - const d = { id: 1 }; - const c1 = { - adapter: mockAdapter({ - before: (config) => { - expect(config.url).toBe('http://api.com/test'); - expect(config.data).toEqual(d); - }, - data, - }), - }; - const c2 = { - adapter: mockAdapter({ - before: (config) => { - expect(config.url).toBe('http://api.com/test/1'); - expect(config.data).toEqual(d); - }, - data, - }), - }; - - Axios.asd.forEach((a) => { - axios[a]('test', d, c1).then((res) => { - expect(res.data).toEqual(data); - }); - axios[a]('test/:id', d, c2).then((res) => { - expect(res.data).toEqual(data); - }); - }); - }); - - test('应该可以获取 URI', () => { - expect( - axios.getUri({ - url: 'test', - }), - ).toBe('test'); - }); - - test('应该可以派生领域', () => { - const a = axios.fork({ - baseURL: 'test', - }); - expect(a.defaults.baseURL).toBe('http://api.com/test'); }); }); diff --git a/test/axios.instance.test.ts b/test/axios.instance.test.ts new file mode 100644 index 0000000..8cc3cfb --- /dev/null +++ b/test/axios.instance.test.ts @@ -0,0 +1,125 @@ +import { describe, test, expect, beforeAll, afterAll } from 'vitest'; +import { mockAdapter } from 'scripts/test.utils'; +import Axios from '@/core/Axios'; +import defaults from '@/defaults'; +import axios from '@/axios'; + +describe('src/axios.ts', () => { + const data = { + result: null, + }; + + beforeAll(() => { + axios.defaults.baseURL = 'http://api.com'; + }); + + afterAll(() => { + axios.defaults.baseURL = undefined; + }); + + test('应该有这些实例属性及方法', () => { + expect(axios.defaults).toBe(defaults); + expect(axios.interceptors).toBeTypeOf('object'); + expect(axios.getUri).toBeTypeOf('function'); + expect(axios.fork).toBeTypeOf('function'); + expect(axios.request).toBeTypeOf('function'); + + [...Axios.as, ...Axios.asp, ...Axios.asd].forEach((k) => { + expect(axios[k]).toBeTypeOf('function'); + }); + }); + + test('应该可以发送普通别名请求', () => { + const c = { + adapter: mockAdapter({ + before: (config) => { + expect(config.url).toBe('http://api.com/test'); + }, + data, + }), + }; + + Axios.as.forEach((a) => { + axios[a]('test', c).then((res) => { + expect(res.data).toEqual(data); + }); + }); + }); + + test('应该可以发送带参数的别名请求', () => { + const p = { id: 1 }; + const c1 = { + adapter: mockAdapter({ + before: (config) => { + expect(config.url).toBe('http://api.com/test?id=1'); + expect(config.params).toEqual(p); + }, + data, + }), + }; + const c2 = { + adapter: mockAdapter({ + before: (config) => { + expect(config.url).toBe('http://api.com/test/1?id=1'); + expect(config.params).toEqual(p); + }, + data, + }), + }; + + Axios.asp.forEach((a) => { + axios[a]('test', p, c1).then((res) => { + expect(res.data).toEqual(data); + }); + axios[a]('test/:id', p, c2).then((res) => { + expect(res.data).toEqual(data); + }); + }); + }); + + test('应该可以发送带数据的别名请求', () => { + const d = { id: 1 }; + const c1 = { + adapter: mockAdapter({ + before: (config) => { + expect(config.url).toBe('http://api.com/test'); + expect(config.data).toEqual(d); + }, + data, + }), + }; + const c2 = { + adapter: mockAdapter({ + before: (config) => { + expect(config.url).toBe('http://api.com/test/1'); + expect(config.data).toEqual(d); + }, + data, + }), + }; + + Axios.asd.forEach((a) => { + axios[a]('test', d, c1).then((res) => { + expect(res.data).toEqual(data); + }); + axios[a]('test/:id', d, c2).then((res) => { + expect(res.data).toEqual(data); + }); + }); + }); + + test('应该可以获取 URI', () => { + expect( + axios.getUri({ + url: 'test', + }), + ).toBe('test'); + }); + + test('应该可以派生领域', () => { + const a = axios.fork({ + baseURL: 'test', + }); + expect(a.defaults.baseURL).toBe('http://api.com/test'); + }); +}); diff --git a/test/axios.test.ts b/test/axios.test.ts index c9fe326..2e7e611 100644 --- a/test/axios.test.ts +++ b/test/axios.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect } from 'vitest'; -import axios from 'src/axios'; import { mockAdapter, mockAdapterError } from 'scripts/test.utils'; +import axios from '@/axios'; describe('src/axios.ts', () => { test('应该处理成功和失败', () => { diff --git a/test/core/Axios.test.ts b/test/core/Axios.test.ts index c7c1315..28bd2c9 100644 --- a/test/core/Axios.test.ts +++ b/test/core/Axios.test.ts @@ -1,27 +1,133 @@ import { describe, test, expect } from 'vitest'; -import Axios from 'src/core/Axios'; +import { mockAdapter } from 'scripts/test.utils'; +import Axios from '@/core/Axios'; +import AxiosDomain from '@/core/AxiosDomain'; describe('src/core/Axios.ts', () => { + const data = { + result: null, + }; + const axios = new Axios({ + baseURL: 'http://api.com', + }); + + test('应该继承自 AxiosDomain', () => { + expect(new Axios() instanceof AxiosDomain).toBeTruthy(); + }); + test('应该有这些静态属性', () => { expect(Axios.as).toEqual(['options', 'trace', 'connect']); expect(Axios.asp).toEqual(['head', 'get', 'delete']); expect(Axios.asd).toEqual(['post', 'put']); }); - test('应该有这些实例属性', () => { + test('应该有这些实例属性及方法', () => { const c = { baseURL: 'http://api.com', }; - const a = new Axios(c); - expect(a.defaults).toEqual(c); - expect(a.interceptors).toBeTypeOf('object'); - expect(a.request).toBeTypeOf('function'); - expect(a.getUri).toBeTypeOf('function'); - expect(a.fork).toBeTypeOf('function'); + expect(axios.defaults).toEqual(c); + expect(axios.interceptors).toBeTypeOf('object'); + expect(axios.request).toBeTypeOf('function'); + expect(axios.getUri).toBeTypeOf('function'); + expect(axios.fork).toBeTypeOf('function'); [...Axios.as, ...Axios.asp, ...Axios.asd].forEach((k) => { - expect(a[k]).toBeTypeOf('function'); + expect(axios[k]).toBeTypeOf('function'); }); }); + + test('应该可以发送普通别名请求', () => { + const c = { + adapter: mockAdapter({ + before: (config) => { + expect(config.url).toBe('http://api.com/test'); + }, + data, + }), + }; + + Axios.as.forEach((a) => { + axios[a]('test', c).then((res) => { + expect(res.data).toEqual(data); + }); + }); + }); + + test('应该可以发送带参数的别名请求', () => { + const p = { id: 1 }; + const c1 = { + adapter: mockAdapter({ + before: (config) => { + expect(config.url).toBe('http://api.com/test?id=1'); + expect(config.params).toEqual(p); + }, + data, + }), + }; + const c2 = { + adapter: mockAdapter({ + before: (config) => { + expect(config.url).toBe('http://api.com/test/1?id=1'); + expect(config.params).toEqual(p); + }, + data, + }), + }; + + Axios.asp.forEach((a) => { + axios[a]('test', p, c1).then((res) => { + expect(res.data).toEqual(data); + }); + axios[a]('test/:id', p, c2).then((res) => { + expect(res.data).toEqual(data); + }); + }); + }); + + test('应该可以发送带数据的别名请求', () => { + const d = { id: 1 }; + const c1 = { + adapter: mockAdapter({ + before: (config) => { + expect(config.url).toBe('http://api.com/test'); + expect(config.data).toEqual(d); + }, + data, + }), + }; + const c2 = { + adapter: mockAdapter({ + before: (config) => { + expect(config.url).toBe('http://api.com/test/1'); + expect(config.data).toEqual(d); + }, + data, + }), + }; + + Axios.asd.forEach((a) => { + axios[a]('test', d, c1).then((res) => { + expect(res.data).toEqual(data); + }); + axios[a]('test/:id', d, c2).then((res) => { + expect(res.data).toEqual(data); + }); + }); + }); + + test('应该可以获取 URI', () => { + expect( + axios.getUri({ + url: 'test', + }), + ).toBe('test'); + }); + + test('应该可以派生领域', () => { + const a = axios.fork({ + baseURL: 'test', + }); + expect(a.defaults.baseURL).toBe('http://api.com/test'); + }); }); diff --git a/test/core/AxiosDomain.test.ts b/test/core/AxiosDomain.test.ts index 4807c15..5643ea5 100644 --- a/test/core/AxiosDomain.test.ts +++ b/test/core/AxiosDomain.test.ts @@ -1,6 +1,7 @@ import { describe, test, expect, vi } from 'vitest'; -import AxiosDomain, { AxiosDomainRequest } from 'src/core/AxiosDomain'; -import { ignore } from 'src/helpers/ignore'; +import { ignore } from '@/helpers/ignore'; +import AxiosDomain from '@/core/AxiosDomain'; +import { AxiosResponse } from '@/core/Axios'; describe('src/core/AxiosDomain.ts', () => { test('应该有这些静态属性', () => { @@ -48,6 +49,45 @@ describe('src/core/AxiosDomain.ts', () => { }); test('应该可以调用这些方法', () => { + const cb = vi.fn(); + const d = { + baseURL: 'http://api.com', + }; + const u = 'test'; + const c = { + params: { + id: 1, + }, + data: { + id: 1, + }, + }; + const a = new AxiosDomain(d, async (config) => { + cb(); + + expect(config.baseURL).toBe(d.baseURL); + expect(config.url).toBe(u); + expect(config.params).toEqual(c.params); + expect(config.data).toEqual(c.data); + + return {} as AxiosResponse; + }); + + a.request(u, c); + + AxiosDomain.as.forEach((k) => a[k](u, c)); + AxiosDomain.asp.forEach((k) => a[k](u, c.params, ignore(c, 'params'))); + AxiosDomain.asd.forEach((k) => a[k](u, c.data, ignore(c, 'data'))); + + const l = + AxiosDomain.as.length + + AxiosDomain.asp.length + + AxiosDomain.asd.length + + 1; + expect(cb.mock.calls.length).toBe(l); + }); + + test('应该可以直接传入 config 调用这些方法', () => { const cb = vi.fn(); const d = { baseURL: 'http://api.com', @@ -61,14 +101,16 @@ describe('src/core/AxiosDomain.ts', () => { id: 1, }, }; - const a = new AxiosDomain(d, ((config) => { + const a = new AxiosDomain(d, async (config) => { cb(); expect(config.baseURL).toBe(d.baseURL); expect(config.url).toBe(c.url); expect(config.params).toEqual(c.params); expect(config.data).toEqual(c.data); - }) as AxiosDomainRequest); + + return {} as AxiosResponse; + }); a.request(c); @@ -107,7 +149,7 @@ describe('src/core/AxiosDomain.ts', () => { }, }; - const a = new AxiosDomain(d, ((config) => { + const a = new AxiosDomain(d, async (config) => { expect(config.params).toEqual({ v1: 1, v2: { @@ -116,7 +158,9 @@ describe('src/core/AxiosDomain.ts', () => { }, v3: 3, }); - }) as AxiosDomainRequest); + + return {} as AxiosResponse; + }); AxiosDomain.asp.forEach((k) => a[k]('test', p, c)); }); @@ -140,7 +184,7 @@ describe('src/core/AxiosDomain.ts', () => { }, }; - const a = new AxiosDomain(ds, ((config) => { + const a = new AxiosDomain(ds, async (config) => { expect(config.data).toEqual({ v1: 1, v2: { @@ -149,7 +193,9 @@ describe('src/core/AxiosDomain.ts', () => { }, v3: 3, }); - }) as AxiosDomainRequest); + + return {} as AxiosResponse; + }); AxiosDomain.asd.forEach((k) => a[k]('test', d, c)); }); diff --git a/test/core/InterceptorManager.test.ts b/test/core/InterceptorManager.test.ts index 0109140..bb9fb90 100644 --- a/test/core/InterceptorManager.test.ts +++ b/test/core/InterceptorManager.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect, vi } from 'vitest'; -import InterceptorManager from 'src/core/InterceptorManager'; +import InterceptorManager from '@/core/InterceptorManager'; describe('src/core/InterceptorManager.ts', () => { test('应该有这些实例属性', () => { diff --git a/test/core/cancel.test.ts b/test/core/cancel.test.ts index 73a5ce7..ed2d105 100644 --- a/test/core/cancel.test.ts +++ b/test/core/cancel.test.ts @@ -7,12 +7,7 @@ import { asyncTimeout, } from 'scripts/test.utils'; import axios from 'src/axios'; -import { - Cancel, - isCancel, - CancelToken, - isCancelToken, -} from '../../src/core/cancel'; +import { Cancel, isCancel, CancelToken, isCancelToken } from '@/core/cancel'; describe('src/helpers/cancel.ts', () => { test('应该支持空参数', () => { diff --git a/test/core/createError.test.ts b/test/core/createError.test.ts index 2b6c24c..de588c0 100644 --- a/test/core/createError.test.ts +++ b/test/core/createError.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect } from 'vitest'; import { checkStack } from 'scripts/test.utils'; -import { createError, isAxiosError } from 'src/core/createError'; +import { createError, isAxiosError } from '@/core/createError'; describe('src/core/createError.ts', () => { test('应该支持空参数', () => { diff --git a/test/core/dispatchRequest.test.ts b/test/core/dispatchRequest.test.ts new file mode 100644 index 0000000..d81a067 --- /dev/null +++ b/test/core/dispatchRequest.test.ts @@ -0,0 +1,8 @@ +import { describe, test, expect } from 'vitest'; +import { dispatchRequest } from 'src/core/dispatchRequest'; + +describe('src/core/dispatchRequest.ts', () => { + test('应该有这些实例属性', () => { + expect(dispatchRequest).toBeTypeOf('function'); + }); +}); diff --git a/test/core/flattenHeaders.test.ts b/test/core/flattenHeaders.test.ts index 23584b1..5c0d07d 100644 --- a/test/core/flattenHeaders.test.ts +++ b/test/core/flattenHeaders.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect } from 'vitest'; -import { flattenHeaders } from 'src/core/flattenHeaders'; -import Axios from 'src/core/Axios'; +import { flattenHeaders } from '@/core/flattenHeaders'; +import Axios from '@/core/Axios'; describe('src/core/flattenHeaders.ts', () => { const keys = [...Axios.as, ...Axios.asp, ...Axios.asd]; diff --git a/test/core/generateType.test.ts b/test/core/generateType.test.ts index 3081f89..1f9e818 100644 --- a/test/core/generateType.test.ts +++ b/test/core/generateType.test.ts @@ -1,6 +1,6 @@ import { describe, test, expect } from 'vitest'; -import { generateType } from 'src/core/generateType'; -import Axios from 'src/core/Axios'; +import { generateType } from '@/core/generateType'; +import Axios from '@/core/Axios'; describe('src/core/generateType.ts', () => { test('应该是一个 reuqest', () => { diff --git a/test/core/mergeConfig.test.ts b/test/core/mergeConfig.test.ts index 4ab01b3..2107fff 100644 --- a/test/core/mergeConfig.test.ts +++ b/test/core/mergeConfig.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect, vi } from 'vitest'; -import { ignore } from 'src/helpers/ignore'; -import { mergeConfig } from 'src/core/mergeConfig'; -import { CancelToken } from 'src/core/cancel'; +import { ignore } from '@/helpers/ignore'; +import { mergeConfig } from '@/core/mergeConfig'; +import { CancelToken } from '@/core/cancel'; describe('src/core/mergeConfig.ts', () => { test('应该支持空参数', () => { diff --git a/test/core/request.test.ts b/test/core/request.test.ts index a6fa76b..e39e581 100644 --- a/test/core/request.test.ts +++ b/test/core/request.test.ts @@ -1,10 +1,10 @@ import { describe, test, expect } from 'vitest'; -import { request } from 'src/core/request'; import { mockAdapter, mockAdapterError, mockAdapterFail, } from 'scripts/test.utils'; +import { request } from '@/core/request'; describe('src/core/request.ts', () => { test('应该抛出异常', () => { @@ -18,14 +18,25 @@ describe('src/core/request.ts', () => { ); }); - test('应该能够取到数据', async () => { - await expect( - request({ - adapter: mockAdapter(), - url: '/test', - method: 'get', - }), - ).resolves.toMatchInlineSnapshot(` + test('应该正确的响应请求', async () => { + const s = request({ + adapter: mockAdapter(), + url: '/test', + method: 'get', + }); + const e = request({ + adapter: mockAdapterError(), + url: '/test', + method: 'get', + validateStatus: () => false, + }); + const f = request({ + adapter: mockAdapterFail(), + url: '/test', + method: 'get', + }); + + await expect(s).resolves.toMatchInlineSnapshot(` { "config": { "adapter": [Function], @@ -34,37 +45,75 @@ describe('src/core/request.ts', () => { }, "data": {}, "headers": {}, - "request": undefined, + "request": { + "abort": [Function], + }, "status": 200, "statusText": "OK", } `); - await expect( - request({ - adapter: mockAdapterError(), - url: '/test', - method: 'get', - }), - ).resolves.toMatchInlineSnapshot(` + + await expect(e).rejects.toMatchInlineSnapshot( + '[Error: validate status fail]', + ); + await expect(e.catch((e) => Object.assign({}, e))).resolves + .toMatchInlineSnapshot(` + { + "config": { + "adapter": [Function], + "method": "get", + "url": "/test", + "validateStatus": [Function], + }, + "request": { + "abort": [Function], + }, + "response": { + "config": { + "adapter": [Function], + "method": "get", + "url": "/test", + "validateStatus": [Function], + }, + "data": {}, + "headers": {}, + "request": { + "abort": [Function], + }, + "status": 500, + "statusText": "ERROR", + }, + } + `); + + await expect(f).rejects.toMatchInlineSnapshot('[Error: request fail]'); + await expect(f.catch((e) => Object.assign({}, e))).resolves + .toMatchInlineSnapshot(` { "config": { "adapter": [Function], "method": "get", "url": "/test", }, - "data": {}, - "headers": {}, - "request": undefined, - "status": 400, - "statusText": "FAIL", + "request": { + "abort": [Function], + }, + "response": { + "config": { + "adapter": [Function], + "method": "get", + "url": "/test", + }, + "data": {}, + "headers": {}, + "isFail": true, + "request": { + "abort": [Function], + }, + "status": 400, + "statusText": "FAIL", + }, } `); - await expect( - request({ - adapter: mockAdapterFail(), - url: '/test', - method: 'get', - }), - ).rejects.toThrowErrorMatchingInlineSnapshot('"FAIL"'); }); }); diff --git a/test/core/transformData.test.ts b/test/core/transformData.test.ts index 32adf57..9abceae 100644 --- a/test/core/transformData.test.ts +++ b/test/core/transformData.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from 'vitest'; -import { transformData } from 'src/core/transformData'; +import { transformData } from '@/core/transformData'; describe('src/core/transformData.ts', () => { test('应该支持空配置', () => { diff --git a/test/core/transformURL.test.ts b/test/core/transformURL.test.ts index 32b2eba..b22b311 100644 --- a/test/core/transformURL.test.ts +++ b/test/core/transformURL.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect } from 'vitest'; -import { transformURL } from 'src/core/transformURL'; +import { transformURL } from '@/core/transformURL'; describe('src/core/transformURL.ts', () => { test('应该支持空配置', () => { diff --git a/tsconfig.json b/tsconfig.json index 488f013..e2ce41c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,10 @@ "module": "ESNext", "strict": true, "noEmit": true, - "moduleResolution": "node" + "moduleResolution": "node", + "paths": { + "@/*": ["src/*"] + } }, "include": ["./src", "./test", "./global.d.ts", "./global.variables.d.ts"], "exclude": ["node_modules"] diff --git a/vitest.config.ts b/vitest.config.ts index f10dec3..57f4791 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,6 +6,9 @@ export default defineConfig({ test: { root: resolve(__dirname), globals: true, + alias: { + '@': resolve(__dirname, 'src'), + }, include: ['./test/**/*.test.ts'], }, });