mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-29 03:56:51 +08:00
135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
import '@testing-library/jest-dom/vitest'
|
|
import { abortAllRequests } from '@/utils/requestOptimizer'
|
|
import { cleanup } from '@testing-library/vue'
|
|
import { HttpResponse, type HttpResponseInit, type JsonBodyType } from 'msw'
|
|
import { afterAll, afterEach, beforeAll, vi } from 'vitest'
|
|
import { createDataApiMock as buildDataApiMock } from './support/apiMock'
|
|
import { server } from './support/msw/server'
|
|
|
|
declare global {
|
|
// 测试文件中的 hoisted API mock 在应用模块加载前执行,因此通过 setup 注册统一适配器。
|
|
var createDataApiMock: typeof buildDataApiMock
|
|
}
|
|
|
|
globalThis.createDataApiMock = buildDataApiMock
|
|
|
|
const originalJsonResponse = HttpResponse.json.bind(HttpResponse)
|
|
|
|
/** 判断测试夹具是否已经表达了业务响应状态。 */
|
|
function isLegacyApiEnvelope(body: unknown): body is Record<string, unknown> & { success: boolean } {
|
|
return (
|
|
body !== null &&
|
|
typeof body === 'object' &&
|
|
!Array.isArray(body) &&
|
|
typeof (body as { success?: unknown }).success === 'boolean'
|
|
)
|
|
}
|
|
|
|
/** 识别 MSW 夹具中历史遗留的 Axios `{ data }` 响应壳。 */
|
|
function isLegacyDataWrapper(body: unknown): body is { data: unknown } {
|
|
if (!body || typeof body !== 'object' || Array.isArray(body)) return false
|
|
return Object.keys(body).length === 1 && 'data' in body
|
|
}
|
|
|
|
/**
|
|
* 将业务测试中的裸成功数据适配为当前后端统一 envelope。
|
|
*
|
|
* 低层客户端协议测试使用 Axios adapter,不经过这里;HTTP 错误保持原载荷,
|
|
* 便于继续覆盖 detail、Blob 和非标准错误响应的归一化行为。
|
|
*/
|
|
function installApiEnvelopeFixtureAdapter() {
|
|
Object.defineProperty(HttpResponse, 'json', {
|
|
configurable: true,
|
|
value: <BodyType extends JsonBodyType>(body?: BodyType | null, init: HttpResponseInit = {}) => {
|
|
const status = init.status ?? 200
|
|
if (status >= 400) return originalJsonResponse(body, init)
|
|
|
|
if (isLegacyApiEnvelope(body)) {
|
|
const envelope = body as Record<string, unknown> & { success: boolean }
|
|
return originalJsonResponse(
|
|
{
|
|
...envelope,
|
|
message: typeof envelope.message === 'string' ? envelope.message : '',
|
|
data: Object.hasOwn(envelope, 'data') ? envelope.data : null,
|
|
},
|
|
init,
|
|
)
|
|
}
|
|
|
|
if (isLegacyDataWrapper(body)) {
|
|
return originalJsonResponse({ success: true, message: '', data: body.data }, init)
|
|
}
|
|
|
|
return originalJsonResponse({ success: true, message: '', data: body ?? null }, init)
|
|
},
|
|
writable: true,
|
|
})
|
|
}
|
|
|
|
installApiEnvelopeFixtureAdapter()
|
|
|
|
class ResizeObserverStub implements ResizeObserver {
|
|
disconnect() {}
|
|
observe() {}
|
|
unobserve() {}
|
|
}
|
|
|
|
class IntersectionObserverStub implements IntersectionObserver {
|
|
readonly root = null
|
|
readonly rootMargin = '0px'
|
|
readonly thresholds = [0]
|
|
|
|
disconnect() {}
|
|
observe() {}
|
|
takeRecords(): IntersectionObserverEntry[] {
|
|
return []
|
|
}
|
|
unobserve() {}
|
|
}
|
|
|
|
Object.defineProperty(globalThis, 'ResizeObserver', {
|
|
configurable: true,
|
|
value: ResizeObserverStub,
|
|
writable: true,
|
|
})
|
|
|
|
Object.defineProperty(globalThis, 'IntersectionObserver', {
|
|
configurable: true,
|
|
value: IntersectionObserverStub,
|
|
writable: true,
|
|
})
|
|
|
|
Object.defineProperty(window, 'matchMedia', {
|
|
configurable: true,
|
|
value: (query: string): MediaQueryList => ({
|
|
addEventListener: vi.fn(),
|
|
addListener: vi.fn(),
|
|
dispatchEvent: vi.fn(),
|
|
matches: false,
|
|
media: query,
|
|
onchange: null,
|
|
removeEventListener: vi.fn(),
|
|
removeListener: vi.fn(),
|
|
}),
|
|
writable: true,
|
|
})
|
|
|
|
beforeAll(() => {
|
|
server.listen({ onUnhandledRequest: 'error' })
|
|
})
|
|
|
|
afterEach(() => {
|
|
cleanup()
|
|
abortAllRequests()
|
|
server.resetHandlers()
|
|
localStorage.clear()
|
|
sessionStorage.clear()
|
|
vi.useRealTimers()
|
|
vi.restoreAllMocks()
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
afterAll(() => {
|
|
server.close()
|
|
})
|