feat: 添加扩展实例

BREAKING CHANGE :
原派生领域替换为功能更强的扩展实例
pull/49/head
zjx0905 2023-04-25 11:54:10 +08:00
parent 6263759ba9
commit 9093e1bdff
12 changed files with 351 additions and 646 deletions

View File

@ -140,11 +140,11 @@ export interface AxiosAdapterBaseOptions extends AxiosAdapterRequestConfig {
/**
*
*/
success(response: AxiosAdapterResponse): void;
success(response: AnyObject): void;
/**
*
*/
fail(error: AxiosAdapterResponseError): void;
fail(error: AnyObject): void;
}
/**
@ -271,11 +271,11 @@ export function createAdapter(platform: AxiosAdapterPlatform) {
return {
...config,
header: config.headers,
success(response) {
success(response: AxiosAdapterResponse) {
transformResponse(response);
config.success(response);
},
fail(responseError) {
fail(responseError: AxiosAdapterResponseError) {
responseError.data = {
errMsg: responseError.errMsg,
errno: responseError.errno,
@ -284,14 +284,14 @@ export function createAdapter(platform: AxiosAdapterPlatform) {
config.fail(responseError);
},
};
}
function transformResponse(
response: AxiosAdapterResponse | AxiosAdapterResponseError,
) {
response.status = response.status ?? response.statusCode;
response.headers = response.headers ?? response.header;
clean(response, ['statusCode', 'errMsg', 'errno', 'header']);
}
function transformResponse(
response: AxiosAdapterResponse | AxiosAdapterResponseError,
) {
response.status = response.status ?? response.statusCode;
response.headers = response.headers ?? response.header;
clean(response, ['statusCode', 'errMsg', 'errno', 'header']);
}
function processRequest(

View File

@ -4,37 +4,13 @@ import {
isCancel,
} from './request/cancel';
import { isAxiosError } from './request/createError';
import Axios, { AxiosConstructor, AxiosRequestConfig } from './core/Axios';
import { AxiosInstance, createInstance } from './core/createInstance';
import { mergeConfig } from './core/mergeConfig';
import { AxiosDomainRequest } from './core/AxiosDomain';
import Axios, {
AxiosConstructor,
AxiosRequestConfig,
AxiosRequestHeaders,
} from './core/Axios';
import { createAdapter } from './adpater/createAdapter';
import defaults from './defaults';
import { version } from './version';
/**
* axios
*/
export interface AxiosInstanceDefaults extends AxiosRequestConfig {
/**
*
*/
headers: Required<AxiosRequestHeaders>;
}
/**
* axios
*/
export interface AxiosInstance extends AxiosDomainRequest, Axios {
/**
*
*/
defaults: AxiosInstanceDefaults;
}
/**
* axios
*/
@ -51,12 +27,6 @@ export interface AxiosStatic extends AxiosInstance {
*
*/
CancelToken: CancelTokenConstructor;
/**
* axios
*
* @param config
*/
create(config?: AxiosRequestConfig): AxiosInstance;
/**
*
*/
@ -69,26 +39,18 @@ export interface AxiosStatic extends AxiosInstance {
* true
*/
isAxiosError: typeof isAxiosError;
}
function createInstance(config: AxiosRequestConfig) {
const context = new Axios(config);
const instance = context.request as AxiosInstance;
Object.assign(instance, context);
Object.setPrototypeOf(instance, Axios.prototype);
return instance;
/**
* axios
*
* @param config
*/
create(config?: AxiosRequestConfig): AxiosInstance;
}
const axios = createInstance(defaults) as AxiosStatic;
axios.create = function create(config) {
const instance = createInstance(mergeConfig(axios.defaults, config));
instance.flush = axios.middleware.wrap(instance.flush);
return instance;
return createInstance(mergeConfig(axios.defaults, config));
};
axios.version = version;
axios.Axios = Axios;
axios.CancelToken = CancelToken;

View File

@ -1,7 +1,9 @@
import { buildURL } from '../helpers/buildURL';
import { combineURL } from '../helpers/combineURL';
import { isString } from '../helpers/isTypes';
import { dispatchRequest } from '../request/dispatchRequest';
import { CancelToken } from '../request/cancel';
import { AxiosTransformer } from '../request/transformData';
import { deepMerge } from '../helpers/deepMerge';
import {
AxiosAdapter,
AxiosAdapterRequestMethod,
@ -9,9 +11,17 @@ import {
AxiosAdapterRequestConfig,
AxiosAdapterResponseData,
} from '../adpater/createAdapter';
import InterceptorManager, { Interceptor } from './InterceptorManager';
import InterceptorManager, {
Interceptor,
InterceptorExecutor,
} from './InterceptorManager';
import { mergeConfig } from './mergeConfig';
import AxiosDomain, { AxiosDomainRequestHandler } from './AxiosDomain';
import {
PLAIN_METHODS,
WITH_DATA_METHODS,
WITH_PARAMS_METHODS,
} from '../constants/methods';
import MiddlewareManager from './MiddlewareManager';
/**
*
@ -293,6 +303,51 @@ export interface AxiosResponseError extends AnyObject {
request?: AxiosAdapterPlatformTask;
}
export interface AxiosContext {
req: AxiosRequestConfig;
res: null | AxiosResponse;
}
export interface AxiosRequest {
<TData extends AxiosResponseData>(config: AxiosRequestConfig): Promise<
AxiosResponse<TData>
>;
<TData extends AxiosResponseData>(
url: string,
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
}
/**
*
*/
export type AxiosRequestMethodFn = <TData extends AxiosResponseData>(
url: string,
config?: AxiosRequestConfig,
) => Promise<AxiosResponse<TData>>;
/**
*
*/
export type AxiosRequestMethodFnWithParams = <TData extends AxiosResponseData>(
url: string,
params?: AnyObject,
config?: AxiosRequestConfig,
) => Promise<AxiosResponse<TData>>;
/**
*
*/
export type AxiosRequestMethodFnWithData = <TData extends AxiosResponseData>(
url: string,
data?: AxiosRequestData,
config?: AxiosRequestConfig,
) => Promise<AxiosResponse<TData>>;
export interface AxiosDomainRequestHandler {
(config: AxiosRequestConfig): Promise<AxiosResponse>;
}
/**
* Axios
*/
@ -300,7 +355,13 @@ export interface AxiosConstructor {
new (config: AxiosRequestConfig): Axios;
}
export default class Axios extends AxiosDomain {
export default class Axios {
#parent?: Axios;
/**
*
*/
defaults: AxiosRequestConfig;
/**
*
*/
@ -315,37 +376,102 @@ export default class Axios extends AxiosDomain {
response: new InterceptorManager<AxiosResponse>(),
};
constructor(defaults: AxiosRequestConfig = {}) {
super(defaults, (...args) => this.#processRequest(...args));
}
/**
*
*/
#middleware = new MiddlewareManager<AxiosContext>(async (ctx) => {
ctx.res = await dispatchRequest(ctx.req);
});
getUri(config: AxiosRequestConfig): string {
const { url, params, paramsSerializer } = mergeConfig(
this.defaults,
config,
);
return buildURL(url, params, paramsSerializer).replace(/^\?/, '');
/**
* options
*/
options!: AxiosRequestMethodFn;
/**
* get
*/
get!: AxiosRequestMethodFnWithParams;
/**
* head
*/
head!: AxiosRequestMethodFnWithParams;
/**
* post
*/
post!: AxiosRequestMethodFnWithData;
/**
* put
*/
put!: AxiosRequestMethodFnWithData;
/**
* patch
*/
patch!: AxiosRequestMethodFnWithData;
/**
* delete
*/
delete!: AxiosRequestMethodFnWithParams;
/**
* trace
*/
trace!: AxiosRequestMethodFn;
/**
* connect
*/
connect!: AxiosRequestMethodFn;
/**
*
*/
use: MiddlewareManager<AxiosContext>['use'];
constructor(defaults: AxiosRequestConfig = {}, parent?: Axios) {
this.defaults = defaults;
this.#parent = parent;
if (this.#parent) {
this.#middleware.flush = this.#parent.#middleware.wrap(
this.#middleware.flush,
);
}
this.use = this.#middleware.use.bind(this.#middleware);
}
/**
*
*
*/
fork = (config: AxiosRequestConfig = {}) => {
config.baseURL = combineURL(this.defaults.baseURL, config.baseURL);
const domain = new AxiosDomain(
mergeConfig(this.defaults, config),
(...args) => this.#processRequest(...args),
);
domain.flush = this.middleware.wrap(domain.flush);
return domain;
request: AxiosRequest = (
urlOrConfig: string | AxiosRequestConfig,
config: AxiosRequestConfig = {},
) => {
if (isString(urlOrConfig)) {
config.url = urlOrConfig;
} else {
config = urlOrConfig;
}
config.method = config.method || 'get';
return this.#processRequest(mergeConfig(this.defaults, config));
};
#processRequest(
config: AxiosRequestConfig,
requestHandlerFn: AxiosDomainRequestHandler,
) {
#processRequest(config: AxiosRequestConfig) {
const requestHandler = {
resolved: requestHandlerFn,
resolved: async (config: AxiosRequestConfig) => {
config.url = combineURL(config.baseURL, config.url);
const ctx: AxiosContext = {
req: config,
res: null,
};
await this.#middleware.flush(ctx);
return ctx.res as AxiosResponse;
},
};
const errorHandler = {
rejected: config.errorHandler,
@ -355,11 +481,11 @@ export default class Axios extends AxiosDomain {
| Partial<Interceptor<AxiosResponse>>
)[] = [];
this.interceptors.request.forEach((requestInterceptor) => {
this.#eacheRequestInterceptors((requestInterceptor) => {
chain.unshift(requestInterceptor);
});
chain.push(requestHandler);
this.interceptors.response.forEach((responseInterceptor) => {
this.#eacheResponseInterceptors((responseInterceptor) => {
chain.push(responseInterceptor);
});
chain.push(errorHandler);
@ -374,4 +500,49 @@ export default class Axios extends AxiosDomain {
Promise.resolve(config),
) as Promise<AxiosResponse>;
}
#eacheRequestInterceptors(executor: InterceptorExecutor<AxiosRequestConfig>) {
this.interceptors.request.forEach(executor);
if (this.#parent) {
this.#parent.#eacheRequestInterceptors(executor);
}
}
#eacheResponseInterceptors(executor: InterceptorExecutor<AxiosResponse>) {
this.interceptors.response.forEach(executor);
if (this.#parent) {
this.#parent.#eacheResponseInterceptors(executor);
}
}
}
for (const method of PLAIN_METHODS) {
Axios.prototype[method] = function processRequestMethod(url, config = {}) {
config.method = method;
return this.request(url, config);
};
}
for (const method of WITH_PARAMS_METHODS) {
Axios.prototype[method] = function processRequestMethodWithParams(
url,
params = {},
config = {},
) {
config.method = method;
config.params = deepMerge(params, config.params ?? {});
return this.request(url, config);
};
}
for (const method of WITH_DATA_METHODS) {
Axios.prototype[method] = function processRequestMethodWithData(
url,
data,
config = {},
) {
config.method = method;
config.data = data;
return this.request(url, config);
};
}

View File

@ -1,205 +0,0 @@
import {
PLAIN_METHODS,
WITH_DATA_METHODS,
WITH_PARAMS_METHODS,
} from '../constants/methods';
import { isString, isUndefined } from '../helpers/isTypes';
import { deepMerge } from '../helpers/deepMerge';
import { combineURL } from '../helpers/combineURL';
import { dispatchRequest } from '../request/dispatchRequest';
import { mergeConfig } from './mergeConfig';
import {
AxiosRequestConfig,
AxiosRequestData,
AxiosResponse,
AxiosResponseData,
} from './Axios';
import MiddlewareManager, {
MiddlewareContext,
MiddlewareFlush,
} from './MiddlewareManager';
/**
*
*/
export interface AxiosDomainRequest {
<TData extends AxiosResponseData>(config: AxiosRequestConfig): Promise<
AxiosResponse<TData>
>;
<TData extends AxiosResponseData>(
url: string,
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
}
/**
*
*/
export type AxiosDomainRequestMethod = <TData extends AxiosResponseData>(
url: string,
config?: AxiosRequestConfig,
) => Promise<AxiosResponse<TData>>;
/**
*
*/
export type AxiosDomainRequestMethodWithParams = <
TData extends AxiosResponseData,
>(
url: string,
params?: AnyObject,
config?: AxiosRequestConfig,
) => Promise<AxiosResponse<TData>>;
/**
*
*/
export type AxiosDomainRequestMethodWithData = <
TData extends AxiosResponseData,
>(
url: string,
data?: AxiosRequestData,
config?: AxiosRequestConfig,
) => Promise<AxiosResponse<TData>>;
export interface AxiosDomainRequestHandler {
(config: AxiosRequestConfig): Promise<AxiosResponse>;
}
export default class AxiosDomain {
/**
*
*/
defaults: AxiosRequestConfig;
middleware = new MiddlewareManager();
/**
*
*/
request!: AxiosDomainRequest;
/**
* options
*/
options!: AxiosDomainRequestMethod;
/**
* get
*/
get!: AxiosDomainRequestMethodWithParams;
/**
* head
*/
head!: AxiosDomainRequestMethodWithParams;
/**
* post
*/
post!: AxiosDomainRequestMethodWithData;
/**
* put
*/
put!: AxiosDomainRequestMethodWithData;
/**
* patch
*/
patch!: AxiosDomainRequestMethodWithData;
/**
* delete
*/
delete!: AxiosDomainRequestMethodWithParams;
/**
* trace
*/
trace!: AxiosDomainRequestMethod;
/**
* connect
*/
connect!: AxiosDomainRequestMethod;
flush: MiddlewareFlush;
constructor(
defaults: AxiosRequestConfig,
processRequest: (
config: AxiosRequestConfig,
requestHandler: AxiosDomainRequestHandler,
) => Promise<AxiosResponse>,
) {
this.defaults = defaults;
this.request = (
urlOrConfig: string | AxiosRequestConfig,
config: AxiosRequestConfig = {},
) => {
if (isString(urlOrConfig)) {
config.url = urlOrConfig;
} else {
config = urlOrConfig;
}
if (isUndefined(config.method)) {
config.method = 'get';
}
return processRequest(
mergeConfig(this.defaults, config),
this.#requestHandler,
);
};
this.flush = this.middleware.wrap(async (ctx) => {
ctx.res = await dispatchRequest(ctx.req);
});
}
#requestHandler: AxiosDomainRequestHandler = async (config) => {
config.url = combineURL(config.baseURL, config.url);
const ctx: MiddlewareContext = {
req: config,
res: null,
};
await this.flush(ctx);
return ctx.res as AxiosResponse;
};
}
for (const method of PLAIN_METHODS) {
AxiosDomain.prototype[method] = function processRequestMethod(
url,
config = {},
) {
config.method = method;
return this.request(url, config);
};
}
for (const method of WITH_PARAMS_METHODS) {
AxiosDomain.prototype[method] = function processRequestMethodWithParams(
url,
params = {},
config = {},
) {
config.method = method;
config.params = deepMerge(params, config.params ?? {});
return this.request(url, config);
};
}
for (const method of WITH_DATA_METHODS) {
AxiosDomain.prototype[method] = function processRequestMethodWithData(
url,
data,
config = {},
) {
config.method = method;
config.data = data;
return this.request(url, config);
};
}

View File

@ -1,36 +1,44 @@
import { assert } from '../helpers/error';
import { combineURL } from '../helpers/combineURL';
import { isFunction } from '../helpers/isTypes';
import { AxiosRequestConfig, AxiosResponse } from './Axios';
export interface MiddlewareContext {
req: AxiosRequestConfig;
res: null | AxiosResponse;
}
import { isFunction, isString } from '../helpers/isTypes';
export interface MiddlewareNext {
(): Promise<void>;
}
export interface MiddlewareCallback {
(ctx: MiddlewareContext, next: MiddlewareNext): Promise<void>;
export interface MiddlewareCallback<Conext extends AnyObject> {
(ctx: Conext, next: MiddlewareNext): Promise<void>;
}
export interface MiddlewareFlush {
(ctx: MiddlewareContext): Promise<void>;
export interface MiddlewareFlush<Conext extends AnyObject> {
(ctx: Conext): Promise<void>;
}
export default class MiddlewareManager {
#map = new Map<string, MiddlewareCallback[]>();
export default class MiddlewareManager<Conext extends AnyObject = AnyObject> {
#map = new Map<string, MiddlewareCallback<Conext>[]>();
use(callback: MiddlewareCallback): MiddlewareManager;
use(path: string, callback: MiddlewareCallback): MiddlewareManager;
use(path: string | MiddlewareCallback, callback?: MiddlewareCallback) {
flush: MiddlewareFlush<Conext>;
constructor(flush: MiddlewareFlush<Conext>) {
this.flush = this.wrap(flush);
}
use(callback: MiddlewareCallback<Conext>): MiddlewareManager<Conext>;
use(
path: string,
callback: MiddlewareCallback<Conext>,
): MiddlewareManager<Conext>;
use(
path: string | MiddlewareCallback<Conext>,
callback?: MiddlewareCallback<Conext>,
) {
if (isFunction(path)) {
callback = path;
path = '/';
}
assert(!!path, 'path 不是一个非空的 string');
assert(isString(path), 'path 不是一个 string');
assert(!!path, 'path 不是一个长度大于零的 string');
assert(isFunction(callback), 'callback 不是一个 function');
const middlewares = this.#map.get(path) ?? [];
middlewares.push(callback!);
@ -39,32 +47,25 @@ export default class MiddlewareManager {
return this;
}
wrap(flush: MiddlewareFlush): MiddlewareFlush {
return (ctx) => this.#performer(ctx, flush);
}
wrap(flush: MiddlewareFlush<Conext>): MiddlewareFlush<Conext> {
return (ctx) => {
const allMiddlewares: MiddlewareCallback<Conext>[] = [];
#performer(ctx: MiddlewareContext, flush: MiddlewareFlush) {
const middlewares = [...this.#getAllMiddlewares(ctx), flush];
for (const [path, middlewares] of this.#map.entries()) {
const url = combineURL(ctx.req.baseURL, path);
const checkRE = new RegExp(`^${url}([/?].*)?`);
function next(): Promise<void> {
return middlewares.shift()!(ctx, next);
}
return next();
}
#getAllMiddlewares(ctx: MiddlewareContext) {
const allMiddlewares: MiddlewareCallback[] = [];
for (const [path, middlewares] of this.#map.entries()) {
const url = combineURL(ctx.req.baseURL, path);
const checkRE = new RegExp(`^${url}([/?].*)?`);
if (checkRE.test(ctx.req.url!)) {
allMiddlewares.push(...middlewares);
if (path === '/') {
allMiddlewares.push(...middlewares);
} else if (checkRE.test(ctx.req.url!)) {
allMiddlewares.push(...middlewares);
}
}
}
return allMiddlewares;
const tasks = [...allMiddlewares, flush];
return (function next(): Promise<void> {
return tasks.shift()!(ctx, next);
})();
};
}
}

View File

@ -0,0 +1,69 @@
import { combineURL } from '../helpers/combineURL';
import { buildURL } from '../helpers/buildURL';
import Axios, {
AxiosRequest,
AxiosRequestConfig,
AxiosRequestHeaders,
} from './Axios';
import { mergeConfig } from './mergeConfig';
/**
* axios
*/
export interface AxiosInstanceDefaults extends AxiosRequestConfig {
/**
*
*/
headers: Required<AxiosRequestHeaders>;
}
/**
* axios
*/
export interface AxiosInstance extends AxiosRequest, Axios {
/**
*
*/
defaults: AxiosInstanceDefaults;
/**
* URI
*
* @param config
*/
getUri(config: AxiosRequestConfig): string;
/**
*
*
* @param config
*/
fork(config: AxiosRequestConfig): AxiosInstance;
/**
*
*
* @param config
*/
extend(config: AxiosRequestConfig): AxiosInstance;
}
export function createInstance(config: AxiosRequestConfig, parent?: Axios) {
const context = new Axios(config, parent);
const instance = context.request as AxiosInstance;
instance.getUri = function getUri(config: AxiosRequestConfig) {
const { url, params, paramsSerializer } = mergeConfig(
instance.defaults,
config,
);
return buildURL(url, params, paramsSerializer).replace(/^\?/, '');
};
instance.extend = function extend(config: AxiosRequestConfig = {}) {
config.url = combineURL(instance.defaults.baseURL, config.url);
return createInstance(mergeConfig(instance.defaults, config), context);
};
instance.fork = instance.extend;
Object.assign(instance, context);
Object.setPrototypeOf(instance, Axios.prototype);
return instance;
}

View File

@ -1,5 +1,5 @@
import { getDefaultAdapter } from './adpater/getDefaultAdapter';
import { AxiosInstanceDefaults } from './axios';
import { AxiosInstanceDefaults } from './core/createInstance';
const defaults: AxiosInstanceDefaults = {
// 适配器,在支持的平台中有值。

View File

@ -15,10 +15,13 @@ export type {
AxiosUploadProgressCallback,
} from './core/Axios';
export type {
MiddlewareContext,
MiddlewareCallback,
MiddlewareNext,
} from './core/MiddlewareManager';
export type {
AxiosInstanceDefaults,
AxiosInstance,
} from './core/createInstance';
export type {
AxiosAdapter,
AxiosAdapterRequestConfig,
@ -35,11 +38,7 @@ export type {
AxiosAdapterPlatform,
AxiosAdapterPlatformTask,
} from './adpater/createAdapter';
export type {
AxiosInstanceDefaults,
AxiosInstance,
AxiosStatic,
} from './axios';
export type { AxiosStatic } from './axios';
export { CancelToken, isCancel } from './request/cancel';
export { isAxiosError } from './request/createError';

View File

@ -1,11 +1,10 @@
import { describe, test, expect, vi, afterEach } from 'vitest';
import { describe, test, expect, vi } from 'vitest';
import { mockAdapter, testEachMethods } from 'scripts/test.utils';
import {
PLAIN_METHODS,
WITH_DATA_METHODS,
WITH_PARAMS_METHODS,
} from '@/constants/methods';
import AxiosDomain from '@/core/AxiosDomain';
import defaults from '@/defaults';
import axios from '@/axios';
@ -163,8 +162,4 @@ describe('src/axios.ts', () => {
'test?id=1',
);
});
test('派生的领域应该为 AxiosDomain 的实例', () => {
expect(axios.fork() instanceof AxiosDomain).toBeTruthy();
});
});

View File

@ -10,7 +10,6 @@ import {
WITH_DATA_METHODS,
WITH_PARAMS_METHODS,
} from '@/constants/methods';
import AxiosDomain from '@/core/AxiosDomain';
import Axios from '@/core/Axios';
import axios from '@/axios';
@ -22,10 +21,6 @@ describe('src/core/Axios.ts', () => {
baseURL: 'http://api.com',
});
test('应该继承自 AxiosDomain', () => {
expect(new Axios() instanceof AxiosDomain).toBeTruthy();
});
test('应该有这些实例属性及方法', () => {
const c = {
baseURL: 'http://api.com',
@ -34,8 +29,6 @@ describe('src/core/Axios.ts', () => {
expect(axiosObj.defaults).toEqual(c);
expect(axiosObj.interceptors).toBeTypeOf('object');
expect(axiosObj.request).toBeTypeOf('function');
expect(axiosObj.getUri).toBeTypeOf('function');
expect(axiosObj.fork).toBeTypeOf('function');
});
testEachMethods('%s 应该是一个函数', (k) => {
@ -354,45 +347,4 @@ describe('src/core/Axios.ts', () => {
expect(res2).not.toBeCalled();
expect(rej2).toBeCalled();
});
test('应该可以获取 URI', () => {
expect(axiosObj.getUri({ url: 'test' })).toBe('test');
expect(axiosObj.getUri({ url: 'test', params: { id: 1 } })).toBe(
'test?id=1',
);
expect(
axiosObj.getUri({ url: 'test', paramsSerializer: () => 'id=1' }),
).toBe('test?id=1');
});
test('派生的领域应该为 AxiosDomain 的实例', () => {
expect(axiosObj.fork() instanceof AxiosDomain).toBeTruthy();
});
test('应该支持 绝对路径/相对路径 派生领域', () => {
const a1 = axiosObj.fork({ baseURL: 'test' });
const a2 = new Axios().fork({ baseURL: 'test' });
const a3 = axiosObj.fork({ baseURL: 'https://api.com' });
const a4 = axiosObj.fork();
expect(a1.defaults.baseURL).toBe('http://api.com/test');
expect(a2.defaults.baseURL).toBe('/test');
expect(a3.defaults.baseURL).toBe('https://api.com');
expect(a4.defaults.baseURL).toBe('http://api.com');
});
test('派生自当前实例的领域应该可以复用当前实例的拦截器', async () => {
const axiosObj = new Axios();
const req = vi.fn((v) => v);
const res = vi.fn((v) => v);
axiosObj.interceptors.request.use(req);
axiosObj.interceptors.response.use(res);
const a = axiosObj.fork({ baseURL: 'test' });
await a.request('test', { adapter: mockAdapter() });
expect(req).toBeCalled();
expect(res).toBeCalled();
});
});

View File

@ -1,240 +0,0 @@
import { describe, test, expect, vi } from 'vitest';
import { ignore } from '@/helpers/ignore';
import {
PLAIN_METHODS,
WITH_DATA_METHODS,
WITH_PARAMS_METHODS,
} from '@/constants/methods';
import AxiosDomain from '@/core/AxiosDomain';
import { AxiosResponse } from '@/core/Axios';
import { eachMethods } from 'scripts/test.utils';
describe('src/core/AxiosDomain.ts', () => {
test('应该有这些实例属性', () => {
const c = {
baseURL: 'http://api.com',
};
const a = new AxiosDomain(c, vi.fn());
expect(a.defaults).toEqual(c);
expect(a.request).toBeTypeOf('function');
eachMethods((k) => {
expect(a[k]).toBeTypeOf('function');
});
});
test('发送请求时 processRequest 应该被调用', () => {
const p = vi.fn();
const d = {
baseURL: 'http://api.com',
};
const c = {
url: 'test',
params: {
id: 1,
},
data: {
id: 1,
},
};
new AxiosDomain(d, p).request(c);
expect(p).toBeCalled();
expect(p.mock.calls[0][0]).toEqual({
...d,
...c,
});
});
test('请求方法应该支持空参数', () => {
const cb = vi.fn();
const a = new AxiosDomain({}, cb);
a.request('test');
PLAIN_METHODS.forEach((k) => a[k]('test'));
WITH_PARAMS_METHODS.forEach((k) => a[k]('test'));
WITH_DATA_METHODS.forEach((k) => a[k]('test'));
const l =
PLAIN_METHODS.length +
WITH_PARAMS_METHODS.length +
WITH_DATA_METHODS.length +
1;
expect(cb.mock.calls.length).toBe(l);
cb.mock.calls.forEach(([config]) => expect(config.url).toBe('test'));
});
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);
PLAIN_METHODS.forEach((k) => a[k](u, c));
WITH_PARAMS_METHODS.forEach((k) => a[k](u, c.params, ignore(c, 'params')));
WITH_DATA_METHODS.forEach((k) => a[k](u, c.data, ignore(c, 'data')));
const l =
PLAIN_METHODS.length +
WITH_PARAMS_METHODS.length +
WITH_DATA_METHODS.length +
1;
expect(cb.mock.calls.length).toBe(l);
});
test('应该可以直接传递 config 调用请求方法', () => {
const cb = vi.fn();
const d = {
baseURL: 'http://api.com',
};
const c = {
url: 'test',
params: {
id: 1,
},
data: {
id: 1,
},
};
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);
return {} as AxiosResponse;
});
a.request(c);
PLAIN_METHODS.forEach((k) => a[k](c.url, ignore(c, 'url')));
WITH_PARAMS_METHODS.forEach((k) =>
a[k](c.url, c.params, ignore(c, 'url', 'params')),
);
WITH_DATA_METHODS.forEach((k) =>
a[k](c.url, c.data, ignore(c, 'url', 'data')),
);
const l =
PLAIN_METHODS.length +
WITH_PARAMS_METHODS.length +
WITH_DATA_METHODS.length +
1;
expect(cb.mock.calls.length).toBe(l);
});
test('应该支持深度合并 params', () => {
const d = {
baseURL: 'http://api.com',
};
const p = {
v1: 1,
v2: {
v1: 1,
},
};
const c = {
params: {
v2: {
v2: 2,
},
v3: 3,
},
};
const a = new AxiosDomain(d, async (config) => {
expect(config.params).toEqual({
v1: 1,
v2: {
v1: 1,
v2: 2,
},
v3: 3,
});
return {} as AxiosResponse;
});
WITH_PARAMS_METHODS.forEach((k) => a[k]('test', p, c));
});
test('应该只取传入的 data', () => {
const ds = {
baseURL: 'http://api.com',
};
const d = {
v: 1,
};
const c = {
v: 2,
};
const a = new AxiosDomain(ds, async (config) => {
expect(config.data).toEqual({
v: 1,
});
return {} as AxiosResponse;
});
WITH_DATA_METHODS.forEach((k) => a[k]('test', d, c));
});
test('应该支持多种类型 data', () => {
const ds = {
baseURL: 'http://api.com',
};
const str = '11';
const obj = {};
const buff = new ArrayBuffer(0);
const testStr = new AxiosDomain(ds, async (config) => {
expect(config.data).toBe(str);
return {} as AxiosResponse;
});
const testObj = new AxiosDomain(ds, async (config) => {
expect(config.data).toBe(obj);
return {} as AxiosResponse;
});
const testBuff = new AxiosDomain(ds, async (config) => {
expect(config.data).toBe(buff);
return {} as AxiosResponse;
});
WITH_DATA_METHODS.forEach((k) => {
testStr[k]('test', str);
testObj[k]('test', obj);
testBuff[k]('test', buff);
});
});
});

View File

@ -3,14 +3,18 @@ import MiddlewareManager from '@/core/MiddlewareManager';
describe('src/core/MiddlewareManager.ts', () => {
test('应该有这些实例属性', () => {
const m = new MiddlewareManager();
const m = new MiddlewareManager(vi.fn());
expect(m.use).toBeTypeOf('function');
expect(m.wrap).toBeTypeOf('function');
});
test('应该可以添加中间件回调', async () => {
const m = new MiddlewareManager();
const flush = vi.fn(async (ctx) => {
expect(ctx.req.url).toBe('test');
ctx.res = res;
});
const m = new MiddlewareManager(flush);
const ctx = {
req: { url: 'https://api.com' },
res: null,
@ -24,20 +28,20 @@ describe('src/core/MiddlewareManager.ts', () => {
await next();
expect(ctx.res).toBe(res);
});
const flush = vi.fn(async (ctx) => {
expect(ctx.req.url).toBe('test');
ctx.res = res;
});
m.use(midde);
await m.wrap(flush)(ctx);
await m.flush(ctx);
expect(ctx.res).toBe(res);
expect(midde).toBeCalled();
});
test('应该可以给路径添加中间件回调', async () => {
const m = new MiddlewareManager();
const flush = vi.fn(async (ctx) => {
ctx.res = res;
});
const m = new MiddlewareManager(flush);
const ctx1 = {
req: {
baseURL: 'https://api.com',
@ -60,18 +64,15 @@ describe('src/core/MiddlewareManager.ts', () => {
await next();
expect(ctx.res).toBe(res);
});
const flush = vi.fn(async (ctx) => {
ctx.res = res;
});
m.use('/test', midde);
await m.wrap(flush)(ctx1);
await m.flush(ctx1);
expect(ctx1.res).toBe(res);
expect(midde).not.toBeCalled();
m.use('/test', midde);
await m.wrap(flush)(ctx2);
await m.flush(ctx2);
expect(midde).toBeCalled();
});