diff --git a/scripts/test.utils.ts b/scripts/test.utils.ts index 598bf5f..23b8667 100644 --- a/scripts/test.utils.ts +++ b/scripts/test.utils.ts @@ -15,6 +15,13 @@ export function captureError(fn: () => void): T { } } +export function cleanedStack(error: Error) { + if (error.stack) { + return error.stack.indexOf('at') === error.stack.indexOf('at /'); + } + return true; +} + export function noop() { return; } diff --git a/src/adapter.ts b/src/adapter.ts index 03bb2d3..c325977 100644 --- a/src/adapter.ts +++ b/src/adapter.ts @@ -168,13 +168,10 @@ export function getAdapterDefault(): AxiosAdapter | undefined { } export function createAdapter(platform: AxiosPlatform): AxiosAdapter { - assert(isPlainObject(platform), 'platform 需要是一个 object'); - assert(isFunction(platform.request), 'platform.request 需要是一个 function'); - assert(isFunction(platform.upload), 'platform.upload 需要是一个 function'); - assert( - isFunction(platform.download), - 'platform.download 需要是一个 function', - ); + assert(isPlainObject(platform), 'platform 不是一个 object'); + assert(isFunction(platform.request), 'platform.request 不是一个 function'); + assert(isFunction(platform.upload), 'platform.upload 不是一个 function'); + assert(isFunction(platform.download), 'platform.download 不是一个 function'); function adapterDefault( config: AxiosAdapterRequestConfig, @@ -204,17 +201,14 @@ export function createAdapter(platform: AxiosPlatform): AxiosAdapter { upload: AxiosAdapterUpload, baseOptions: AxiosAdapterBaseOptions, ): AxiosAdapterTask | void { - assert( - isPlainObject(baseOptions.data), - '上传文件时 data 需要是一个 object', - ); + assert(isPlainObject(baseOptions.data), 'data 不是一个 object'); assert( isString(baseOptions.data?.fileName), - '上传文件时 data.fileName 需要是一个 string', + 'data.fileName 不是一个 string', ); assert( isString(baseOptions.data?.filePath), - '上传文件时 data.filePath 需要是一个 string', + 'data.filePath 不是一个 string', ); const { fileName, filePath, fileType, ...formData } = diff --git a/src/core/createError.ts b/src/core/createError.ts index bd2394f..02e1d3f 100644 --- a/src/core/createError.ts +++ b/src/core/createError.ts @@ -1,3 +1,4 @@ +import { cleanStack } from '../helpers/error'; import { AxiosAdapterTask } from '../adapter'; import { AxiosRequestConfig, AxiosResponse, AxiosResponseError } from './Axios'; @@ -34,5 +35,7 @@ export function createError( request?: AxiosAdapterTask, response?: AxiosErrorResponse, ): AxiosError { - return new AxiosError(message, config, request, response); + const axiosError = new AxiosError(message, config, request, response); + cleanStack(axiosError); + return axiosError; } diff --git a/src/core/flattenHeaders.ts b/src/core/flattenHeaders.ts index 74a7f1b..bbf8abe 100644 --- a/src/core/flattenHeaders.ts +++ b/src/core/flattenHeaders.ts @@ -1,5 +1,5 @@ import { isPlainObject } from '../helpers/isTypes'; -import { omit } from '../helpers/omit'; +import { ignore } from '../helpers/ignore'; import { AxiosRequestConfig, AxiosRequestHeaders } from './Axios'; export function flattenHeaders( @@ -12,7 +12,7 @@ export function flattenHeaders( return { ...(config.headers.common ?? {}), ...(config.headers[config.method!.toLowerCase()] ?? {}), - ...omit( + ...ignore( config.headers, 'common', 'options', diff --git a/src/core/request.ts b/src/core/request.ts index 4c5f81b..5a60456 100644 --- a/src/core/request.ts +++ b/src/core/request.ts @@ -41,7 +41,7 @@ export function request( config: AxiosRequestConfig, ): Promise> { return new Promise((resolve, reject) => { - assert(isFunction(config.adapter), 'adapter 需要是一个 function'); + assert(isFunction(config.adapter), 'adapter 不是一个 function'); const adapterConfig: AxiosAdapterRequestConfig = { ...config, diff --git a/src/core/transformURL.ts b/src/core/transformURL.ts index f0c3baa..c96c872 100644 --- a/src/core/transformURL.ts +++ b/src/core/transformURL.ts @@ -1,6 +1,6 @@ import { buildURL } from '../helpers/buildURL'; import { combineURL } from '../helpers/combineURL'; -import { interpolation, isDynamicURL } from '../helpers/dynamicURL'; +import { dynamicURL } from '../helpers/dynamicURL'; import { isAbsoluteURL } from '../helpers/isAbsoluteURL'; import { AxiosRequestConfig } from './Axios'; @@ -8,10 +8,7 @@ export function transformURL(config: AxiosRequestConfig): string { let url = config.url ?? ''; if (!isAbsoluteURL(url)) url = combineURL(config.baseURL ?? '', url); - - if (isDynamicURL(url)) - url = interpolation(url, Object.assign({}, config.params, config.data)); - + url = dynamicURL(url, Object.assign({}, config.params, config.data)); url = buildURL(url, config.params, config.paramsSerializer); return url; diff --git a/src/helpers/buildURL.ts b/src/helpers/buildURL.ts index be8378b..8063c3d 100644 --- a/src/helpers/buildURL.ts +++ b/src/helpers/buildURL.ts @@ -1,65 +1,46 @@ -import { isDate, isNull, isPlainObject, isUndefined } from './isTypes'; +import { isArray, isDate, isNull, isPlainObject, isUndefined } from './isTypes'; export function buildURL( url = '', - params?: unknown, - paramsSerializer = paramsSerialization, + params?: AnyObject, + paramsSerializer = defaultSerializer, ): string { - if (!isPlainObject(params)) { - return url; - } - - return generateURL(url, paramsSerializer(params)); -} - -function generateURL(url: string, serializedParams: string): string { const hashIndex = url.indexOf('#'); - if (hashIndex !== -1) { url = url.slice(0, hashIndex); } - if (serializedParams === '') { - return url; + const paramsStr = paramsSerializer(params); + if (paramsStr) { + url = `${url}${url.indexOf('?') === -1 ? '?' : '&'}${paramsStr}`; } - const prefix = url.indexOf('?') === -1 ? '?' : '&'; - - serializedParams = `${prefix}${serializedParams}`; - - return `${url}${serializedParams}`; + return url; } -function paramsSerialization(params?: AnyObject): string { - if (!isPlainObject(params)) { - return ''; - } +function defaultSerializer(params?: AnyObject): string { + if (!isPlainObject(params)) return ''; const parts: string[] = []; - Object.keys(params).forEach((key): void => { - const value = params[key]; + function push(key: string, value: string) { + parts.push(`${encode(key)}=${encode(value)}`); + } - if (isNull(value) || isUndefined(value) || value !== value) { - return; - } - - if (Array.isArray(value)) { - key += '[]'; - } - - const values = [].concat(value); - - values.forEach((val: any): void => { + for (const [key, val] of Object.entries(params)) { + if (!isNull(val) && !isUndefined(val) && val === val) { if (isPlainObject(val)) { - val = JSON.stringify(val); + for (const [k, v] of Object.entries(val)) push(`${key}[${k}]`, v); + } else if (isArray(val)) { + const k = `${key}[]`; + for (const v of val) push(k, v); } else if (isDate(val)) { - val = (val as Date).toISOString(); + push(key, val.toISOString()); + } else { + push(key, val); } - - parts.push(`${encode(key)}=${encode(val)}`); - }); - }); + } + } return parts.join('&'); } diff --git a/src/helpers/combineURL.ts b/src/helpers/combineURL.ts index 123ec8d..b384df2 100644 --- a/src/helpers/combineURL.ts +++ b/src/helpers/combineURL.ts @@ -1,4 +1,4 @@ -const combineREG = /(^|[^:])\/{2,}/g; +const combineRE = /(^|[^:])\/{2,}/g; export function combineURL(baseURL: string, url: string): string { - return url ? `${baseURL}/${url}`.replace(combineREG, '$1/') : baseURL; + return url ? `${baseURL}/${url}`.replace(combineRE, '$1/') : baseURL; } diff --git a/src/helpers/deepMerge.ts b/src/helpers/deepMerge.ts index 4ecd4f3..4fb5818 100644 --- a/src/helpers/deepMerge.ts +++ b/src/helpers/deepMerge.ts @@ -3,20 +3,18 @@ import { isPlainObject } from './isTypes'; export function deepMerge(...objs: T[]): T { const result: AnyObject = {}; - objs.forEach((obj: AnyObject) => - Object.keys(obj).forEach((key) => { - const val = obj[key]; - const resultVal = result[key]; - - if (isPlainObject(resultVal) && isPlainObject(val)) { - result[key] = deepMerge(resultVal, val); - } else if (isPlainObject(val)) { - result[key] = deepMerge(val); + for (const obj of objs) { + for (const [key, val] of Object.entries(obj)) { + if (isPlainObject(val)) { + const rVal = result[key]; + result[key] = isPlainObject(rVal) + ? deepMerge(rVal, val) + : deepMerge(val); } else { result[key] = val; } - }), - ); + } + } return result as T; } diff --git a/src/helpers/dynamicURL.ts b/src/helpers/dynamicURL.ts index 4bd4a1f..19f7996 100644 --- a/src/helpers/dynamicURL.ts +++ b/src/helpers/dynamicURL.ts @@ -1,18 +1,4 @@ -import { isPlainObject } from './isTypes'; - -const dynamicREG = /\/?(:([a-zA-Z_$][\w-$]*))\/??/g; - -export function interpolation(url: string, sourceData?: unknown): string { - if (!isPlainObject(sourceData)) { - return url; - } - - return url.replace(dynamicREG, ($1, $2, $3) => - $1.replace($2, sourceData[$3]), - ); -} - -export function isDynamicURL(url: string): boolean { - dynamicREG.lastIndex = 0; - return dynamicREG.test(url); +const dynamicRE = /:([^/]+)/g; +export function dynamicURL(url: string, data: AnyObject = {}): string { + return url.replace(dynamicRE, (_, $2) => data[$2]); } diff --git a/src/helpers/error.ts b/src/helpers/error.ts index f8ba1ff..957a3a6 100644 --- a/src/helpers/error.ts +++ b/src/helpers/error.ts @@ -5,5 +5,19 @@ export function assert(condition: boolean, msg: string) { } export function throwError(msg: string): void { - throw new Error(`[axios-miniprogram]: ${msg}`); + const error = new Error(`[axios-miniprogram]: ${msg}`); + cleanStack(error); + throw error; +} + +export function cleanStack(error: Error) { + if (error.stack) { + const start = error.stack.indexOf('at'); + const end = error.stack.indexOf('at /'); + + if (start !== end) { + const removed = error.stack.slice(start, end); + error.stack = error.stack.replace(removed, ''); + } + } } diff --git a/src/helpers/ignore.ts b/src/helpers/ignore.ts new file mode 100644 index 0000000..af4437c --- /dev/null +++ b/src/helpers/ignore.ts @@ -0,0 +1,8 @@ +export function ignore( + obj: T, + ...keys: K[] +): Omit { + const result = { ...obj }; + for (const key of keys) delete result[key]; + return result; +} diff --git a/src/helpers/isAbsoluteURL.ts b/src/helpers/isAbsoluteURL.ts index b244389..4f83575 100644 --- a/src/helpers/isAbsoluteURL.ts +++ b/src/helpers/isAbsoluteURL.ts @@ -1,4 +1,4 @@ -const absoluteREG = /^([a-z][a-z\d+\-.]*:)?\/\//i; +const absoluteRE = /^([a-z][\w-.]*:)\/\//i; export function isAbsoluteURL(url: string): boolean { - return absoluteREG.test(url); + return absoluteRE.test(url); } diff --git a/src/helpers/isTypes.ts b/src/helpers/isTypes.ts index 6f20db0..5c2bf71 100644 --- a/src/helpers/isTypes.ts +++ b/src/helpers/isTypes.ts @@ -1,38 +1,36 @@ const _toString = Object.prototype.toString; -export function isArray(value: any): value is T[] { - return Array.isArray(value); -} - -export function isDate(date: any): date is Date { - return _toString.call(date) === '[object Date]'; -} - -export function isEmptyArray(value: any): value is [] { - return isArray(value) && value.length === 0; -} - -export function isEmptyObject(value: any): value is object { - return isPlainObject(value) && Object.keys(value).length === 0; -} - -// eslint-disable-next-line @typescript-eslint/ban-types -export function isFunction(value: any): value is T { - return typeof value === 'function'; -} - export function isNull(value: any): value is null { return value === null; } +export function isUndefined(value: any): value is undefined { + return typeof value === 'undefined'; +} + +export function isString(value: any): value is string { + return ( + typeof value === 'string' || _toString.call(value) === '[object String]' + ); +} + export function isPlainObject(value: any): value is object & AnyObject { return _toString.call(value) === '[object Object]'; } -export function isString(value: any): value is string { - return typeof value === 'string'; +export function isArray(value: any): value is T[] { + return Array.isArray(value); } -export function isUndefined(value: any): value is undefined { - return typeof value === 'undefined'; +export function isEmptyArray(value: any): value is [] { + return isArray(value) && value.length === 0; +} + +export function isDate(date: any): date is Date { + return _toString.call(date) === '[object Date]'; +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export function isFunction(value: any): value is T { + return typeof value === 'function'; } diff --git a/src/helpers/omit.ts b/src/helpers/omit.ts deleted file mode 100644 index 5f18be6..0000000 --- a/src/helpers/omit.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function omit( - obj: T, - ...keys: K[] -): Omit { - const res = { ...obj }; - keys.forEach((key: K) => delete res[key]); - return res; -} diff --git a/test/core/cancel.test.ts b/test/core/cancel.test.ts index b33a9ea..3ee286a 100644 --- a/test/core/cancel.test.ts +++ b/test/core/cancel.test.ts @@ -9,7 +9,7 @@ import { import axios from 'src/axios'; import { Cancel, isCancel, CancelToken, isCancelToken } from 'src/core/cancel'; -describe('测试 src/helpers/cancel.ts', () => { +describe('src/helpers/cancel.ts', () => { test('应该支持空参数', () => { const cancel = new Cancel(); diff --git a/test/core/createError.test.ts b/test/core/createError.test.ts new file mode 100644 index 0000000..005e300 --- /dev/null +++ b/test/core/createError.test.ts @@ -0,0 +1,27 @@ +import { describe, test, expect } from 'vitest'; +import { cleanedStack } from 'scripts/test.utils'; +import { createError } from 'src/core/createError'; + +describe('src/core/createError.ts', () => { + test('应该支持空参数', () => { + const config = {}; + const axiosError = createError('error', config); + + expect(axiosError.isAxiosError).toBeTruthy(); + expect(axiosError.message).toBe('error'); + expect(axiosError.config).toBe(config); + expect(cleanedStack(axiosError)).toBeTruthy(); + }); + + test('应该支持传入更多参数', () => { + const config = {}; + const request = {}; + const response = {}; + const axiosError = createError('error', config, request, response as any); + + expect(axiosError.message).toBe('error'); + expect(axiosError.config).toBe(config); + expect(axiosError.request).toBe(request); + expect(axiosError.response).toBe(response); + }); +}); diff --git a/test/helpers/buildURL.test.ts b/test/helpers/buildURL.test.ts new file mode 100644 index 0000000..8faddb8 --- /dev/null +++ b/test/helpers/buildURL.test.ts @@ -0,0 +1,63 @@ +import { describe, test, expect } from 'vitest'; +import { buildURL } from 'src/helpers/buildURL'; + +describe('src/helpers/buildURL.ts', () => { + test('应该支持空参数', () => { + expect(buildURL('/user')).toBe('/user'); + }); + + test('应该清理哈希值', () => { + expect(buildURL('/user#hash')).toBe('/user'); + }); + + test('应该对参数进行系列化', () => { + expect( + buildURL('/user#hash', { + v1: 1, + v2: undefined, + v3: null, + v4: '4', + v5: NaN, + }), + ).toBe('/user?v1=1&v4=4'); + + expect( + buildURL('/user?v1=1', { + v2: 2, + }), + ).toBe('/user?v1=1&v2=2'); + }); + + test('应该对数组进行系列化', () => { + expect( + buildURL('/user', { + arr: [1, 2], + }), + ).toBe('/user?arr[]=1&arr[]=2'); + }); + + test('应该对对象进行系列化', () => { + expect( + buildURL('/user', { + obj: { + k1: 1, + k2: 2, + }, + }), + ).toBe('/user?obj[k1]=1&obj[k2]=2'); + }); + + test('应该对日期进行系列化', () => { + const date = new Date(); + expect(buildURL('/user', { date })).toBe( + `/user?date=${date.toISOString()}`, + ); + }); + + test('应该支持自定义序列化器', () => { + expect(buildURL('/user', {}, () => 'v1=1&v2=2')).toBe('/user?v1=1&v2=2'); + expect(buildURL('/user?v1=1', {}, () => 'v2=2&v3=3')).toBe( + '/user?v1=1&v2=2&v3=3', + ); + }); +}); diff --git a/test/helpers/combineURL.test.ts b/test/helpers/combineURL.test.ts index a4d8fbb..644f4b1 100644 --- a/test/helpers/combineURL.test.ts +++ b/test/helpers/combineURL.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from 'vitest'; import { combineURL } from 'src/helpers/combineURL'; -describe('测试 src/helpers/combineURL.ts', () => { +describe('src/helpers/combineURL.ts', () => { test('应该直接返回第一个参数', () => { expect(combineURL('http://api.com', '')).toBe('http://api.com'); expect(combineURL('file://api.com', '')).toBe('file://api.com'); diff --git a/test/helpers/deepMerge.test.ts b/test/helpers/deepMerge.test.ts new file mode 100644 index 0000000..31bf985 --- /dev/null +++ b/test/helpers/deepMerge.test.ts @@ -0,0 +1,71 @@ +import { describe, test, expect } from 'vitest'; +import { deepMerge } from 'src/helpers/deepMerge'; + +describe('src/helpers/deepMerge.ts', () => { + test('应该支持空参数', () => { + expect(deepMerge()).toEqual({}); + }); + + test('应该直接返回第一个参数', () => { + expect( + deepMerge({ + v1: 1, + v2: [1], + v3: { v: 'v3' }, + v4: undefined, + v5: null, + v6: 'v6', + }), + ).toEqual({ + v1: 1, + v2: [1], + v3: { v: 'v3' }, + v4: undefined, + v5: null, + v6: 'v6', + }); + }); + + test('应该进行合并', () => { + expect( + deepMerge( + { + v1: 1, + v2: 2, + v3: 3, + }, + { + v2: 22, + v3: undefined, + v4: 4, + }, + ), + ).toEqual({ + v1: 1, + v2: 22, + v3: undefined, + v4: 4, + }); + }); + + test('应该合并对象里的对象', () => { + expect( + deepMerge( + { + v1: { v: 1 }, + v2: { v: 2 }, + v3: 3, + }, + { + v1: { vv: 11 }, + v2: 2, + v3: { v: 3 }, + }, + ), + ).toEqual({ + v1: { v: 1, vv: 11 }, + v2: 2, + v3: { v: 3 }, + }); + }); +}); diff --git a/test/helpers/dynamicURL.test.ts b/test/helpers/dynamicURL.test.ts new file mode 100644 index 0000000..b8a08bd --- /dev/null +++ b/test/helpers/dynamicURL.test.ts @@ -0,0 +1,22 @@ +import { describe, test, expect } from 'vitest'; +import { dynamicURL } from 'src/helpers/dynamicURL'; + +describe('src/helpers/dynamicURL.ts', () => { + test('应该替换关键字', () => { + expect(dynamicURL('http://api.com/user/:id', {})).toBe( + 'http://api.com/user/undefined', + ); + expect(dynamicURL('http://api.com/user/:id', { id: 1 })).toBe( + 'http://api.com/user/1', + ); + }); + + test('应该支持多个关键字', () => { + expect( + dynamicURL('http://api.com/users/name/:name/age/:age/list', { + name: 'my', + age: 18, + }), + ).toBe('http://api.com/users/name/my/age/18/list'); + }); +}); diff --git a/test/helpers/error.test.ts b/test/helpers/error.test.ts index 423f49c..042460a 100644 --- a/test/helpers/error.test.ts +++ b/test/helpers/error.test.ts @@ -1,13 +1,15 @@ import { describe, test, expect } from 'vitest'; -import { assert, throwError } from 'src/helpers/error'; +import { captureError, cleanedStack } from 'scripts/test.utils'; +import { assert, throwError, cleanStack } from 'src/helpers/error'; -describe('测试 src/helpers/error.ts', () => { +describe('src/helpers/error.ts', () => { test('第一个参数为 true 时应该无事发生', () => { expect(assert(true, '')).toBeUndefined(); }); test('第一个参数为 false 时应该抛出异常', () => { expect(() => assert(false, '')).toThrowError(); + expect(cleanedStack(captureError(() => assert(false, '')))).toBeTruthy(); }); test('应该抛出异常', () => { @@ -15,5 +17,17 @@ describe('测试 src/helpers/error.ts', () => { expect(() => throwError('error')).toThrowError( '[axios-miniprogram]: error', ); + expect(cleanedStack(captureError(() => throwError('error')))).toBeTruthy(); + }); + + test('应该清掉多余的错误栈', () => { + const ce = () => new Error(); + const error = ce(); + + expect(cleanedStack(error)).toBeFalsy(); + + cleanStack(error); + + expect(cleanedStack(error)).toBeTruthy(); }); }); diff --git a/test/helpers/ignore.test.ts b/test/helpers/ignore.test.ts new file mode 100644 index 0000000..4aa4d37 --- /dev/null +++ b/test/helpers/ignore.test.ts @@ -0,0 +1,33 @@ +import { describe, test, expect } from 'vitest'; +import { ignore } from 'src/helpers/ignore'; + +describe('src/helpers/ignore.ts', () => { + test('不应该改变传入的对象', () => { + expect( + ignore({ + v1: 1, + }), + ).toEqual({ + v1: 1, + }); + }); + + test('应该忽略指定键值', () => { + expect( + ignore( + { + v1: 1, + v2: {}, + v3: [], + v4: undefined, + v5: 5, + v6: null, + }, + 'v1', + 'v2', + 'v3', + 'v4', + ), + ).toEqual({ v5: 5, v6: null }); + }); +}); diff --git a/test/helpers/isAbsoluteURL.test.ts b/test/helpers/isAbsoluteURL.test.ts new file mode 100644 index 0000000..eda4a17 --- /dev/null +++ b/test/helpers/isAbsoluteURL.test.ts @@ -0,0 +1,20 @@ +import { describe, test, expect } from 'vitest'; +import { isAbsoluteURL } from 'src/helpers/isAbsoluteURL'; + +describe('src/helpers/isAbsoluteURL.ts', () => { + test('应该不是绝对路径', () => { + expect(isAbsoluteURL('user')).toBeFalsy(); + expect(isAbsoluteURL('/user')).toBeFalsy(); + expect(isAbsoluteURL('//user')).toBeFalsy(); + expect(isAbsoluteURL('://user')).toBeFalsy(); + }); + + test('应该是绝对路径', () => { + expect(isAbsoluteURL('http://user')).toBeTruthy(); + expect(isAbsoluteURL('HTTP://user')).toBeTruthy(); + expect(isAbsoluteURL('https://user')).toBeTruthy(); + expect(isAbsoluteURL('custom://user')).toBeTruthy(); + expect(isAbsoluteURL('custom-v1.0://user')).toBeTruthy(); + expect(isAbsoluteURL('custom_v1.0://user')).toBeTruthy(); + }); +}); diff --git a/test/helpers/isTypes.test.ts b/test/helpers/isTypes.test.ts new file mode 100644 index 0000000..beaee11 --- /dev/null +++ b/test/helpers/isTypes.test.ts @@ -0,0 +1,63 @@ +import { describe, test, expect } from 'vitest'; +import { + isArray, + isDate, + isEmptyArray, + isPlainObject, + isFunction, + isNull, + isUndefined, + isString, +} from 'src/helpers/isTypes'; + +describe('src/helpers/isTypes.ts', () => { + test('应该能判断是数组', () => { + expect(isArray(new Array(1))).toBeTruthy(); + expect(isArray([])).toBeTruthy(); + expect(isArray([1])).toBeTruthy(); + }); + + test('应该能判断是空数组', () => { + expect(isEmptyArray([1])).toBeFalsy(); + expect(isArray(new Array(0))).toBeTruthy(); + expect(isEmptyArray([])).toBeTruthy(); + }); + + test('应该能判断是普通对象', () => { + expect(isPlainObject(new String())).toBeFalsy(); + expect(isPlainObject(new Function())).toBeFalsy(); + expect(isPlainObject({ v: 1 })).toBeTruthy(); + expect(isPlainObject(new Object())).toBeTruthy(); + }); + + test('应该能判断是日期', () => { + expect(isDate({})).toBeFalsy(); + expect(isDate(new Date())).toBeTruthy(); + }); + + test('应该能判断是函数', () => { + expect(isFunction(() => null)).toBeTruthy(); + expect( + isFunction(function () { + return; + }), + ).toBeTruthy(); + expect(isFunction(new Function())).toBeTruthy(); + }); + + test('应该能判断是 Null', () => { + expect(isNull(undefined)).toBeFalsy(); + expect(isNull(null)).toBeTruthy(); + }); + + test('应该能判断是 Undefined', () => { + expect(isUndefined(null)).toBeFalsy(); + expect(isUndefined(undefined)).toBeTruthy(); + }); + + test('应该能判断是字符串', () => { + expect(isString(new String())).toBeTruthy(); + expect(isString('')).toBeTruthy(); + expect(isString(``)).toBeTruthy(); + }); +});