test: clean up expected console output (#604)

This commit is contained in:
InfinityPacer
2026-07-31 06:17:52 +08:00
committed by GitHub
parent e67edd9feb
commit 544acc0758
9 changed files with 82 additions and 34 deletions

View File

@@ -1,5 +1,6 @@
import SiteAddEditDialog from '@/components/dialog/SiteAddEditDialog.vue'
import DialogCloseBtn from '@/@core/components/DialogCloseBtn.vue'
import { getActiveRequestsCount } from '@/utils/requestOptimizer'
import { fireEvent, screen, waitFor } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { createSite, createSiteDownloader } from '@tests/support/factories/site'
@@ -11,7 +12,7 @@ import {
} from '@tests/support/msw/handlers/site'
import { server } from '@tests/support/msw/server'
import { renderWithProviders } from '@tests/support/render'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
done: vi.fn(),
@@ -30,8 +31,10 @@ vi.mock('@/api/nprogress', () => ({
startNProgress: mocks.start,
}))
async function renderDialog(oper: 'add' | 'edit', siteid?: number) {
async function renderDialog(oper: 'add' | 'edit', siteid?: number, downloaders = [createSiteDownloader()]) {
const events = { close: vi.fn(), save: vi.fn() }
const downloaderRequested = vi.fn()
server.use(siteDownloadersHandler(downloaders, 200, downloaderRequested))
const result = await renderWithProviders(SiteAddEditDialog, {
props: {
modelValue: true,
@@ -42,22 +45,17 @@ async function renderDialog(oper: 'add' | 'edit', siteid?: number) {
},
global: { components: { VDialogCloseBtn: DialogCloseBtn } },
})
await waitFor(() => expect(downloaderRequested).toHaveBeenCalledOnce())
await waitFor(() => expect(getActiveRequestsCount()).toBe(0))
return { ...result, events }
}
describe('SiteAddEditDialog', () => {
beforeEach(() => {
server.use(siteDownloadersHandler())
})
it('creates a site with the entered form values and loaded downloader', async () => {
const saved = vi.fn()
server.use(
siteDownloadersHandler([createSiteDownloader({ name: '下载器 A' })]),
addSiteHandler({ success: true }, 200, saved),
)
server.use(addSiteHandler({ success: true }, 200, saved))
const user = userEvent.setup()
const { events } = await renderDialog('add')
const { events } = await renderDialog('add', undefined, [createSiteDownloader({ name: '下载器 A' })])
await user.type(screen.getByLabelText('站点地址'), 'https://new.example.com/')
await fireEvent.update(screen.getByLabelText('RSS地址'), 'https://new.example.com/rss')
@@ -125,6 +123,7 @@ describe('SiteAddEditDialog', () => {
})
it('restores progress and keeps the dialog open after an HTTP creation failure', async () => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
server.use(addSiteHandler({ message: '服务异常', success: false }, 500))
const user = userEvent.setup()
const { events } = await renderDialog('add')
@@ -135,6 +134,7 @@ describe('SiteAddEditDialog', () => {
await waitFor(() => expect(mocks.done).toHaveBeenCalledOnce())
expect(events.save).not.toHaveBeenCalled()
expect(mocks.toastSuccess).not.toHaveBeenCalled()
expect(consoleError).toHaveBeenCalledOnce()
})
it('restores numeric flags and API mode when editing a rate-limited site', async () => {
@@ -148,8 +148,8 @@ describe('SiteAddEditDialog', () => {
render: 0,
token: 'token',
})
server.use(siteDetailsHandler(site.id, site), siteDownloadersHandler([createSiteDownloader({ name: '下载器 A' })]))
await renderDialog('edit', site.id)
server.use(siteDetailsHandler(site.id, site))
await renderDialog('edit', site.id, [createSiteDownloader({ name: '下载器 A' })])
expect(await screen.findByText(site.name)).toBeInTheDocument()
expect(screen.getByLabelText('使用代理访问')).toBeChecked()
@@ -178,6 +178,7 @@ describe('SiteAddEditDialog', () => {
['business', 200, { message: '不允许更新', success: false }, '限流站点 更新失败:不允许更新'],
['HTTP', 500, { message: '服务异常', success: false }, '限流站点 更新失败!'],
])('keeps the dialog open on %s update failure', async (_case, status, response, message) => {
const consoleError = status === 500 ? vi.spyOn(console, 'error').mockImplementation(() => {}) : undefined
const site = createSite({ name: '限流站点' })
server.use(siteDetailsHandler(site.id, site), updateSiteHandler(response, status))
const { events } = await renderDialog('edit', site.id)
@@ -187,6 +188,7 @@ describe('SiteAddEditDialog', () => {
await waitFor(() => expect(mocks.toastError).toHaveBeenCalledWith(message))
expect(events.save).not.toHaveBeenCalled()
expect(mocks.done).toHaveBeenCalledOnce()
if (status === 500) expect(consoleError).toHaveBeenCalledOnce()
})
it('emits close from the dialog close button', async () => {

View File

@@ -90,6 +90,7 @@ describe('SiteCookieUpdateDialog', () => {
['detail', { detail: '站点不存在' }, '站点不存在'],
['message', { message: '认证服务异常' }, '认证服务异常'],
])('shows an HTTP %s response and restores pending state', async (_case, body, message) => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
server.use(http.post('http://localhost/api/v1/site/cookie/701', () => HttpResponse.json(body, { status: 500 })))
const user = userEvent.setup()
await renderDialog()
@@ -100,6 +101,7 @@ describe('SiteCookieUpdateDialog', () => {
await waitFor(() => expect(mocks.toastError).toHaveBeenCalledWith(`Cookie 站点 更新失败:${message}`))
expect(screen.getByRole('button', { name: '开始更新' })).toBeEnabled()
expect(screen.queryByRole('status')).not.toBeInTheDocument()
expect(consoleError).toHaveBeenCalledOnce()
})
it('disables the update action while the request is pending and restores it afterward', async () => {

View File

@@ -83,15 +83,23 @@ describe('SiteImportDialog', () => {
expect(screen.getByLabelText('选择文件')).toBeInTheDocument()
})
it.each([
['invalid JSON', brokenJsonFile(), '文件解析失败,请检查文件格式'],
['non-array JSON', jsonFile({ name: 'single' }), '文件格式无效,请检查文件内容'],
])('rejects %s', async (_case, file, message) => {
it('rejects invalid JSON', async () => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
await renderDialog()
await chooseFile(file)
await chooseFile(brokenJsonFile())
await waitFor(() => expect(mocks.toastError).toHaveBeenCalledWith(message))
await waitFor(() => expect(mocks.toastError).toHaveBeenCalledWith('文件解析失败,请检查文件格式'))
expect(screen.getByLabelText('选择文件')).toBeInTheDocument()
expect(consoleError).toHaveBeenCalledWith('Parse file error:', expect.any(SyntaxError))
})
it('rejects non-array JSON', async () => {
await renderDialog()
await chooseFile(jsonFile({ name: 'single' }))
await waitFor(() => expect(mocks.toastError).toHaveBeenCalledWith('文件格式无效,请检查文件内容'))
expect(screen.getByLabelText('选择文件')).toBeInTheDocument()
})
@@ -169,6 +177,7 @@ describe('SiteImportDialog', () => {
})
it('reports partial success and preserves the HTTP error message', async () => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const sites = [createSite({ name: '成功站点' }), createSite({ name: '失败站点' })]
let requestIndex = 0
server.use(
@@ -188,6 +197,7 @@ describe('SiteImportDialog', () => {
expect(mocks.toastError).toHaveBeenCalledWith('导入完成,成功 1 个,失败 1 个')
await fireEvent.click(screen.getByText('失败站点 - 错误详情'))
expect(await screen.findByText('Request failed with status code 500')).toBeInTheDocument()
expect(consoleError).toHaveBeenCalledWith('Import site 失败站点 failed:', expect.any(Error))
})
it('does not start a request when every record is invalid', async () => {

View File

@@ -2,6 +2,7 @@ import type { MediaInfo, MediaSeason, NotExistMediaInfo } from '@/api/types'
import DialogCloseBtn from '@/@core/components/DialogCloseBtn.vue'
import SubscribeSeasonDialog from '@/components/dialog/SubscribeSeasonDialog.vue'
import type { SubscribeMode } from '@/composables/useMediaSubscribe'
import { getActiveRequestsCount } from '@/utils/requestOptimizer'
import { fireEvent, screen, waitFor, within } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { createMediaInfo, createMediaSeason, createNotExistMediaInfo } from '@tests/support/factories/media'
@@ -14,7 +15,7 @@ import {
import { server } from '@tests/support/msw/server'
import { renderWithProviders } from '@tests/support/render'
import { flushPromises } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, describe, expect, it, vi } from 'vitest'
/** 季订阅弹窗测试属性。 */
interface SeasonDialogProps {
@@ -102,9 +103,8 @@ async function settleRequests() {
}
describe('SubscribeSeasonDialog', () => {
beforeEach(() => {
vi.spyOn(console, 'error').mockImplementation(() => {})
vi.spyOn(console, 'warn').mockImplementation(() => {})
afterEach(async () => {
await waitFor(() => expect(getActiveRequestsCount()).toBe(0))
})
it('loads the default TMDB seasons once with exact requests and renders season states', async () => {
@@ -250,7 +250,9 @@ describe('SubscribeSeasonDialog', () => {
},
'tmdb:source-7306',
],
] as const)('uses the %s media identifier without requesting TMDB groups', async (_label, overrides, mediaId) => {
] as const)('uses the %s media identifier without requesting TMDB groups', async (label, overrides, mediaId) => {
const consoleWarn =
label === 'source-only TMDB' ? vi.spyOn(console, 'warn').mockImplementation(() => {}) : undefined
const media = createTvMedia(overrides)
const requested = vi.fn()
server.use(
@@ -264,6 +266,7 @@ describe('SubscribeSeasonDialog', () => {
await settleRequests()
expect(requested).toHaveBeenCalledOnce()
expect(requested.mock.calls[0][0].searchParams.get('mediaid')).toBe(mediaId)
if (label === 'source-only TMDB') expect(consoleWarn).toHaveBeenCalledWith('tmdbid is not set or is empty')
})
it.each([
@@ -417,6 +420,7 @@ describe('SubscribeSeasonDialog', () => {
})
it('exits Loading after the default season request fails', async () => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const media = createTvMedia({ tmdb_id: 7310 })
const requested = vi.fn()
server.use(
@@ -431,9 +435,11 @@ describe('SubscribeSeasonDialog', () => {
expect(document.querySelector('.initial-loading-container')).not.toBeInTheDocument()
expect(screen.getByText(`${media.title} 未查询到季集信息`)).toBeInTheDocument()
expect(consoleError).toHaveBeenCalledOnce()
})
it('keeps season selection usable when the missing-state request fails', async () => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const media = createTvMedia({ tmdb_id: 7313 })
const missingRequested = vi.fn()
server.use(
@@ -452,9 +458,11 @@ describe('SubscribeSeasonDialog', () => {
expect(events.subscribe).toHaveBeenCalledOnce()
expect(events.subscribe.mock.calls[0][1]).toEqual({})
expect(consoleError).toHaveBeenCalledOnce()
})
it('keeps season selection usable when optional episode groups fail to load', async () => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const media = createTvMedia({ tmdb_id: 7314 })
const groupsRequested = vi.fn()
server.use(
@@ -473,6 +481,7 @@ describe('SubscribeSeasonDialog', () => {
expect(events.subscribe).toHaveBeenCalledOnce()
expect(screen.getByRole('button', { name: /^默认/ })).toBeInTheDocument()
expect(consoleError).toHaveBeenCalledOnce()
})
it('renders the successful empty state and emits close without submitting', async () => {

View File

@@ -1420,6 +1420,7 @@ describe('glass optical surface discovery', () => {
})
it('publishes only the current pending preparation failure identity', async () => {
const consoleWarn = vi.spyOn(console, 'warn').mockImplementation(() => {})
const three = await import('three')
const pendingWallpaperUrl = ref('')
const pendingWallpaperRevision = ref(0)
@@ -1445,6 +1446,10 @@ describe('glass optical surface discovery', () => {
pendingWallpaperUrl.value = 'https://example.com/wallpaper-next.jpg'
await vi.waitFor(() => expect(renderer?.failedWallpaperRevision.value).toBe(51))
expect(consoleWarn).toHaveBeenCalledWith(
'玻璃光学壁纸预备失败,继续使用当前纹理:',
expect.objectContaining({ message: 'pending source failed' }),
)
expect(renderer?.failedWallpaperUrl.value).toBe('https://example.com/wallpaper-next.jpg')
expect(renderer?.failedWallpaperPreparationKey.value).toContain('frosted:balanced:')

View File

@@ -7,6 +7,7 @@ import {
type SubscribeMode,
useMediaSubscribe,
} from '@/composables/useMediaSubscribe'
import { getActiveRequestsCount } from '@/utils/requestOptimizer'
import { fireEvent, screen, waitFor } from '@testing-library/vue'
import { createSubscribe, createSubscribeMovie, createSubscribeTv } from '@tests/support/factories/subscribe'
import {
@@ -18,8 +19,9 @@ import {
} from '@tests/support/msw/handlers/subscribe'
import { server } from '@tests/support/msw/server'
import { renderWithProviders } from '@tests/support/render'
import { flushPromises } from '@vue/test-utils'
import { defineComponent, ref } from 'vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
cacheStatus: vi.fn(),
@@ -223,6 +225,11 @@ describe('useMediaSubscribe entry flows', () => {
mocks.openSharedDialog.mockReturnValue({ close: vi.fn(), id: 1, updateProps: vi.fn() })
})
afterEach(async () => {
await flushPromises()
await waitFor(() => expect(getActiveRequestsCount()).toBe(0))
})
it('creates a normal movie subscription and synchronizes public state', async () => {
const media = createSubscribeMovie({ title: '普通电影', tmdb_id: 101, year: '2025' })
const created = vi.fn()
@@ -302,7 +309,7 @@ describe('useMediaSubscribe entry flows', () => {
expect(mocks.toastSuccess).toHaveBeenCalledOnce()
expect(mocks.toastError).not.toHaveBeenCalled()
expect(mocks.openSharedDialog).not.toHaveBeenCalled()
consoleLog.mockRestore()
expect(consoleLog).toHaveBeenCalledOnce()
})
it('opens the mode chooser for an existing movie and creates the selected mode', async () => {
@@ -544,7 +551,7 @@ describe('useMediaSubscribe entry flows', () => {
['business failure', 200, { message: 'duplicate', success: false }],
['HTTP failure', 500, { message: 'server down', success: false }],
])('keeps state unchanged when create returns a %s', async (_case, status, response) => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const consoleError = status === 500 ? vi.spyOn(console, 'error').mockImplementation(() => {}) : undefined
server.use(createSubscribeHandler(response, status))
await renderSubscribeHarness({ media: createSubscribeMovie({ tmdb_id: 107 }) })
@@ -555,14 +562,14 @@ describe('useMediaSubscribe entry flows', () => {
expect(mocks.cacheStatus).not.toHaveBeenCalled()
expect(mocks.openSharedDialog).not.toHaveBeenCalled()
expect(mocks.doneProgress).toHaveBeenCalledOnce()
consoleError.mockRestore()
if (status === 500) expect(consoleError).toHaveBeenCalledOnce()
})
it.each([
['business failure', 200, { message: 'delete rejected', success: false }],
['HTTP failure', 500, { message: 'server down', success: false }],
])('keeps subscription state when removal returns a %s', async (_case, status, response) => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const consoleError = status === 500 ? vi.spyOn(console, 'error').mockImplementation(() => {}) : undefined
const deleted = vi.fn()
server.use(deleteSubscribeByMediaHandler('tmdb:108', response, status, url => deleted(url)))
await renderSubscribeHarness({
@@ -586,14 +593,14 @@ describe('useMediaSubscribe entry flows', () => {
expect(screen.getByTestId('modes')).toHaveTextContent('"2":"best_version"')
expect(mocks.cacheStatus).not.toHaveBeenCalled()
expect(mocks.doneProgress).toHaveBeenCalledOnce()
consoleError.mockRestore()
if (status === 500) expect(consoleError).toHaveBeenCalledOnce()
})
it.each([
['business failure', 200, { message: 'update rejected', success: false }],
['HTTP failure', 500, { message: 'server down', success: false }],
])('keeps the subscribed mode when an update returns a %s', async (_case, status, response) => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
const consoleError = status === 500 ? vi.spyOn(console, 'error').mockImplementation(() => {}) : undefined
const media = createSubscribeTv({ title: '模式更新失败剧集', tmdb_id: 110 })
const updated = vi.fn()
server.use(
@@ -621,7 +628,7 @@ describe('useMediaSubscribe entry flows', () => {
expect(screen.getByTestId('modes')).toHaveTextContent('"2":"normal"')
expect(mocks.cacheStatus).not.toHaveBeenCalled()
expect(mocks.doneProgress).toHaveBeenCalledOnce()
consoleError.mockRestore()
if (status === 500) expect(consoleError).toHaveBeenCalledOnce()
})
it('maps a 404 query to missing and propagates other HTTP errors', async () => {

View File

@@ -15,6 +15,7 @@ describe('useVersionChecker', () => {
})
it('没有可用 Service Worker 时保留版本不一致的清缓存兜底', async () => {
const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
const { checkVersion } = useVersionChecker()
await checkVersion('version-that-never-matches-the-build')
@@ -35,5 +36,9 @@ describe('useVersionChecker', () => {
timeout: false,
}),
)
expect(consoleLog.mock.calls).toEqual([
[expect.stringMatching(/^\[VersionChecker\] 检测到版本不一致:/)],
['[VersionChecker] 无 Service Worker, 直接显示通知'],
])
})
})

View File

@@ -83,8 +83,6 @@ export function initializeRequestOptimizer(axiosInstance: any) {
return Promise.reject(error)
},
)
console.log('Request optimizer initialized - all requests will be auto-managed')
}
// 获取当前活跃请求数量(调试用)

View File

@@ -152,6 +152,7 @@ describe('dashboard media server cards', () => {
[MediaServerPlaying, 'mediaserver/playing', '暂无继续观看记录'],
[MediaServerLibrary, 'mediaserver/library', '暂无媒体库数据'],
])('keeps the successful empty snapshot when %s later fails', async (component, endpoint, emptyText) => {
const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
let endpointReads = 0
mocks.apiGet.mockImplementation((url: string) => {
if (url === 'system/setting/MediaServers') return { data: { value: [{ enabled: true, name: 'home' }] } }
@@ -172,6 +173,7 @@ describe('dashboard media server cards', () => {
expect(screen.getByText(emptyText)).toBeInTheDocument()
expect(screen.queryByText('媒体服务器数据加载失败')).not.toBeInTheDocument()
expect(await screen.findByRole('button', { name: '刷新失败,当前显示上次数据' })).toBeInTheDocument()
expect(consoleLog).toHaveBeenCalledOnce()
})
it.each([
@@ -179,6 +181,7 @@ describe('dashboard media server cards', () => {
[MediaServerPlaying, 'mediaserver/playing', '恢复的继续观看'],
[MediaServerLibrary, 'mediaserver/library', '恢复的媒体库'],
])('shows a retry state when %s fails without a snapshot', async (component, endpoint, recoveredText) => {
const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
let endpointReads = 0
let shouldFail = true
mocks.apiGet.mockImplementation((url: string) => {
@@ -202,6 +205,7 @@ describe('dashboard media server cards', () => {
await fireEvent.click(screen.getByRole('button', { name: '媒体服务器数据加载失败' }))
expect(await screen.findByText(recoveredText)).toBeInTheDocument()
expect(endpointReads).toBe(failedReads + 1)
expect(consoleLog).toHaveBeenCalledOnce()
})
it('restores the last successful library snapshot before F5 revalidation completes', async () => {
@@ -256,6 +260,7 @@ describe('dashboard media server cards', () => {
})
it('keeps continue-watching content when a warm refresh fails', async () => {
const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
const refresh = deferred<Array<{ id: string; title: string }>>()
let playingReads = 0
mocks.apiGet.mockImplementation((url: string) => {
@@ -281,9 +286,11 @@ describe('dashboard media server cards', () => {
await new Promise(resolve => setTimeout(resolve, 0))
expect(screen.getByText('旧继续观看')).toBeInTheDocument()
expect(screen.getByRole('button', { name: '刷新失败,当前显示上次数据' })).toBeInTheDocument()
expect(consoleLog).toHaveBeenCalledOnce()
})
it('keeps recent-library content when a warm refresh fails', async () => {
const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
const refresh = deferred<Array<{ id: string; title: string }>>()
let latestReads = 0
mocks.apiGet.mockImplementation((url: string) => {
@@ -307,9 +314,11 @@ describe('dashboard media server cards', () => {
await new Promise(resolve => setTimeout(resolve, 0))
expect(screen.getByText('旧最近入库')).toBeInTheDocument()
expect(screen.getByRole('button', { name: '刷新失败,当前显示上次数据' })).toBeInTheDocument()
expect(consoleLog).toHaveBeenCalledOnce()
})
it('keeps media-library content when a warm refresh fails', async () => {
const consoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
const refresh = deferred<Array<{ id: string; name: string }>>()
let libraryReads = 0
mocks.apiGet.mockImplementation((url: string) => {
@@ -330,6 +339,7 @@ describe('dashboard media server cards', () => {
await new Promise(resolve => setTimeout(resolve, 0))
expect(screen.getByText('旧媒体库')).toBeInTheDocument()
expect(screen.getByRole('button', { name: '刷新失败,当前显示上次数据' })).toBeInTheDocument()
expect(consoleLog).toHaveBeenCalledOnce()
})
it('replaces the media-library snapshot atomically after a warm refresh', async () => {