【初始化】前端工程项目

This commit is contained in:
chudong
2025-05-09 15:11:21 +08:00
parent c012704c9a
commit d7c556c3b0
524 changed files with 55595 additions and 112 deletions

View File

@@ -0,0 +1,429 @@
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { nextTick, ref } from 'vue'
import { useAxios, MiddlewareStage } from '../src/axios'
import axios from 'axios'
// 模拟axios
vi.mock('axios', () => {
return {
default: {
create: vi.fn(() => ({
interceptors: {
request: {
use: vi.fn(),
eject: vi.fn(),
},
response: {
use: vi.fn(),
eject: vi.fn(),
},
},
request: vi.fn(),
})),
isCancel: vi.fn((error) => error && error.__CANCEL__),
CancelToken: {
source: vi.fn(() => ({
token: 'mock-token',
cancel: vi.fn(),
})),
},
},
}
})
describe('useAxios', () => {
let mockResponse
beforeEach(() => {
vi.clearAllMocks()
mockResponse = {
data: { message: 'success' },
status: 200,
statusText: 'OK',
headers: {},
config: {},
}
// 设置axios.request的模拟实现
axios.create().request.mockImplementation(() => Promise.resolve(mockResponse))
})
afterEach(() => {
vi.clearAllMocks()
})
it('应该返回正确的响应数据', async () => {
const { data, error, loading, request } = useAxios()
expect(loading.value).toBe(false)
expect(data.value).toBe(null)
expect(error.value).toBe(null)
const promise = request({ url: '/test' })
expect(loading.value).toBe(true)
await promise
expect(loading.value).toBe(false)
expect(data.value).toEqual({ message: 'success' })
expect(error.value).toBe(null)
expect(axios.create().request).toHaveBeenCalledWith(expect.objectContaining({ url: '/test' }))
})
it('当请求失败时应该设置错误信息', async () => {
const mockError = new Error('Request failed')
axios.create().request.mockImplementation(() => Promise.reject(mockError))
const { data, error, loading, request } = useAxios()
try {
await request({ url: '/test' })
} catch (e) {
// 预期抛出错误
}
expect(loading.value).toBe(false)
expect(data.value).toBe(null)
expect(error.value).toBe(mockError)
})
it('应该支持请求重试', async () => {
// 前两次请求失败,第三次成功
axios
.create()
.request.mockImplementationOnce(() => Promise.reject(new Error('Retry 1')))
.mockImplementationOnce(() => Promise.reject(new Error('Retry 2')))
.mockImplementationOnce(() => Promise.resolve(mockResponse))
const { data, request } = useAxios()
await request({
url: '/test',
retry: true,
retryTimes: 3,
})
expect(axios.create().request).toHaveBeenCalledTimes(3)
expect(data.value).toEqual({ message: 'success' })
})
it('应该能够取消请求', async () => {
// 模拟取消功能
const cancelError = new Error('Request canceled')
cancelError.__CANCEL__ = true
const sourceCancel = axios.CancelToken.source().cancel
sourceCancel.mockImplementation(() => {
axios.create().request.mockImplementation(() => Promise.reject(cancelError))
})
const { loading, request, cancel } = useAxios()
const requestPromise = request({ url: '/test', requestId: 'test-request' })
cancel('test-request')
try {
await requestPromise
} catch (e) {
// 预期抛出错误
}
expect(loading.value).toBe(false)
expect(sourceCancel).toHaveBeenCalled()
})
it('应该支持中间件机制', async () => {
const requestMiddleware = vi.fn()
const responseMiddleware = vi.fn()
const { request, use } = useAxios()
// 添加请求中间件
use({
id: 'request-middleware',
stage: MiddlewareStage.REQUEST,
handler: requestMiddleware,
})
// 添加响应中间件
use({
id: 'response-middleware',
stage: MiddlewareStage.RESPONSE,
handler: responseMiddleware,
})
await request({ url: '/test' })
expect(requestMiddleware).toHaveBeenCalled()
expect(responseMiddleware).toHaveBeenCalled()
})
it('应该缓存请求结果', async () => {
const { request, clearCache } = useAxios()
await request({ url: '/cached', cache: true })
await request({ url: '/cached', cache: true })
// 由于缓存实际axios请求应该只执行一次
expect(axios.create().request).toHaveBeenCalledTimes(1)
// 清除缓存后再次请求应该执行新的请求
clearCache()
await request({ url: '/cached', cache: true })
expect(axios.create().request).toHaveBeenCalledTimes(2)
})
it('应该支持自定义实例配置', async () => {
const customConfig = {
baseURL: 'https://api.example.com',
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'Custom-Value',
},
}
const { request } = useAxios({
options: customConfig,
})
await request({ url: '/test' })
// 验证创建实例时使用了自定义配置
expect(axios.create).toHaveBeenCalledWith(customConfig)
})
it('应该支持请求级别的配置覆盖实例配置', async () => {
const { request } = useAxios({
options: {
baseURL: 'https://api.example.com',
timeout: 5000,
},
})
await request({
url: '/test',
timeout: 10000, // 覆盖实例的timeout
headers: {
'X-Request-Header': 'Request-Value',
},
})
// 验证请求参数包含覆盖的配置
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
url: '/test',
timeout: 10000,
headers: {
'X-Request-Header': 'Request-Value',
},
}),
)
})
it('应该支持删除中间件', async () => {
const requestMiddleware = vi.fn()
const { request, use, eject } = useAxios()
// 添加中间件
use({
id: 'test-middleware',
stage: MiddlewareStage.REQUEST,
handler: requestMiddleware,
})
// 删除中间件
eject('test-middleware')
await request({ url: '/test' })
// 由于中间件已被删除,不应该被调用
expect(requestMiddleware).not.toHaveBeenCalled()
})
it('应该支持请求前的数据转换', async () => {
const { request, use } = useAxios()
// 添加请求转换中间件
use({
id: 'transform-request',
stage: MiddlewareStage.REQUEST,
handler: (config) => {
if (config.data) {
config.data = { ...config.data, transformed: true }
}
return config
},
})
await request({
url: '/test',
method: 'post',
data: { original: true },
})
// 验证请求数据被转换
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
data: { original: true, transformed: true },
}),
)
})
it('应该支持响应数据转换', async () => {
const { data, request, use } = useAxios()
// 添加响应转换中间件
use({
id: 'transform-response',
stage: MiddlewareStage.RESPONSE,
handler: (response) => {
response.data = { ...response.data, transformed: true }
return response
},
})
await request({ url: '/test' })
// 验证响应数据被转换
expect(data.value).toEqual({
message: 'success',
transformed: true,
})
})
it('应该支持响应错误处理中间件', async () => {
const mockError = new Error('Request failed')
axios.create().request.mockImplementation(() => Promise.reject(mockError))
const errorHandler = vi.fn().mockImplementation(() => {
// 将错误转换为成功响应
return {
data: { recovered: true },
status: 200,
statusText: 'OK',
headers: {},
config: {},
}
})
const { data, error, request, use } = useAxios()
// 添加错误处理中间件
use({
id: 'error-handler',
stage: MiddlewareStage.ERROR,
handler: errorHandler,
})
await request({ url: '/test' })
// 验证错误被处理且转换为成功响应
expect(errorHandler).toHaveBeenCalled()
expect(data.value).toEqual({ recovered: true })
expect(error.value).toBe(null)
})
it('应该支持请求防抖', async () => {
vi.useFakeTimers()
const { request } = useAxios()
// 短时间内发起多个相同请求
request({ url: '/debounced', debounce: true, debounceTime: 100 })
request({ url: '/debounced', debounce: true, debounceTime: 100 })
request({ url: '/debounced', debounce: true, debounceTime: 100 })
// 前进100ms触发防抖后的请求
vi.advanceTimersByTime(100)
await Promise.resolve() // 等待微任务队列
// 仅发送一次请求
expect(axios.create().request).toHaveBeenCalledTimes(1)
vi.useRealTimers()
})
it('应该支持请求节流', async () => {
vi.useFakeTimers()
const { request } = useAxios()
// 立即执行第一个请求
request({ url: '/throttled', throttle: true, throttleTime: 100 })
// 这些请求应该被忽略
request({ url: '/throttled', throttle: true, throttleTime: 100 })
request({ url: '/throttled', throttle: true, throttleTime: 100 })
// 验证立即执行了第一个请求
expect(axios.create().request).toHaveBeenCalledTimes(1)
// 重置模拟
axios.create().request.mockClear()
// 前进100ms节流时间结束
vi.advanceTimersByTime(100)
// 再次发送请求
request({ url: '/throttled', throttle: true, throttleTime: 100 })
// 验证发送了新的请求
expect(axios.create().request).toHaveBeenCalledTimes(1)
vi.useRealTimers()
})
it('应该支持同时处理多个并发请求', async () => {
const { loading, request } = useAxios()
// 发起多个请求
const promise1 = request({ url: '/request1' })
const promise2 = request({ url: '/request2' })
const promise3 = request({ url: '/request3' })
expect(loading.value).toBe(true)
// 等待所有请求完成
await Promise.all([promise1, promise2, promise3])
expect(loading.value).toBe(false)
expect(axios.create().request).toHaveBeenCalledTimes(3)
})
it('应该支持通过配置禁用全局loading状态', async () => {
const { loading, request } = useAxios({
options: {
useGlobalLoading: false,
},
})
// 初始状态
expect(loading.value).toBe(false)
// 发起请求
const promise = request({ url: '/test' })
// loading状态应该仍为false
expect(loading.value).toBe(false)
await promise
expect(loading.value).toBe(false)
})
it('应该支持通过URL参数进行请求', async () => {
const { request } = useAxios()
await request({
url: '/test',
params: {
id: 123,
filter: 'active',
sort: 'desc',
},
})
// 验证请求包含参数
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
url: '/test',
params: { id: 123, filter: 'active', sort: 'desc' },
}),
)
})
it('应该支持自定义请求头', async () => {
const { request } = useAxios()
await request({
url: '/test',
headers: {
Authorization: 'Bearer token123',
'Accept-Language': 'zh-CN',
},
})
// 验证请求包含自定义头
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
headers: {
Authorization: 'Bearer token123',
'Accept-Language': 'zh-CN',
},
}),
)
})
it('应该支持不同的响应类型', async () => {
mockResponse.data = 'blob-data'
const { data, request } = useAxios()
await request({
url: '/download',
responseType: 'blob',
})
// 验证请求参数
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
responseType: 'blob',
}),
)
// 验证获取了正确的响应
expect(data.value).toBe('blob-data')
})
it('应该支持请求超时配置', async () => {
const mockTimeoutError = new Error('timeout')
mockTimeoutError.code = 'ECONNABORTED'
axios.create().request.mockImplementation(() => Promise.reject(mockTimeoutError))
const { error, request } = useAxios()
try {
await request({
url: '/test',
timeout: 1000,
})
} catch (e) {
// 预期抛出错误
}
// 验证请求参数
expect(axios.create().request).toHaveBeenCalledWith(
expect.objectContaining({
timeout: 1000,
}),
)
// 验证错误状态
expect(error.value).toBe(mockTimeoutError)
})
it('应该支持动态更新请求选项', async () => {
const defaultOptions = ref({
baseURL: 'https://api.example.com',
headers: {
'Content-Type': 'application/json',
},
})
const { request } = useAxios({
options: defaultOptions,
})
// 发起第一个请求
await request({ url: '/test1' })
// 验证使用了初始配置
expect(axios.create).toHaveBeenCalledWith(defaultOptions.value)
// 更新配置
defaultOptions.value = {
...defaultOptions.value,
baseURL: 'https://api2.example.com',
timeout: 3000,
}
await nextTick()
// 发起第二个请求
await request({ url: '/test2' })
// 验证使用了更新后的配置
expect(axios.create).toHaveBeenCalledWith(defaultOptions.value)
})
})
//# sourceMappingURL=axios.spec.js.map