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
@@ -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 () => {
@@ -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 () => {
@@ -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 () => {
@@ -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 () => {