test(site): cover site resource dialog (#563)

This commit is contained in:
InfinityPacer
2026-07-19 17:05:56 +08:00
committed by GitHub
parent 0d094b01b5
commit 2542339733
9 changed files with 558 additions and 21 deletions
-5
View File
@@ -380,11 +380,6 @@
"count": 1
}
},
"src/components/dialog/SiteResourceDialog.vue": {
"@typescript-eslint/no-unused-vars": {
"count": 2
}
},
"src/components/dialog/SiteStatisticsDialog.vue": {
"@typescript-eslint/no-explicit-any": {
"count": 1
+48 -14
View File
@@ -45,6 +45,11 @@ const resourcePage = ref(1)
// 加载状态
const resourceLoading = ref(false)
const resourceError = ref(false)
// 只有最后发起的搜索可以更新资源数据、请求状态和移动端搜索面板。
let resourceRequestId = 0
// 移动端搜索栏是否展开
const mobileSearchExpanded = ref(false)
@@ -134,35 +139,46 @@ async function addDownload(_torrent: TorrentInfo) {
}
// 添加下载成功
function addDownloadSuccess(_url: string) {
function addDownloadSuccess() {
addDownloadDialog.value = false
}
// 添加下载失败
function addDownloadError(_error: string) {
function addDownloadError() {
addDownloadDialog.value = false
}
// 调用API,查询站点资源
async function getResourceList() {
const requestId = ++resourceRequestId
resourceLoading.value = true
resourceError.value = false
resourcePage.value = 1
try {
resourceDataList.value = await api.get(`site/resource/${props.site?.id}`, {
const resources: TorrentInfo[] = await api.get(`site/resource/${props.site?.id}`, {
params: {
keyword: keyword.value,
cat: selectCategory.value?.join(','),
},
})
if (requestId === resourceRequestId) {
resourceDataList.value = resources
}
} catch (error) {
console.error(error)
}
if (requestId === resourceRequestId) {
console.error(error)
resourceError.value = true
}
} finally {
if (requestId === resourceRequestId) {
resourceLoading.value = false
resourceLoading.value = false
if (isMobileLayout.value) {
mobileSearchExpanded.value = false
if (isMobileLayout.value) {
mobileSearchExpanded.value = false
}
}
}
}
@@ -280,10 +296,7 @@ onMounted(() => {
</VCol>
</VRow>
<div
v-if="resourceTotalItems > 0"
class="d-flex justify-space-between align-center flex-wrap gap-2 mt-3"
>
<div v-if="resourceTotalItems > 0" class="d-flex justify-space-between align-center flex-wrap gap-2 mt-3">
<div class="text-body-2 text-medium-emphasis">
{{ resultSummaryText }}
</div>
@@ -350,7 +363,14 @@ onMounted(() => {
/>
</VCol>
<VCol cols="12" class="d-flex gap-2">
<VBtn color="primary" variant="flat" block rounded="lg" class="site-resource-search-btn" @click="getResourceList">
<VBtn
color="primary"
variant="flat"
block
rounded="lg"
class="site-resource-search-btn"
@click="getResourceList"
>
{{ t('dialog.siteResource.search') }}
</VBtn>
<VBtn variant="text" rounded="lg" @click="closeMobileSearch">
@@ -366,6 +386,20 @@ onMounted(() => {
</div>
<VCardText class="site-resource-content px-0 py-0 my-0">
<VAlert
v-if="resourceError && !resourceLoading"
type="error"
variant="tonal"
class="mx-3 mb-3"
:text="t('dialog.siteResource.loadFailed')"
>
<template #append>
<VBtn variant="text" color="error" @click="getResourceList">
{{ t('common.retry') }}
</VBtn>
</template>
</VAlert>
<VDataTable
v-if="display.mdAndUp.value"
v-model:page="resourcePage"
@@ -0,0 +1,428 @@
import type { TorrentInfo } from '@/api/types'
import SiteResourceDialog from '@/components/dialog/SiteResourceDialog.vue'
import i18n from '@/plugins/i18n'
import { getActiveRequestsCount } from '@/utils/requestOptimizer'
import { screen, waitFor } from '@testing-library/vue'
import userEvent from '@testing-library/user-event'
import { createSite, createSiteCategory, createTorrentInfo } from '@tests/support/factories/site'
import { siteApiUrls, siteCategoriesHandler, siteResourcesHandler } from '@tests/support/msw/handlers/site'
import { server } from '@tests/support/msw/server'
import { renderWithProviders } from '@tests/support/render'
import { HttpResponse, http } from 'msw'
import { defineComponent, h, type Component, type PropType } from 'vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
const AddDownloadDialogStub = defineComponent({
name: 'AddDownloadDialog',
props: {
torrent: Object,
},
emits: ['close', 'done', 'error', 'update:modelValue'],
template: `
<section data-testid="add-download-dialog">
{{ torrent?.title }}
<button type="button" @click="$emit('done', 'url')">done</button>
<button type="button" @click="$emit('error', 'error')">error</button>
<button type="button" @click="$emit('close')">close-download</button>
</section>
`,
})
const DialogStub = defineComponent({
name: 'VDialog',
template: '<div role="dialog"><slot /></div>',
})
const ProgressiveCardGridStub = defineComponent({
name: 'ProgressiveCardGrid',
props: {
getItemKey: { type: Function as PropType<(item: TorrentInfo, index: number) => string>, required: true },
items: { type: Array as PropType<TorrentInfo[]>, required: true },
},
setup(props, { slots }) {
return () =>
h(
'section',
{ 'data-testid': 'progressive-grid' },
props.items.map((item, index) =>
h('article', { 'data-resource-key': props.getItemKey(item, index) }, slots.default?.({ item })),
),
)
},
})
const DataTableStub = defineComponent({
name: 'VDataTable',
props: {
items: { type: Array as PropType<TorrentInfo[]>, required: true },
itemsPerPage: { type: Number, required: true },
loading: Boolean,
page: { type: Number, required: true },
},
emits: ['update:itemsPerPage', 'update:page'],
setup(props, { emit, slots }) {
return () =>
h('section', { 'data-loading': String(props.loading), 'data-testid': 'resource-table' }, [
h('span', { 'data-testid': 'resource-page' }, String(props.page)),
h('button', { onClick: () => emit('update:page', 4), type: 'button' }, 'page-4'),
h('button', { onClick: () => emit('update:itemsPerPage', 100), type: 'button' }, 'per-page-100'),
...props.items.flatMap(item => slots['item.title']?.({ item }) ?? []),
props.items.length === 0 ? slots['no-data']?.({}) : null,
])
},
})
function createDeferred<T>() {
let resolve!: (value: T) => void
const promise = new Promise<T>(done => {
resolve = done
})
return { promise, resolve }
}
function setViewport(width: number) {
Object.defineProperty(window, 'innerWidth', { configurable: true, value: width, writable: true })
window.dispatchEvent(new Event('resize'))
}
async function renderDialog(
site = createSite({ id: 501, name: '资源测试站' }),
stubs: Record<string, boolean | Component> = {},
) {
const close = vi.fn()
const result = await renderWithProviders(SiteResourceDialog, {
props: { site, onClose: close },
global: {
stubs: {
AddDownloadDialog: AddDownloadDialogStub,
ProgressiveCardGrid: ProgressiveCardGridStub,
VDialog: DialogStub,
...stubs,
},
},
})
return { ...result, close, site }
}
describe('SiteResourceDialog', () => {
beforeEach(() => {
vi.spyOn(console, 'error').mockImplementation(() => {})
vi.spyOn(console, 'warn').mockImplementation(() => {})
setViewport(1280)
})
it.each([
['success', 200],
['HTTP failure', 500],
] as const)('keeps the newest mobile search state when an older request ends with %s', async (_case, oldStatus) => {
setViewport(390)
const oldResponse = createDeferred<void>()
const latestResponse = createDeferred<TorrentInfo[]>()
const oldRequested = vi.fn()
const latestRequested = vi.fn()
const latestResource = createTorrentInfo({ title: '新条件结果' })
server.use(
siteCategoriesHandler(501, [createSiteCategory()]),
http.get(siteApiUrls.resources(501), async ({ request }) => {
const keyword = new URL(request.url).searchParams.get('keyword')
if (keyword) {
latestRequested()
return HttpResponse.json(await latestResponse.promise)
}
oldRequested()
await oldResponse.promise
return oldStatus === 200
? HttpResponse.json([createTorrentInfo({ title: '旧条件结果' })])
: HttpResponse.json({ detail: 'stale failure' }, { status: oldStatus })
}),
)
const user = userEvent.setup()
const { container } = await renderDialog()
await waitFor(() => expect(oldRequested).toHaveBeenCalledOnce())
await user.click(container.querySelector('.site-resource-mobile-search__toggle') as HTMLElement)
const keywordFields = screen.getAllByLabelText('搜索关键字')
const keyword = keywordFields[keywordFields.length - 1]
await user.type(keyword, 'new')
const searchButtons = screen.getAllByRole('button', { name: /搜索/ })
await user.click(searchButtons[searchButtons.length - 1])
await waitFor(() => expect(latestRequested).toHaveBeenCalledOnce())
await waitFor(() => expect(getActiveRequestsCount()).toBe(2))
oldResponse.resolve()
await waitFor(() => expect(getActiveRequestsCount()).toBe(1))
expect(screen.getByText('加载中...')).toBeInTheDocument()
expect(container.querySelector('.site-resource-filter-panel')).toBeInTheDocument()
expect(screen.queryByText('资源加载失败,请重试')).not.toBeInTheDocument()
if (oldStatus === 500) expect(console.error).not.toHaveBeenCalled()
latestResponse.resolve([latestResource])
expect(await screen.findByText('新条件结果')).toBeInTheDocument()
await waitFor(() => expect(getActiveRequestsCount()).toBe(0))
expect(screen.queryByText('旧条件结果')).not.toBeInTheDocument()
expect(screen.queryByText('加载中...')).not.toBeInTheDocument()
expect(screen.queryByLabelText('搜索关键字')).not.toBeInTheDocument()
expect(screen.queryByText('资源加载失败,请重试')).not.toBeInTheDocument()
})
it('shows a retry action after resource loading fails and recovers on the same query', async () => {
let attempts = 0
server.use(
siteCategoriesHandler(501, []),
http.get(siteApiUrls.resources(501), () => {
attempts += 1
if (attempts === 1) return HttpResponse.json({ detail: 'temporary failure' }, { status: 500 })
return HttpResponse.json([createTorrentInfo({ title: '重试恢复结果' })])
}),
)
const user = userEvent.setup()
await renderDialog()
await user.click(await screen.findByRole('button', { name: '重试' }))
expect(await screen.findByText('重试恢复结果')).toBeInTheDocument()
expect(attempts).toBe(2)
})
it('loads categories and resources from their direct-array endpoints', async () => {
const categoryRequests: URL[] = []
const resourceRequests: URL[] = []
server.use(
siteCategoriesHandler(501, [createSiteCategory({ desc: '高清电影', id: 11 })], 200, url =>
categoryRequests.push(url),
),
siteResourcesHandler(501, [createTorrentInfo({ title: '首载资源' })], 200, url => resourceRequests.push(url)),
)
await renderDialog()
expect(await screen.findByText('首载资源')).toBeInTheDocument()
expect(categoryRequests).toHaveLength(1)
expect(resourceRequests).toHaveLength(1)
expect(resourceRequests[0].searchParams.get('keyword')).toBeNull()
expect(resourceRequests[0].searchParams.get('cat')).toBe('')
})
it('sends the exact keyword and selected category ids on every search', async () => {
const requests: URL[] = []
server.use(
siteCategoriesHandler(501, [
createSiteCategory({ desc: '电影', id: 11 }),
createSiteCategory({ cat: 'tv', desc: '剧集', id: 22 }),
]),
siteResourcesHandler(501, [], 200, url => requests.push(url)),
)
const user = userEvent.setup()
await renderDialog()
await waitFor(() => expect(requests).toHaveLength(1))
await user.type(screen.getByLabelText('搜索关键字'), '2160p')
await user.click(screen.getByLabelText('资源分类'))
await user.click(await screen.findByRole('option', { name: '电影' }))
await user.click(await screen.findByRole('option', { name: '剧集' }))
await user.click(screen.getByRole('button', { name: /搜索/ }))
await waitFor(() => expect(requests).toHaveLength(2))
expect(requests[1].searchParams.get('keyword')).toBe('2160p')
expect(requests[1].searchParams.get('cat')).toBe('11,22')
})
it('distinguishes a valid empty resource list from a category request failure', async () => {
server.use(siteCategoriesHandler(501, [], 500), siteResourcesHandler(501, []))
await renderDialog()
expect(await screen.findByText('没有数据')).toBeInTheDocument()
expect(screen.queryByText('资源加载失败,请重试')).not.toBeInTheDocument()
await waitFor(() => expect(console.error).toHaveBeenCalledOnce())
})
it('exposes loading during a repeated search and clears it when the latest request completes', async () => {
const nextResponse = createDeferred<TorrentInfo[]>()
let requestCount = 0
server.use(
siteCategoriesHandler(501, []),
http.get(siteApiUrls.resources(501), async () => {
requestCount += 1
if (requestCount === 1) return HttpResponse.json([])
return HttpResponse.json(await nextResponse.promise)
}),
)
const user = userEvent.setup()
await renderDialog(undefined, { VDataTable: DataTableStub })
await waitFor(() => expect(screen.getByTestId('resource-table')).toHaveAttribute('data-loading', 'false'))
await user.click(screen.getByRole('button', { name: /搜索/ }))
await waitFor(() => expect(screen.getByTestId('resource-table')).toHaveAttribute('data-loading', 'true'))
nextResponse.resolve([createTorrentInfo({ title: '重复搜索结果' })])
expect(await screen.findByText('重复搜索结果')).toBeInTheDocument()
expect(screen.getByTestId('resource-table')).toHaveAttribute('data-loading', 'false')
})
it('renders resource metadata and every promotion style branch', async () => {
server.use(
siteCategoriesHandler(501, []),
siteResourcesHandler(501, [
createTorrentInfo({
date_elapsed: '2 小时前',
description: '完整资源说明',
downloadvolumefactor: 0,
freedate_diff: '剩余 1 天',
hit_and_run: true,
labels: ['原盘'],
pubdate: '2026-07-19',
title: '免费资源',
volume_factor: 'FREE',
}),
createTorrentInfo({ downloadvolumefactor: 0.5, title: '半价资源', volume_factor: '50%' }),
createTorrentInfo({ title: '双倍上传', uploadvolumefactor: 2, volume_factor: '2X' }),
createTorrentInfo({ title: '普通资源' }),
]),
)
await renderDialog()
expect(await screen.findByText('免费资源')).toBeInTheDocument()
expect(screen.getByText('完整资源说明')).toBeInTheDocument()
expect(screen.getByText('H&R')).toBeInTheDocument()
expect(screen.getByText('剩余 1 天')).toBeInTheDocument()
expect(screen.getByText('原盘')).toBeInTheDocument()
expect(screen.getByText('FREE').closest('.v-chip')).toHaveClass('bg-lime-500')
expect(screen.getByText('50%').closest('.v-chip')).toHaveClass('bg-green-500')
expect(screen.getByText('2X').closest('.v-chip')).toHaveClass('bg-sky-500')
expect(screen.queryByText('1x')).not.toBeInTheDocument()
})
it('opens and closes the add-download boundary through all child outcomes', async () => {
server.use(siteCategoriesHandler(501, []), siteResourcesHandler(501, [createTorrentInfo({ title: '待下载资源' })]))
const user = userEvent.setup()
await renderDialog()
const title = await screen.findByRole('button', { name: /待下载资源/ })
await user.click(title)
expect(screen.getByTestId('add-download-dialog')).toHaveTextContent('待下载资源')
await user.click(screen.getByRole('button', { name: 'done' }))
expect(screen.queryByTestId('add-download-dialog')).not.toBeInTheDocument()
await user.click(title)
await user.click(screen.getByRole('button', { name: 'error' }))
expect(screen.queryByTestId('add-download-dialog')).not.toBeInTheDocument()
await user.click(title)
await user.click(screen.getByRole('button', { name: 'close-download' }))
expect(screen.queryByTestId('add-download-dialog')).not.toBeInTheDocument()
})
it('corrects an out-of-range desktop page after the page size changes', async () => {
server.use(
siteCategoriesHandler(501, []),
siteResourcesHandler(
501,
Array.from({ length: 30 }, (_, index) => createTorrentInfo({ title: `分页资源 ${index + 1}` })),
),
)
const user = userEvent.setup()
await renderDialog(undefined, { VDataTable: DataTableStub })
await screen.findByText('分页资源 1')
await user.click(screen.getByRole('button', { name: 'page-4' }))
expect(screen.getByTestId('resource-page')).toHaveTextContent('4')
await user.click(screen.getByRole('button', { name: 'per-page-100' }))
await waitFor(() => expect(screen.getByTestId('resource-page')).toHaveTextContent('1'))
})
it('uses stable mobile keys and guards detail and torrent external links', async () => {
setViewport(390)
const open = vi.spyOn(window, 'open').mockImplementation(() => null)
server.use(
siteCategoriesHandler(501, []),
siteResourcesHandler(501, [
createTorrentInfo({
description: '移动端资源说明',
downloadvolumefactor: 0,
enclosure: 'https://tracker.example.com/download/one',
freedate_diff: '剩余 2 小时',
hit_and_run: true,
labels: ['移动标签'],
page_url: 'https://tracker.example.com/details/one',
title: '详情资源',
volume_factor: 'FREE',
}),
createTorrentInfo({ enclosure: 'magnet:?xt=urn:btih:test', page_url: '', title: '无外链资源' }),
createTorrentInfo({ enclosure: '', page_url: '', pubdate: '2026-07-19', title: '回退键资源' }),
]),
)
const user = userEvent.setup()
await renderDialog()
expect(await screen.findByText('详情资源')).toBeInTheDocument()
expect(screen.getByText('移动端资源说明')).toBeInTheDocument()
expect(screen.getByText('移动标签')).toBeInTheDocument()
const cards = screen.getByTestId('progressive-grid').querySelectorAll('[data-resource-key]')
expect(Array.from(cards, card => card.getAttribute('data-resource-key'))).toEqual([
'https://tracker.example.com/details/one',
'magnet:?xt=urn:btih:test',
'回退键资源-2026-07-19-2',
])
await user.click(screen.getByRole('button', { name: /详情资源/ }))
await user.click(screen.getByRole('button', { name: 'close-download' }))
await user.click(screen.getAllByRole('button', { name: '添加下载' })[0])
await user.click(screen.getByRole('button', { name: 'close-download' }))
const detailButtons = screen.getAllByLabelText('查看详情')
await user.click(detailButtons[0])
await user.click(detailButtons[1])
await user.click(screen.getAllByLabelText('下载种子文件')[0])
expect(open).toHaveBeenNthCalledWith(1, 'https://tracker.example.com/details/one', '_blank')
expect(open).toHaveBeenNthCalledWith(2, 'https://tracker.example.com/download/one', '_blank')
expect(open).toHaveBeenCalledTimes(2)
expect(screen.getAllByLabelText('下载种子文件')[1]).toBeDisabled()
})
it('expands and closes mobile search without retaining it after a desktop switch', async () => {
setViewport(390)
server.use(siteCategoriesHandler(501, []), siteResourcesHandler(501, []))
const user = userEvent.setup()
const { container } = await renderDialog()
await screen.findByText('没有数据')
const toggle = container.querySelector('.site-resource-mobile-search__toggle')
expect(toggle).not.toBeNull()
await user.click(toggle as HTMLElement)
await user.type(screen.getByLabelText('搜索关键字'), 'mobile')
await user.click(screen.getByRole('button', { name: '取消' }))
expect(screen.queryByLabelText('搜索关键字')).not.toBeInTheDocument()
await user.click(toggle as HTMLElement)
setViewport(1280)
await waitFor(() => expect(container.querySelector('.site-resource-mobile-search')).toBeNull())
setViewport(390)
await waitFor(() => expect(screen.queryByLabelText('搜索关键字')).not.toBeInTheDocument())
})
it('emits close and formats the result summary in English', async () => {
server.use(
siteCategoriesHandler(501, []),
siteResourcesHandler(501, [createTorrentInfo({ title: 'Language resource' })]),
)
const user = userEvent.setup()
const { close, container } = await renderDialog()
await screen.findByText('Language resource')
i18n.global.locale.value = 'en-US'
expect(await screen.findByText('1 results')).toBeInTheDocument()
const closeButton = container.querySelector('.v-toolbar-items .v-btn')
expect(closeButton).not.toBeNull()
await user.click(closeButton as HTMLElement)
expect(close).toHaveBeenCalledOnce()
})
})
+1
View File
@@ -3264,6 +3264,7 @@ export default {
itemsPerPage: 'Items Per Page',
noData: 'No Data',
loading: 'Loading...',
loadFailed: 'Failed to load resources. Please retry.',
titleColumn: 'Title',
timeColumn: 'Time',
sizeColumn: 'Size',
+1
View File
@@ -3212,6 +3212,7 @@ export default {
itemsPerPage: '每页条数',
noData: '没有数据',
loading: '加载中...',
loadFailed: '资源加载失败,请重试',
titleColumn: '标题',
timeColumn: '时间',
sizeColumn: '大小',
+1
View File
@@ -3212,6 +3212,7 @@ export default {
pageText: '{0}-{1} 共 {2} 條',
noData: '沒有數據',
loading: '加載中...',
loadFailed: '資源加載失敗,請重試',
titleColumn: '標題',
timeColumn: '時間',
sizeColumn: '大小',
+37 -1
View File
@@ -1,6 +1,7 @@
import type { DownloaderConf, Site, SiteStatistic, SiteUserData } from '@/api/types'
import type { DownloaderConf, Site, SiteCategory, SiteStatistic, SiteUserData, TorrentInfo } from '@/api/types'
let siteSeed = 0
let torrentSeed = 0
export function createSite(overrides: Partial<Site> = {}): Site {
siteSeed += 1
@@ -49,3 +50,38 @@ export function createSiteDownloader(overrides: Partial<DownloaderConf> = {}): D
...overrides,
}
}
/** 构造站点资源分类测试记录。 */
export function createSiteCategory(overrides: Partial<SiteCategory> = {}): SiteCategory {
return {
cat: 'movie',
desc: '电影',
id: 1,
...overrides,
}
}
/** 构造满足资源列表展示与下载边界的种子记录。 */
export function createTorrentInfo(overrides: Partial<TorrentInfo> = {}): TorrentInfo {
torrentSeed += 1
return {
category: '电影',
downloadvolumefactor: 1,
freedate: '',
freedate_diff: '',
grabs: 10,
hit_and_run: false,
imdbid: '',
labels: [],
peers: 3,
pri_order: 0,
seeders: 12,
site_order: 0,
site_proxy: false,
size: 1_073_741_824,
title: `测试资源 ${torrentSeed}`,
uploadvolumefactor: 1,
volume_factor: '1x',
...overrides,
}
}
+35 -1
View File
@@ -1,4 +1,12 @@
import type { ApiResponse, DownloaderConf, Site, SiteStatistic, SiteUserData } from '@/api/types'
import type {
ApiResponse,
DownloaderConf,
Site,
SiteCategory,
SiteStatistic,
SiteUserData,
TorrentInfo,
} from '@/api/types'
import { HttpResponse, http, type JsonBodyType } from 'msw'
const API_BASE_URL = 'http://localhost/api/v1/'
@@ -10,6 +18,7 @@ interface SiteMutationResponse {
}
export const siteApiUrls = {
categories: (siteId: number) => new URL(`site/category/${siteId}`, API_BASE_URL).href,
cookie: (id: number) => new URL(`site/cookie/${id}`, API_BASE_URL).href,
delete: (id: number) => new URL(`site/${id}`, API_BASE_URL).href,
details: (id: number) => new URL(`site/${id}`, API_BASE_URL).href,
@@ -17,6 +26,7 @@ export const siteApiUrls = {
icon: (id: number) => new URL(`site/icon/${id}`, API_BASE_URL).href,
list: new URL('site/', API_BASE_URL).href,
priorities: new URL('site/priorities', API_BASE_URL).href,
resources: (siteId: number) => new URL(`site/resource/${siteId}`, API_BASE_URL).href,
statistic: (domain: string) => new URL(`site/statistic/${domain}`, API_BASE_URL).href,
statistics: new URL('site/statistic', API_BASE_URL).href,
test: (id: number) => new URL(`site/test/${id}`, API_BASE_URL).href,
@@ -180,3 +190,27 @@ export function updateSiteCookieHandler(
return HttpResponse.json(response, { status })
})
}
export function siteCategoriesHandler(
siteId: number,
response: SiteCategory[],
status = 200,
onRequest: (url: URL) => void = () => {},
) {
return http.get(siteApiUrls.categories(siteId), ({ request }) => {
onRequest(new URL(request.url))
return HttpResponse.json(response, { status })
})
}
export function siteResourcesHandler(
siteId: number,
response: TorrentInfo[],
status = 200,
onRequest: (url: URL) => void = () => {},
) {
return http.get(siteApiUrls.resources(siteId), ({ request }) => {
onRequest(new URL(request.url))
return HttpResponse.json(response, { status })
})
}
+7
View File
@@ -301,6 +301,7 @@ export default defineConfig(({ mode }) => ({
'src/components/dialog/SubscribeShareDialog.vue',
'src/components/dialog/SubscribeShareStatisticsDialog.vue',
'src/components/dialog/DiscoverTabOrderDialog.vue',
'src/components/dialog/SiteResourceDialog.vue',
'src/views/discover/TheMovieDbView.vue',
'src/views/discover/DoubanView.vue',
'src/views/discover/BangumiView.vue',
@@ -485,6 +486,12 @@ export default defineConfig(({ mode }) => ({
lines: 80,
statements: 80,
},
'src/components/dialog/SiteResourceDialog.vue': {
branches: 85,
functions: 90,
lines: 90,
statements: 90,
},
'src/views/discover/TheMovieDbView.vue': {
branches: 75,
functions: 80,