test(site): cover site user data dialog (#564)

This commit is contained in:
InfinityPacer
2026-07-19 17:33:53 +08:00
committed by GitHub
parent 2542339733
commit 7f8e6952df
9 changed files with 451 additions and 21 deletions
-5
View File
@@ -385,11 +385,6 @@
"count": 1 "count": 1
} }
}, },
"src/components/dialog/SiteUserDataDialog.vue": {
"@typescript-eslint/no-explicit-any": {
"count": 5
}
},
"src/components/dialog/SmbConfigDialog.vue": { "src/components/dialog/SmbConfigDialog.vue": {
"@typescript-eslint/no-explicit-any": { "@typescript-eslint/no-explicit-any": {
"count": 2 "count": 2
+62 -16
View File
@@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Site, SiteUserData } from '@/api/types' import type { ApiResponse, Site, SiteUserData } from '@/api/types'
import api from '@/api' import api from '@/api'
import { useDisplay, useTheme } from 'vuetify' import { useDisplay, useTheme } from 'vuetify'
import { formatFileSize } from '@/@core/utils/formatters' import { formatFileSize } from '@/@core/utils/formatters'
@@ -23,6 +23,9 @@ const emit = defineEmits(['close'])
// 进度框 // 进度框
const progressDialog = ref(false) const progressDialog = ref(false)
// 失败状态保留对应操作,重试时不改变已展示的站点数据。
const failedOperation = ref<'load' | 'refresh'>()
const vuetifyTheme = useTheme() const vuetifyTheme = useTheme()
const currentTheme = controlledComputed( const currentTheme = controlledComputed(
@@ -33,6 +36,9 @@ const currentTheme = controlledComputed(
// 站点数据列表 // 站点数据列表
const siteDatas = ref<SiteUserData[]>([]) const siteDatas = ref<SiteUserData[]>([])
// 只有最近一次读取可以更新弹窗状态,避免首载慢响应覆盖手动刷新结果。
let fetchGeneration = 0
// 最新一天的数据 // 最新一天的数据
const siteData = computed(() => siteDatas.value[siteDatas.value.length - 1]) const siteData = computed(() => siteDatas.value[siteDatas.value.length - 1])
@@ -58,8 +64,8 @@ const historyChartOptions = computed(() => {
parentHeightOffset: 0, parentHeightOffset: 0,
toolbar: { show: false }, toolbar: { show: false },
animations: { enabled: true }, animations: { enabled: true },
background: currentTheme.value.surface, // 新增背景色同步 background: currentTheme.value.surface, // 图表背景随应用主题切换
foreColor: currentTheme.value.onSurface, // 新增文字颜色同步 foreColor: currentTheme.value.onSurface, // 图表文字随应用主题切换
dataLabels: { dataLabels: {
enabled: true, enabled: true,
}, },
@@ -139,10 +145,12 @@ const historyChartOptions = computed(() => {
// 做种分布列,seeding_info的格式为[[x, y], [x, y], ...],x为做种数,y为做种体积,做种体积需要转换为GB // 做种分布列,seeding_info的格式为[[x, y], [x, y], ...],x为做种数,y为做种体积,做种体积需要转换为GB
const seedingSeries = computed(() => { const seedingSeries = computed(() => {
const seedingInfo = siteData.value?.seeding_info as [number?, number?][] | undefined
return [ return [
{ {
name: t('dialog.siteUserData.volumeTitle'), name: t('dialog.siteUserData.volumeTitle'),
data: siteData.value?.seeding_info?.map(item => [item[0] ?? 0, Math.round((item[1] ?? 0) / 1024 / 1024 / 1024)]), data: seedingInfo?.map(item => [item[0] ?? 0, Math.round((item[1] ?? 0) / 1024 / 1024 / 1024)]) ?? [],
}, },
] ]
}) })
@@ -155,8 +163,8 @@ const seedingChartOptions = computed(() => {
parentHeightOffset: 0, parentHeightOffset: 0,
toolbar: { show: false }, toolbar: { show: false },
animations: { enabled: true }, animations: { enabled: true },
background: currentTheme.value.surface, // 新增背景色同步 background: currentTheme.value.surface, // 图表背景随应用主题切换
foreColor: currentTheme.value.onSurface, // 新增文字颜色同步 foreColor: currentTheme.value.onSurface, // 图表文字随应用主题切换
zoom: { zoom: {
enabled: false, enabled: false,
allowMouseWheelZoom: false, allowMouseWheelZoom: false,
@@ -214,7 +222,7 @@ const seedingChartOptions = computed(() => {
}) })
// 根据传入属性,计算列表数据中第一条与第二条的差值,如果没有第二条则差值为全部 // 根据传入属性,计算列表数据中第一条与第二条的差值,如果没有第二条则差值为全部
const diffData: { [key: string]: any } = computed(() => { const diffData = computed(() => {
if (siteDatas.value.length < 2) { if (siteDatas.value.length < 2) {
return siteData.value return siteData.value
} }
@@ -253,33 +261,57 @@ function getDiffClass(diff: number | undefined) {
} }
// 查询站点用户数据 // 查询站点用户数据
async function fetchSiteUserData() { async function fetchSiteUserData(failureOperation: 'load' | 'refresh' = 'load') {
const generation = ++fetchGeneration
try { try {
const result: { [key: string]: any } = await api.get(`site/userdata/${props.site?.id}`) const result = await api.get<ApiResponse<SiteUserData[]>, ApiResponse<SiteUserData[]>>(
`site/userdata/${props.site?.id}`,
)
if (generation !== fetchGeneration) return false
if (result.success) { if (result.success) {
// 使用nextTick确保DOM更新完成后再更新图表数据 // 使用nextTick确保DOM更新完成后再更新图表数据
await nextTick() await nextTick()
siteDatas.value = result.data.sort((a: { updated_day: any }, b: { updated_day: any }) => siteDatas.value = result.data.sort((a, b) => (a.updated_day || '').localeCompare(b.updated_day || ''))
(a.updated_day || '').localeCompare(b.updated_day || ''), failedOperation.value = undefined
) return true
} }
} catch (error) { } catch (error) {
if (generation !== fetchGeneration) return false
console.error(error) console.error(error)
failedOperation.value = failureOperation
} }
return false
} }
// 刷新站点数据 // 刷新站点数据
async function refreshSiteData() { async function refreshSiteData() {
progressDialog.value = true progressDialog.value = true
try { try {
const result: { [key: string]: any } = await api.post(`site/userdata/${props.site?.id}`) const result = await api.post<ApiResponse<unknown>, ApiResponse<unknown>>(`site/userdata/${props.site?.id}`)
if (result.success) { if (result.success) {
await fetchSiteUserData() await fetchSiteUserData('refresh')
} else {
failedOperation.value = 'refresh'
} }
} catch (error) { } catch (error) {
console.log(error) console.error(error)
} failedOperation.value = 'refresh'
} finally {
progressDialog.value = false progressDialog.value = false
}
}
// 重试最近失败的请求,刷新失败时继续保留当前数据。
function retryFailedOperation() {
if (failedOperation.value === 'refresh') {
refreshSiteData()
} else {
fetchSiteUserData()
}
} }
onBeforeMount(() => { onBeforeMount(() => {
@@ -302,6 +334,20 @@ onBeforeMount(() => {
</VCardItem> </VCardItem>
<VDivider /> <VDivider />
<VCardText class="pt-5"> <VCardText class="pt-5">
<VAlert v-if="failedOperation" type="error" variant="tonal" class="mb-5">
<div class="d-flex flex-wrap align-center justify-space-between gap-3">
<span>
{{
failedOperation === 'refresh'
? t('dialog.siteUserData.refreshFailed')
: t('dialog.siteUserData.loadFailed')
}}
</span>
<VBtn size="small" variant="tonal" prepend-icon="mdi-refresh" @click="retryFailedOperation">
{{ t('common.retry') }}
</VBtn>
</div>
</VAlert>
<VRow class="match-height"> <VRow class="match-height">
<!-- 用户信息 --> <!-- 用户信息 -->
<VCol cols="12" md="3"> <VCol cols="12" md="3">
@@ -0,0 +1,346 @@
import SiteUserDataDialog from '@/components/dialog/SiteUserDataDialog.vue'
import type { ApiResponse, SiteUserData } from '@/api/types'
import { fireEvent, screen, waitFor } from '@testing-library/vue'
import { createSite, createSiteUserData } from '@tests/support/factories/site'
import { refreshSiteUserDataHandler, siteUserDataHandler } from '@tests/support/msw/handlers/site'
import { server } from '@tests/support/msw/server'
import { renderWithProviders } from '@tests/support/render'
import { getActiveRequestsCount } from '@/utils/requestOptimizer'
import { flushPromises } from '@vue/test-utils'
import { defineComponent, h, type PropType } from 'vue'
import { beforeEach, describe, expect, it, vi } from 'vitest'
type ChartInput = {
options: Record<string, unknown>
series: { data?: unknown[]; name?: string }[]
type: string
}
const chartInputs: ChartInput[] = []
const ApexChartStub = defineComponent({
name: 'VApexChart',
props: {
options: { type: Object as PropType<Record<string, unknown>>, required: true },
series: { type: Array as PropType<ChartInput['series']>, required: true },
type: { type: String, required: true },
},
setup(props) {
chartInputs.push(props)
return () => h('div', { 'data-testid': `chart-${props.type}` })
},
})
const ProgressDialogStub = defineComponent({
name: 'ProgressDialog',
props: { text: String },
setup: props => () => h('div', { 'data-testid': 'refresh-progress' }, props.text),
})
const DialogCloseButtonStub = defineComponent({
name: 'VDialogCloseBtn',
setup:
(_, { attrs }) =>
() =>
h('button', { ...attrs, 'aria-label': '关闭' }),
})
function deferred<T>() {
let resolve!: (value: T) => void
const promise = new Promise<T>(resolvePromise => {
resolve = resolvePromise
})
return { promise, resolve }
}
async function renderDialog(
initialResult: Pick<ApiResponse<SiteUserData[]>, 'data' | 'message' | 'success'> = { data: [], success: false },
status = 200,
onRequest: () => void | Promise<void> = () => {},
) {
const site = createSite()
server.use(siteUserDataHandler(site.id, initialResult, status, onRequest))
const result = await renderWithProviders(SiteUserDataDialog, {
global: {
stubs: {
ProgressDialog: ProgressDialogStub,
VApexChart: ApexChartStub,
VDialogCloseBtn: DialogCloseButtonStub,
},
},
props: { site },
})
return { ...result, site }
}
function getChart(type: string) {
const chart = chartInputs.find(input => input.type === type)
if (!chart) throw new Error(`Missing ${type} chart input`)
return chart
}
function getRefreshButton() {
const button = document.querySelector<HTMLButtonElement>('.v-card-title button')
if (!button) throw new Error('Missing refresh button')
return button
}
function getRetryButton() {
const button = Array.from(document.querySelectorAll<HTMLButtonElement>('.v-alert button')).find(element =>
element.textContent?.includes('重试'),
)
if (!button) throw new Error('Missing retry button')
return button
}
describe('SiteUserDataDialog projections', () => {
beforeEach(() => {
chartInputs.length = 0
vi.spyOn(console, 'error').mockImplementation(() => {})
vi.spyOn(console, 'log').mockImplementation(() => {})
})
it('sorts history, renders the latest record, and projects positive, negative, and zero deltas', async () => {
const older = createSiteUserData({
bonus: 100,
download: 4 * 1024 ** 3,
ratio: 1.5,
seeding: 2,
seeding_size: 3 * 1024 ** 3,
updated_day: '2026-07-17',
upload: 1024 ** 3,
user_level: 'Old',
})
const latest = createSiteUserData({
bonus: 150,
download: 2 * 1024 ** 3,
ratio: 1.25,
seeding: 2,
seeding_info: [
[10, 2 * 1024 ** 3],
[0, 0],
],
seeding_size: 4 * 1024 ** 3,
updated_day: '2026-07-19',
upload: 3 * 1024 ** 3,
user_level: 'Elite',
})
await renderDialog({ data: [latest, older], success: true })
await screen.findByText('Elite')
expect(screen.getByText('150')).toBeInTheDocument()
expect(screen.getByText('(+50)')).toHaveClass('text-success')
expect(screen.getByText('(-0.25)')).toHaveClass('text-error')
expect(screen.getByText('(+0)')).not.toHaveClass('text-success', 'text-error')
expect(screen.getByText('3.00 GB')).toBeInTheDocument()
expect(screen.getByText('(+2.00 GB)')).toHaveClass('text-success')
expect(screen.getByText('2.00 GB')).toBeInTheDocument()
expect(screen.getByText('(-2.00 GB)')).toHaveClass('text-error')
expect(document.body).toHaveTextContent('2024-01-02')
const history = getChart('line')
expect(history.series).toEqual([
{ data: [1, 3], name: '上传量' },
{ data: [4, 2], name: '下载量' },
])
expect((history.options.xaxis as { categories: string[] }).categories).toEqual(['2026-07-17', '2026-07-19'])
expect((history.options.theme as { mode: string }).mode).toBe('light')
expect((history.options.chart as { background: string; foreColor: string }).background).toBeTruthy()
expect(
(history.options.xaxis as { labels: { formatter: (value: string) => string } }).labels.formatter('2026-07-19'),
).toBe(new Date('2026-07-19').toLocaleDateString('zh-CN'))
expect((history.options.yaxis as { labels: { formatter: (value: number) => string } }).labels.formatter(1234)).toBe(
(1234).toLocaleString(),
)
const seeding = getChart('scatter')
expect(seeding.series).toEqual([
{
data: [
[10, 2],
[0, 0],
],
name: '体积',
},
])
expect((seeding.options.theme as { mode: string }).mode).toBe('light')
expect((seeding.options.tooltip as { x: { formatter: (value: number) => string } }).x.formatter(1234)).toBe(
`数量:${(1234).toLocaleString()}`,
)
expect((seeding.options.xaxis as { labels: { formatter: (value: number) => string } }).labels.formatter(1.6)).toBe(
'2',
)
expect((seeding.options.yaxis as { labels: { formatter: (value: number) => string } }).labels.formatter(2048)).toBe(
`${(2048).toLocaleString()} GB`,
)
})
it('uses the only record as both latest data and the baseline delta', async () => {
const only = createSiteUserData({ bonus: 42, ratio: 0, seeding: 5, updated_day: '2026-07-18' })
await renderDialog({ data: [only], success: true })
await screen.findByText('42')
expect(screen.getByText('(+42)')).toHaveClass('text-success')
expect(screen.getByText('(+0)')).not.toHaveClass('text-success', 'text-error')
expect(screen.getByText('(+5)')).toHaveClass('text-success')
})
it('normalizes missing historical counters and seeding tuple values to zero', async () => {
const sparseRecord = () =>
createSiteUserData({
bonus: undefined,
download: undefined,
ratio: undefined,
seeding: undefined,
seeding_info: [[undefined, undefined]],
seeding_size: undefined,
updated_day: undefined,
upload: undefined,
})
await renderDialog({ data: [sparseRecord(), sparseRecord()], success: true })
await waitFor(() =>
expect(getChart('line').series).toEqual([
{ data: [0, 0], name: '上传量' },
{ data: [0, 0], name: '下载量' },
]),
)
expect(getChart('scatter').series).toEqual([{ data: [[0, 0]], name: '体积' }])
})
it('keeps a legal empty response renderable without fabricated values', async () => {
const requested = vi.fn()
await renderDialog({ data: [], success: false }, 200, requested)
await waitFor(() => expect(requested).toHaveBeenCalledOnce())
await waitFor(() => expect(getActiveRequestsCount()).toBe(0))
expect(getChart('line').series).toEqual([
{ data: [], name: '上传量' },
{ data: [], name: '下载量' },
])
expect(screen.getByText('无')).toBeInTheDocument()
expect(getChart('scatter').series).toEqual([{ data: [], name: '体积' }])
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})
})
describe('SiteUserDataDialog refresh and recovery', () => {
beforeEach(() => {
chartInputs.length = 0
vi.spyOn(console, 'error').mockImplementation(() => {})
vi.spyOn(console, 'log').mockImplementation(() => {})
})
it('reloads history after a successful refresh and keeps progress until both requests finish', async () => {
const initial = createSiteUserData({ bonus: 1 })
const refreshed = createSiteUserData({ bonus: 222, updated_day: '2026-07-19' })
const refreshRequest = deferred<void>()
let loadCount = 0
const { site } = await renderDialog({ data: [initial], success: true }, 200, () => {
loadCount += 1
})
server.use(refreshSiteUserDataHandler(site.id, { data: {}, success: true }, 200, () => refreshRequest.promise))
await screen.findByText('1')
await fireEvent.click(getRefreshButton())
expect(screen.getByTestId('refresh-progress')).toHaveTextContent('正在刷新站点数据...')
server.use(
siteUserDataHandler(site.id, { data: [refreshed], success: true }, 200, () => {
loadCount += 1
}),
)
refreshRequest.resolve()
await screen.findByText('222')
await waitFor(() => expect(screen.queryByTestId('refresh-progress')).not.toBeInTheDocument())
expect(loadCount).toBe(2)
})
it.each([
['late success', 200, { data: [createSiteUserData({ bonus: 1 })] as SiteUserData[], success: true }],
['late HTTP failure', 500, { data: [] as SiteUserData[], message: 'old request failed', success: false }],
] as const)('ignores an initial request that settles after refreshed data on %s', async (_case, status, response) => {
const initialRequest = deferred<void>()
const initialRequested = vi.fn()
const initialReleased = vi.fn()
const refreshed = createSiteUserData({ bonus: 222, updated_day: '2026-07-19' })
const { site } = await renderDialog(response, status, async () => {
initialRequested()
await initialRequest.promise
initialReleased()
})
await waitFor(() => expect(initialRequested).toHaveBeenCalledOnce())
server.use(
refreshSiteUserDataHandler(site.id, { data: {}, success: true }),
siteUserDataHandler(site.id, { data: [refreshed], success: true }),
)
await fireEvent.click(getRefreshButton())
await screen.findByText('222')
initialRequest.resolve()
await waitFor(() => expect(initialReleased).toHaveBeenCalledOnce())
await waitFor(() => expect(getActiveRequestsCount()).toBe(0))
await flushPromises()
expect(screen.getByText('222')).toBeInTheDocument()
expect(screen.queryByText('1')).not.toBeInTheDocument()
expect(screen.queryByRole('alert')).not.toBeInTheDocument()
})
it.each([
['business failure', 200, { data: {}, message: '站点不支持刷新', success: false }],
['HTTP failure', 500, { data: {}, message: 'server down', success: false }],
] as const)('ends progress and exposes a retry after refresh %s', async (_case, status, response) => {
const refreshRequested = vi.fn()
const { site } = await renderDialog({ data: [createSiteUserData()], success: true })
server.use(refreshSiteUserDataHandler(site.id, response, status, refreshRequested))
await screen.findByText('Elite')
await fireEvent.click(getRefreshButton())
await waitFor(() => expect(refreshRequested).toHaveBeenCalledOnce())
await waitFor(() => expect(screen.queryByTestId('refresh-progress')).not.toBeInTheDocument())
expect(await screen.findByText(/刷新站点数据失败/)).toBeInTheDocument()
expect(getRetryButton()).toBeInTheDocument()
server.use(refreshSiteUserDataHandler(site.id, { data: {}, success: true }, 200, refreshRequested))
await fireEvent.click(getRetryButton())
await waitFor(() => expect(refreshRequested).toHaveBeenCalledTimes(2))
await waitFor(() => expect(screen.queryByText(/刷新站点数据失败/)).not.toBeInTheDocument())
})
it('distinguishes initial HTTP failure from empty data and retries the same site', async () => {
let attempt = 0
const { site } = await renderDialog({ data: [], success: false }, 500, () => {
attempt += 1
})
expect(await screen.findByText(/加载站点数据失败/)).toBeInTheDocument()
server.use(
siteUserDataHandler(site.id, { data: [createSiteUserData({ bonus: 88 })], success: true }, 200, () => {
attempt += 1
}),
)
await fireEvent.click(getRetryButton())
await screen.findByText('88')
expect(attempt).toBe(2)
expect(screen.queryByText(/加载站点数据失败/)).not.toBeInTheDocument()
})
it('emits close without mutating loaded user data', async () => {
const { emitted } = await renderDialog({ data: [createSiteUserData()], success: true })
await screen.findByText('Elite')
const closeButton = document.querySelector<HTMLButtonElement>('button[aria-label="关闭"]')
if (!closeButton) throw new Error('Missing close button')
await fireEvent.click(closeButton)
expect(emitted('close')).toHaveLength(1)
expect(screen.getByText('Elite')).toBeInTheDocument()
})
})
+2
View File
@@ -3254,6 +3254,8 @@ export default {
countTitle: 'Count:', countTitle: 'Count:',
noData: 'None', noData: 'None',
refreshing: 'Refreshing site data...', refreshing: 'Refreshing site data...',
loadFailed: 'Failed to load site data',
refreshFailed: 'Failed to refresh site data',
close: 'Close', close: 'Close',
}, },
siteResource: { siteResource: {
+2
View File
@@ -3202,6 +3202,8 @@ export default {
countTitle: '数量:', countTitle: '数量:',
noData: '无', noData: '无',
refreshing: '正在刷新站点数据...', refreshing: '正在刷新站点数据...',
loadFailed: '加载站点数据失败',
refreshFailed: '刷新站点数据失败',
close: '关闭', close: '关闭',
}, },
siteResource: { siteResource: {
+2
View File
@@ -3201,6 +3201,8 @@ export default {
countTitle: '數量:', countTitle: '數量:',
noData: '無', noData: '無',
refreshing: '正在刷新站點數據...', refreshing: '正在刷新站點數據...',
loadFailed: '載入站點數據失敗',
refreshFailed: '刷新站點數據失敗',
close: '關閉', close: '關閉',
}, },
siteResource: { siteResource: {
+5
View File
@@ -31,9 +31,14 @@ export function createSiteUserData(overrides: Partial<SiteUserData> = {}): SiteU
return { return {
bonus: 100, bonus: 100,
download: 1024, download: 1024,
join_at: '2024-01-02 10:00:00',
ratio: 2, ratio: 2,
seeding: 3, seeding: 3,
seeding_info: [],
seeding_size: 4 * 1024 ** 3,
updated_day: '2026-07-18',
upload: 2048, upload: 2048,
user_level: 'Elite',
username: 'site-user', username: 'site-user',
...overrides, ...overrides,
} }
+25
View File
@@ -30,6 +30,7 @@ export const siteApiUrls = {
statistic: (domain: string) => new URL(`site/statistic/${domain}`, 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, statistics: new URL('site/statistic', API_BASE_URL).href,
test: (id: number) => new URL(`site/test/${id}`, API_BASE_URL).href, test: (id: number) => new URL(`site/test/${id}`, API_BASE_URL).href,
userData: (id: number) => new URL(`site/userdata/${id}`, API_BASE_URL).href,
userDataLatest: new URL('site/userdata/latest', API_BASE_URL).href, userDataLatest: new URL('site/userdata/latest', API_BASE_URL).href,
} }
@@ -80,6 +81,30 @@ export function siteUserDataLatestHandler(
}) })
} }
export function siteUserDataHandler(
id: number,
result: Pick<ApiResponse<SiteUserData[]>, 'data' | 'message' | 'success'>,
status = 200,
onRequest: () => void | Promise<void> = () => {},
) {
return http.get(siteApiUrls.userData(id), async () => {
await onRequest()
return response(result as unknown as JsonBodyType, status)
})
}
export function refreshSiteUserDataHandler(
id: number,
result: Pick<ApiResponse<Record<string, unknown>>, 'data' | 'message' | 'success'>,
status = 200,
onRequest: () => void | Promise<void> = () => {},
) {
return http.post(siteApiUrls.userData(id), async () => {
await onRequest()
return response(result as unknown as JsonBodyType, status)
})
}
export function saveSitePrioritiesHandler( export function saveSitePrioritiesHandler(
onSave: (priorities: Array<{ id: number; pri: number }>) => void | Promise<void> = () => {}, onSave: (priorities: Array<{ id: number; pri: number }>) => void | Promise<void> = () => {},
options: { status?: number; success?: boolean } = {}, options: { status?: number; success?: boolean } = {},
+7
View File
@@ -302,6 +302,7 @@ export default defineConfig(({ mode }) => ({
'src/components/dialog/SubscribeShareStatisticsDialog.vue', 'src/components/dialog/SubscribeShareStatisticsDialog.vue',
'src/components/dialog/DiscoverTabOrderDialog.vue', 'src/components/dialog/DiscoverTabOrderDialog.vue',
'src/components/dialog/SiteResourceDialog.vue', 'src/components/dialog/SiteResourceDialog.vue',
'src/components/dialog/SiteUserDataDialog.vue',
'src/views/discover/TheMovieDbView.vue', 'src/views/discover/TheMovieDbView.vue',
'src/views/discover/DoubanView.vue', 'src/views/discover/DoubanView.vue',
'src/views/discover/BangumiView.vue', 'src/views/discover/BangumiView.vue',
@@ -492,6 +493,12 @@ export default defineConfig(({ mode }) => ({
lines: 90, lines: 90,
statements: 90, statements: 90,
}, },
'src/components/dialog/SiteUserDataDialog.vue': {
branches: 85,
functions: 90,
lines: 90,
statements: 90,
},
'src/views/discover/TheMovieDbView.vue': { 'src/views/discover/TheMovieDbView.vue': {
branches: 75, branches: 75,
functions: 80, functions: 80,