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 { return {
...config, ...config,
header: config.headers, header: config.headers,
success(response) { success(response: AxiosAdapterResponse) {
transformResponse(response); transformResponse(response);
config.success(response); config.success(response);
}, },
fail(responseError) { fail(responseError: AxiosAdapterResponseError) {
responseError.data = { responseError.data = {
errMsg: responseError.errMsg, errMsg: responseError.errMsg,
errno: responseError.errno, errno: responseError.errno,
@ -284,14 +284,14 @@ export function createAdapter(platform: AxiosAdapterPlatform) {
config.fail(responseError); config.fail(responseError);
}, },
}; };
}
function transformResponse( function transformResponse(
response: AxiosAdapterResponse | AxiosAdapterResponseError, response: AxiosAdapterResponse | AxiosAdapterResponseError,
) { ) {
response.status = response.status ?? response.statusCode; response.status = response.status ?? response.statusCode;
response.headers = response.headers ?? response.header; response.headers = response.headers ?? response.header;
clean(response, ['statusCode', 'errMsg', 'errno', 'header']); clean(response, ['statusCode', 'errMsg', 'errno', 'header']);
}
} }
function processRequest( function processRequest(

View File

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

View File

@ -1,7 +1,9 @@
import { buildURL } from '../helpers/buildURL';
import { combineURL } from '../helpers/combineURL'; import { combineURL } from '../helpers/combineURL';
import { isString } from '../helpers/isTypes';
import { dispatchRequest } from '../request/dispatchRequest';
import { CancelToken } from '../request/cancel'; import { CancelToken } from '../request/cancel';
import { AxiosTransformer } from '../request/transformData'; import { AxiosTransformer } from '../request/transformData';
import { deepMerge } from '../helpers/deepMerge';
import { import {
AxiosAdapter, AxiosAdapter,
AxiosAdapterRequestMethod, AxiosAdapterRequestMethod,
@ -9,9 +11,17 @@ import {
AxiosAdapterRequestConfig, AxiosAdapterRequestConfig,
AxiosAdapterResponseData, AxiosAdapterResponseData,
} from '../adpater/createAdapter'; } from '../adpater/createAdapter';
import InterceptorManager, { Interceptor } from './InterceptorManager'; import InterceptorManager, {
Interceptor,
InterceptorExecutor,
} from './InterceptorManager';
import { mergeConfig } from './mergeConfig'; 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; 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 * Axios
*/ */
@ -300,7 +355,13 @@ export interface AxiosConstructor {
new (config: AxiosRequestConfig): Axios; 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>(), 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( * options
this.defaults, */
config, options!: AxiosRequestMethodFn;
);
return buildURL(url, params, paramsSerializer).replace(/^\?/, ''); /**
* 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 = {}) => { request: AxiosRequest = (
config.baseURL = combineURL(this.defaults.baseURL, config.baseURL); urlOrConfig: string | AxiosRequestConfig,
const domain = new AxiosDomain( config: AxiosRequestConfig = {},
mergeConfig(this.defaults, config), ) => {
(...args) => this.#processRequest(...args), if (isString(urlOrConfig)) {
); config.url = urlOrConfig;
domain.flush = this.middleware.wrap(domain.flush); } else {
return domain; config = urlOrConfig;
}
config.method = config.method || 'get';
return this.#processRequest(mergeConfig(this.defaults, config));
}; };
#processRequest( #processRequest(config: AxiosRequestConfig) {
config: AxiosRequestConfig,
requestHandlerFn: AxiosDomainRequestHandler,
) {
const requestHandler = { 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 = { const errorHandler = {
rejected: config.errorHandler, rejected: config.errorHandler,
@ -355,11 +481,11 @@ export default class Axios extends AxiosDomain {
| Partial<Interceptor<AxiosResponse>> | Partial<Interceptor<AxiosResponse>>
)[] = []; )[] = [];
this.interceptors.request.forEach((requestInterceptor) => { this.#eacheRequestInterceptors((requestInterceptor) => {
chain.unshift(requestInterceptor); chain.unshift(requestInterceptor);
}); });
chain.push(requestHandler); chain.push(requestHandler);
this.interceptors.response.forEach((responseInterceptor) => { this.#eacheResponseInterceptors((responseInterceptor) => {
chain.push(responseInterceptor); chain.push(responseInterceptor);
}); });
chain.push(errorHandler); chain.push(errorHandler);
@ -374,4 +500,49 @@ export default class Axios extends AxiosDomain {
Promise.resolve(config), Promise.resolve(config),
) as Promise<AxiosResponse>; ) 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 { assert } from '../helpers/error';
import { combineURL } from '../helpers/combineURL'; import { combineURL } from '../helpers/combineURL';
import { isFunction } from '../helpers/isTypes'; import { isFunction, isString } from '../helpers/isTypes';
import { AxiosRequestConfig, AxiosResponse } from './Axios';
export interface MiddlewareContext {
req: AxiosRequestConfig;
res: null | AxiosResponse;
}
export interface MiddlewareNext { export interface MiddlewareNext {
(): Promise<void>; (): Promise<void>;
} }
export interface MiddlewareCallback { export interface MiddlewareCallback<Conext extends AnyObject> {
(ctx: MiddlewareContext, next: MiddlewareNext): Promise<void>; (ctx: Conext, next: MiddlewareNext): Promise<void>;
} }
export interface MiddlewareFlush { export interface MiddlewareFlush<Conext extends AnyObject> {
(ctx: MiddlewareContext): Promise<void>; (ctx: Conext): Promise<void>;
} }
export default class MiddlewareManager { export default class MiddlewareManager<Conext extends AnyObject = AnyObject> {
#map = new Map<string, MiddlewareCallback[]>(); #map = new Map<string, MiddlewareCallback<Conext>[]>();
use(callback: MiddlewareCallback): MiddlewareManager; flush: MiddlewareFlush<Conext>;
use(path: string, callback: MiddlewareCallback): MiddlewareManager;
use(path: string | MiddlewareCallback, callback?: MiddlewareCallback) { 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)) { if (isFunction(path)) {
callback = path; callback = path;
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) ?? []; const middlewares = this.#map.get(path) ?? [];
middlewares.push(callback!); middlewares.push(callback!);
@ -39,32 +47,25 @@ export default class MiddlewareManager {
return this; return this;
} }
wrap(flush: MiddlewareFlush): MiddlewareFlush { wrap(flush: MiddlewareFlush<Conext>): MiddlewareFlush<Conext> {
return (ctx) => this.#performer(ctx, flush); return (ctx) => {
} const allMiddlewares: MiddlewareCallback<Conext>[] = [];
#performer(ctx: MiddlewareContext, flush: MiddlewareFlush) { for (const [path, middlewares] of this.#map.entries()) {
const middlewares = [...this.#getAllMiddlewares(ctx), flush]; const url = combineURL(ctx.req.baseURL, path);
const checkRE = new RegExp(`^${url}([/?].*)?`);
function next(): Promise<void> { if (path === '/') {
return middlewares.shift()!(ctx, next); allMiddlewares.push(...middlewares);
} } else if (checkRE.test(ctx.req.url!)) {
allMiddlewares.push(...middlewares);
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);
} }
}
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 { getDefaultAdapter } from './adpater/getDefaultAdapter';
import { AxiosInstanceDefaults } from './axios'; import { AxiosInstanceDefaults } from './core/createInstance';
const defaults: AxiosInstanceDefaults = { const defaults: AxiosInstanceDefaults = {
// 适配器,在支持的平台中有值。 // 适配器,在支持的平台中有值。

View File

@ -15,10 +15,13 @@ export type {
AxiosUploadProgressCallback, AxiosUploadProgressCallback,
} from './core/Axios'; } from './core/Axios';
export type { export type {
MiddlewareContext,
MiddlewareCallback, MiddlewareCallback,
MiddlewareNext, MiddlewareNext,
} from './core/MiddlewareManager'; } from './core/MiddlewareManager';
export type {
AxiosInstanceDefaults,
AxiosInstance,
} from './core/createInstance';
export type { export type {
AxiosAdapter, AxiosAdapter,
AxiosAdapterRequestConfig, AxiosAdapterRequestConfig,
@ -35,11 +38,7 @@ export type {
AxiosAdapterPlatform, AxiosAdapterPlatform,
AxiosAdapterPlatformTask, AxiosAdapterPlatformTask,
} from './adpater/createAdapter'; } from './adpater/createAdapter';
export type { export type { AxiosStatic } from './axios';
AxiosInstanceDefaults,
AxiosInstance,
AxiosStatic,
} from './axios';
export { CancelToken, isCancel } from './request/cancel'; export { CancelToken, isCancel } from './request/cancel';
export { isAxiosError } from './request/createError'; 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 { mockAdapter, testEachMethods } from 'scripts/test.utils';
import { import {
PLAIN_METHODS, PLAIN_METHODS,
WITH_DATA_METHODS, WITH_DATA_METHODS,
WITH_PARAMS_METHODS, WITH_PARAMS_METHODS,
} from '@/constants/methods'; } from '@/constants/methods';
import AxiosDomain from '@/core/AxiosDomain';
import defaults from '@/defaults'; import defaults from '@/defaults';
import axios from '@/axios'; import axios from '@/axios';
@ -163,8 +162,4 @@ describe('src/axios.ts', () => {
'test?id=1', 'test?id=1',
); );
}); });
test('派生的领域应该为 AxiosDomain 的实例', () => {
expect(axios.fork() instanceof AxiosDomain).toBeTruthy();
});
}); });

View File

@ -10,7 +10,6 @@ import {
WITH_DATA_METHODS, WITH_DATA_METHODS,
WITH_PARAMS_METHODS, WITH_PARAMS_METHODS,
} from '@/constants/methods'; } from '@/constants/methods';
import AxiosDomain from '@/core/AxiosDomain';
import Axios from '@/core/Axios'; import Axios from '@/core/Axios';
import axios from '@/axios'; import axios from '@/axios';
@ -22,10 +21,6 @@ describe('src/core/Axios.ts', () => {
baseURL: 'http://api.com', baseURL: 'http://api.com',
}); });
test('应该继承自 AxiosDomain', () => {
expect(new Axios() instanceof AxiosDomain).toBeTruthy();
});
test('应该有这些实例属性及方法', () => { test('应该有这些实例属性及方法', () => {
const c = { const c = {
baseURL: 'http://api.com', baseURL: 'http://api.com',
@ -34,8 +29,6 @@ describe('src/core/Axios.ts', () => {
expect(axiosObj.defaults).toEqual(c); expect(axiosObj.defaults).toEqual(c);
expect(axiosObj.interceptors).toBeTypeOf('object'); expect(axiosObj.interceptors).toBeTypeOf('object');
expect(axiosObj.request).toBeTypeOf('function'); expect(axiosObj.request).toBeTypeOf('function');
expect(axiosObj.getUri).toBeTypeOf('function');
expect(axiosObj.fork).toBeTypeOf('function');
}); });
testEachMethods('%s 应该是一个函数', (k) => { testEachMethods('%s 应该是一个函数', (k) => {
@ -354,45 +347,4 @@ describe('src/core/Axios.ts', () => {
expect(res2).not.toBeCalled(); expect(res2).not.toBeCalled();
expect(rej2).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', () => { describe('src/core/MiddlewareManager.ts', () => {
test('应该有这些实例属性', () => { test('应该有这些实例属性', () => {
const m = new MiddlewareManager(); const m = new MiddlewareManager(vi.fn());
expect(m.use).toBeTypeOf('function'); expect(m.use).toBeTypeOf('function');
expect(m.wrap).toBeTypeOf('function'); expect(m.wrap).toBeTypeOf('function');
}); });
test('应该可以添加中间件回调', async () => { 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 = { const ctx = {
req: { url: 'https://api.com' }, req: { url: 'https://api.com' },
res: null, res: null,
@ -24,20 +28,20 @@ describe('src/core/MiddlewareManager.ts', () => {
await next(); await next();
expect(ctx.res).toBe(res); expect(ctx.res).toBe(res);
}); });
const flush = vi.fn(async (ctx) => {
expect(ctx.req.url).toBe('test');
ctx.res = res;
});
m.use(midde); m.use(midde);
await m.wrap(flush)(ctx); await m.flush(ctx);
expect(ctx.res).toBe(res); expect(ctx.res).toBe(res);
expect(midde).toBeCalled(); expect(midde).toBeCalled();
}); });
test('应该可以给路径添加中间件回调', async () => { test('应该可以给路径添加中间件回调', async () => {
const m = new MiddlewareManager(); const flush = vi.fn(async (ctx) => {
ctx.res = res;
});
const m = new MiddlewareManager(flush);
const ctx1 = { const ctx1 = {
req: { req: {
baseURL: 'https://api.com', baseURL: 'https://api.com',
@ -60,18 +64,15 @@ describe('src/core/MiddlewareManager.ts', () => {
await next(); await next();
expect(ctx.res).toBe(res); expect(ctx.res).toBe(res);
}); });
const flush = vi.fn(async (ctx) => {
ctx.res = res;
});
m.use('/test', midde); m.use('/test', midde);
await m.wrap(flush)(ctx1); await m.flush(ctx1);
expect(ctx1.res).toBe(res); expect(ctx1.res).toBe(res);
expect(midde).not.toBeCalled(); expect(midde).not.toBeCalled();
m.use('/test', midde); m.use('/test', midde);
await m.wrap(flush)(ctx2); await m.flush(ctx2);
expect(midde).toBeCalled(); expect(midde).toBeCalled();
}); });