mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-07 00:36:41 +08:00
perf(dashboard): restore additional cards from snapshots (#574)
This commit is contained in:
@@ -2,18 +2,25 @@
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { MediaStatistic } from '@/api/types'
|
import type { MediaStatistic } from '@/api/types'
|
||||||
import { formatDashboardCount, useAnimatedDashboardNumber } from '@/composables/useDashboardMotion'
|
import { formatDashboardCount, useAnimatedDashboardNumber } from '@/composables/useDashboardMotion'
|
||||||
|
import { useDashboardSnapshot } from '@/composables/useDashboardSnapshot'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const movieCount = ref(0)
|
const { readSnapshot, writeSnapshot } = useDashboardSnapshot<MediaStatistic>('media-statistic-v1')
|
||||||
const tvCount = ref(0)
|
const currentSnapshot = readSnapshot()
|
||||||
const episodeCount = ref<number | null>(null)
|
|
||||||
const userCount = ref(0)
|
const movieCount = ref(Number(currentSnapshot?.value.movie_count) || 0)
|
||||||
const movieCountMonth = ref(0)
|
const tvCount = ref(Number(currentSnapshot?.value.tv_count) || 0)
|
||||||
const tvCountMonth = ref(0)
|
const episodeCount = ref<number | null>(
|
||||||
const episodeCountMonth = ref(0)
|
currentSnapshot?.value.episode_count == null ? null : Number(currentSnapshot.value.episode_count) || 0,
|
||||||
|
)
|
||||||
|
const userCount = ref(Number(currentSnapshot?.value.user_count) || 0)
|
||||||
|
const movieCountMonth = ref(Number(currentSnapshot?.value.movie_count_month) || 0)
|
||||||
|
const tvCountMonth = ref(Number(currentSnapshot?.value.tv_count_month) || 0)
|
||||||
|
const episodeCountMonth = ref(Number(currentSnapshot?.value.episode_count_month) || 0)
|
||||||
|
let statisticLoadId = 0
|
||||||
|
|
||||||
const animatedMovieCount = useAnimatedDashboardNumber(movieCount, {
|
const animatedMovieCount = useAnimatedDashboardNumber(movieCount, {
|
||||||
duration: 720,
|
duration: 720,
|
||||||
@@ -24,10 +31,13 @@ const animatedTvCount = useAnimatedDashboardNumber(tvCount, {
|
|||||||
duration: 720,
|
duration: 720,
|
||||||
})
|
})
|
||||||
|
|
||||||
const animatedEpisodeCount = useAnimatedDashboardNumber(computed(() => episodeCount.value ?? 0), {
|
const animatedEpisodeCount = useAnimatedDashboardNumber(
|
||||||
delay: 120,
|
computed(() => episodeCount.value ?? 0),
|
||||||
duration: 720,
|
{
|
||||||
})
|
delay: 120,
|
||||||
|
duration: 720,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const animatedUserCount = useAnimatedDashboardNumber(userCount, {
|
const animatedUserCount = useAnimatedDashboardNumber(userCount, {
|
||||||
delay: 180,
|
delay: 180,
|
||||||
@@ -67,16 +77,29 @@ const statistics = computed(() => [
|
|||||||
|
|
||||||
// 调用API加载媒体统计数据
|
// 调用API加载媒体统计数据
|
||||||
async function loadMediaStatistic() {
|
async function loadMediaStatistic() {
|
||||||
|
const loadId = ++statisticLoadId
|
||||||
try {
|
try {
|
||||||
const res: MediaStatistic = await api.get('dashboard/statistic')
|
const res: MediaStatistic = await api.get('dashboard/statistic')
|
||||||
|
if (loadId !== statisticLoadId) return
|
||||||
|
|
||||||
movieCount.value = Number(res.movie_count) || 0
|
const statistic: MediaStatistic = {
|
||||||
tvCount.value = Number(res.tv_count) || 0
|
movie_count: Number(res.movie_count) || 0,
|
||||||
episodeCount.value = res.episode_count == null ? null : Number(res.episode_count) || 0
|
tv_count: Number(res.tv_count) || 0,
|
||||||
userCount.value = Number(res.user_count) || 0
|
episode_count: res.episode_count == null ? null : Number(res.episode_count) || 0,
|
||||||
movieCountMonth.value = Number(res.movie_count_month) || 0
|
user_count: Number(res.user_count) || 0,
|
||||||
tvCountMonth.value = Number(res.tv_count_month) || 0
|
movie_count_month: Number(res.movie_count_month) || 0,
|
||||||
episodeCountMonth.value = Number(res.episode_count_month) || 0
|
tv_count_month: Number(res.tv_count_month) || 0,
|
||||||
|
episode_count_month: Number(res.episode_count_month) || 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
movieCount.value = statistic.movie_count
|
||||||
|
tvCount.value = statistic.tv_count
|
||||||
|
episodeCount.value = statistic.episode_count
|
||||||
|
userCount.value = statistic.user_count
|
||||||
|
movieCountMonth.value = statistic.movie_count_month
|
||||||
|
tvCountMonth.value = statistic.tv_count_month
|
||||||
|
episodeCountMonth.value = statistic.episode_count_month
|
||||||
|
writeSnapshot(statistic)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import type { MediaInfo } from '@/api/types'
|
import type { MediaInfo } from '@/api/types'
|
||||||
|
import { useDashboardSnapshot } from '@/composables/useDashboardSnapshot'
|
||||||
import { getMediaSubscribeId } from '@/composables/useMediaSubscribe'
|
import { getMediaSubscribeId } from '@/composables/useMediaSubscribe'
|
||||||
import { useGlobalSettingsStore } from '@/stores'
|
import { useGlobalSettingsStore } from '@/stores'
|
||||||
import { getDisplayImageUrl } from '@/utils/imageUtils'
|
import { getDisplayImageUrl } from '@/utils/imageUtils'
|
||||||
@@ -25,10 +26,16 @@ const selectedSourcePath = ref(
|
|||||||
? storedSourcePath
|
? storedSourcePath
|
||||||
: sources.value[0].apipath,
|
: sources.value[0].apipath,
|
||||||
)
|
)
|
||||||
const mediaItems = shallowRef<MediaInfo[]>([])
|
const mediaSnapshots = new Map(
|
||||||
const mediaCache = new Map<string, MediaInfo[]>()
|
sources.value.map(source => [
|
||||||
|
source.apipath,
|
||||||
|
useDashboardSnapshot<MediaInfo[]>(`media-recommend-v1:${source.apipath}`),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
const initialSnapshot = mediaSnapshots.get(selectedSourcePath.value)?.readSnapshot()
|
||||||
|
const mediaItems = shallowRef<MediaInfo[]>(initialSnapshot?.value ?? [])
|
||||||
const activeIndex = ref(0)
|
const activeIndex = ref(0)
|
||||||
const loading = ref(true)
|
const loading = ref(!initialSnapshot)
|
||||||
const loadFailed = ref(false)
|
const loadFailed = ref(false)
|
||||||
const isHovered = ref(false)
|
const isHovered = ref(false)
|
||||||
const isFocusWithin = ref(false)
|
const isFocusWithin = ref(false)
|
||||||
@@ -76,34 +83,36 @@ function getMediaKey(item: MediaInfo) {
|
|||||||
return getMediaSubscribeId(item)
|
return getMediaSubscribeId(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加载指定推荐来源,并缓存当前会话已获取的数据。 */
|
/** 加载指定推荐来源,持久快照只负责立即恢复,随后仍以成功响应更新内容。 */
|
||||||
async function loadMedia(sourcePath = selectedSourcePath.value) {
|
async function loadMedia(sourcePath = selectedSourcePath.value) {
|
||||||
const currentRequestId = ++requestId
|
const currentRequestId = ++requestId
|
||||||
const cachedItems = mediaCache.get(sourcePath)
|
const cachedItems = mediaSnapshots.get(sourcePath)?.readSnapshot()?.value
|
||||||
|
|
||||||
if (cachedItems) {
|
if (cachedItems) {
|
||||||
mediaItems.value = cachedItems
|
mediaItems.value = cachedItems
|
||||||
activeIndex.value = 0
|
activeIndex.value = 0
|
||||||
loading.value = false
|
loading.value = false
|
||||||
loadFailed.value = false
|
loadFailed.value = false
|
||||||
resumeAutoplayIfReady()
|
resumeAutoplayIfReady()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = !cachedItems
|
||||||
loadFailed.value = false
|
loadFailed.value = false
|
||||||
try {
|
try {
|
||||||
const response = await api.get(sourcePath)
|
const response = await api.get(sourcePath)
|
||||||
if (currentRequestId !== requestId) return
|
if (currentRequestId !== requestId) return
|
||||||
|
|
||||||
const items = normalizeMediaResponse(response).filter(isUsableMedia).slice(0, RECOMMEND_SLIDE_COUNT)
|
const items = normalizeMediaResponse(response).filter(isUsableMedia).slice(0, RECOMMEND_SLIDE_COUNT)
|
||||||
mediaCache.set(sourcePath, items)
|
mediaSnapshots.get(sourcePath)?.writeSnapshot(items)
|
||||||
mediaItems.value = items
|
mediaItems.value = items
|
||||||
activeIndex.value = 0
|
activeIndex.value = 0
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (currentRequestId !== requestId) return
|
if (currentRequestId !== requestId) return
|
||||||
console.error(error)
|
console.error(error)
|
||||||
mediaItems.value = []
|
if (!cachedItems) {
|
||||||
loadFailed.value = true
|
mediaItems.value = []
|
||||||
|
loadFailed.value = true
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (currentRequestId === requestId) {
|
if (currentRequestId === requestId) {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -477,7 +486,9 @@ onBeforeUnmount(() => {
|
|||||||
font-weight: 750;
|
font-weight: 750;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
line-height: 1.15;
|
line-height: 1.15;
|
||||||
text-shadow: 0 3px 20px rgba(0, 0, 0, 0.82), 0 1px 2px rgba(0, 0, 0, 0.72);
|
text-shadow:
|
||||||
|
0 3px 20px rgba(0, 0, 0, 0.82),
|
||||||
|
0 1px 2px rgba(0, 0, 0, 0.72);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-recommend-meta {
|
.dashboard-recommend-meta {
|
||||||
@@ -545,7 +556,9 @@ onBeforeUnmount(() => {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
inline-size: 54px;
|
inline-size: 54px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
transition: background-color 0.2s ease, inline-size 0.2s ease;
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
inline-size 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-recommend-page.is-active {
|
.dashboard-recommend-page.is-active {
|
||||||
@@ -569,7 +582,9 @@ onBeforeUnmount(() => {
|
|||||||
.dashboard-recommend-arrow {
|
.dashboard-recommend-arrow {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.2s ease, transform 0.2s ease;
|
transition:
|
||||||
|
opacity 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-recommend-topbar {
|
.dashboard-recommend-topbar {
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
import api from '@/api'
|
||||||
|
import AnalyticsMediaStatistic from '@/views/dashboard/AnalyticsMediaStatistic.vue'
|
||||||
|
import { renderWithProviders } from '@tests/support/render'
|
||||||
|
import { screen, waitFor } from '@testing-library/vue'
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
|
vi.mock('@/api', () => ({ default: { get: vi.fn() } }))
|
||||||
|
vi.mock('@/composables/useDashboardMotion', async importOriginal => {
|
||||||
|
const actual = await importOriginal<typeof import('@/composables/useDashboardMotion')>()
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useAnimatedDashboardNumber: (source: { value: number }) => source,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const apiGet = vi.mocked(api.get)
|
||||||
|
const snapshotKey = 'MP_DASHBOARD_SNAPSHOT_V1:7:media-statistic-v1'
|
||||||
|
|
||||||
|
function deferred<T>() {
|
||||||
|
let resolve!: (value: T) => void
|
||||||
|
const promise = new Promise<T>(resolver => {
|
||||||
|
resolve = resolver
|
||||||
|
})
|
||||||
|
return { promise, resolve }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('AnalyticsMediaStatistic', () => {
|
||||||
|
it('restores the last successful statistic before F5 revalidation completes', async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
snapshotKey,
|
||||||
|
JSON.stringify({
|
||||||
|
savedAt: Date.now(),
|
||||||
|
value: {
|
||||||
|
movie_count: 12,
|
||||||
|
tv_count: 34,
|
||||||
|
episode_count: 56,
|
||||||
|
user_count: 7,
|
||||||
|
movie_count_month: 1,
|
||||||
|
tv_count_month: 2,
|
||||||
|
episode_count_month: 3,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const request = deferred<Record<string, number>>()
|
||||||
|
apiGet.mockReturnValue(request.promise)
|
||||||
|
|
||||||
|
await renderWithProviders(AnalyticsMediaStatistic, { initialState: { user: { userID: 7 } } })
|
||||||
|
|
||||||
|
expect(screen.getByText('12')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('34')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('56')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('7')).toBeInTheDocument()
|
||||||
|
expect(apiGet).toHaveBeenCalledWith('dashboard/statistic')
|
||||||
|
|
||||||
|
request.resolve({
|
||||||
|
movie_count: 21,
|
||||||
|
tv_count: 43,
|
||||||
|
episode_count: 65,
|
||||||
|
user_count: 8,
|
||||||
|
movie_count_month: 4,
|
||||||
|
tv_count_month: 5,
|
||||||
|
episode_count_month: 6,
|
||||||
|
})
|
||||||
|
|
||||||
|
await waitFor(() => expect(screen.getByText('21')).toBeInTheDocument())
|
||||||
|
expect(JSON.parse(localStorage.getItem(snapshotKey) ?? '{}').value).toMatchObject({
|
||||||
|
movie_count: 21,
|
||||||
|
tv_count: 43,
|
||||||
|
episode_count: 65,
|
||||||
|
user_count: 8,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps the restored statistic when revalidation fails', async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
snapshotKey,
|
||||||
|
JSON.stringify({
|
||||||
|
savedAt: Date.now(),
|
||||||
|
value: {
|
||||||
|
movie_count: 12,
|
||||||
|
tv_count: 34,
|
||||||
|
episode_count: null,
|
||||||
|
user_count: 7,
|
||||||
|
movie_count_month: 1,
|
||||||
|
tv_count_month: 2,
|
||||||
|
episode_count_month: 3,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||||
|
apiGet.mockRejectedValue(new Error('remote unavailable'))
|
||||||
|
|
||||||
|
await renderWithProviders(AnalyticsMediaStatistic, { initialState: { user: { userID: 7 } } })
|
||||||
|
|
||||||
|
await waitFor(() => expect(apiGet).toHaveBeenCalledWith('dashboard/statistic'))
|
||||||
|
expect(screen.getByText('12')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('34')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('未获取')).toBeInTheDocument()
|
||||||
|
expect(screen.getByText('7')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('keeps the newest KeepAlive response when initial refreshes finish out of order', async () => {
|
||||||
|
const firstRequest = deferred<Record<string, number>>()
|
||||||
|
const secondRequest = deferred<Record<string, number>>()
|
||||||
|
apiGet.mockReturnValueOnce(firstRequest.promise).mockReturnValueOnce(secondRequest.promise)
|
||||||
|
const KeepAliveHarness = defineComponent({
|
||||||
|
components: { AnalyticsMediaStatistic },
|
||||||
|
template: '<KeepAlive><AnalyticsMediaStatistic /></KeepAlive>',
|
||||||
|
})
|
||||||
|
|
||||||
|
await renderWithProviders(KeepAliveHarness, { initialState: { user: { userID: 7 } } })
|
||||||
|
await waitFor(() => expect(apiGet).toHaveBeenCalledTimes(2))
|
||||||
|
|
||||||
|
secondRequest.resolve({
|
||||||
|
movie_count: 21,
|
||||||
|
tv_count: 43,
|
||||||
|
episode_count: 65,
|
||||||
|
user_count: 8,
|
||||||
|
movie_count_month: 4,
|
||||||
|
tv_count_month: 5,
|
||||||
|
episode_count_month: 6,
|
||||||
|
})
|
||||||
|
await waitFor(() => expect(screen.getByText('21')).toBeInTheDocument())
|
||||||
|
|
||||||
|
firstRequest.resolve({
|
||||||
|
movie_count: 12,
|
||||||
|
tv_count: 34,
|
||||||
|
episode_count: 56,
|
||||||
|
user_count: 7,
|
||||||
|
movie_count_month: 1,
|
||||||
|
tv_count_month: 2,
|
||||||
|
episode_count_month: 3,
|
||||||
|
})
|
||||||
|
await Promise.resolve()
|
||||||
|
|
||||||
|
expect(screen.getByText('21')).toBeInTheDocument()
|
||||||
|
expect(screen.queryByText('12')).not.toBeInTheDocument()
|
||||||
|
expect(JSON.parse(localStorage.getItem(snapshotKey) ?? '{}').value).toMatchObject({
|
||||||
|
movie_count: 21,
|
||||||
|
tv_count: 43,
|
||||||
|
episode_count: 65,
|
||||||
|
user_count: 8,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -20,20 +20,16 @@ function getSourceMenuButton() {
|
|||||||
|
|
||||||
async function renderMediaRecommend(
|
async function renderMediaRecommend(
|
||||||
response: unknown,
|
response: unknown,
|
||||||
options: { sourcePath?: string; status?: number; onRequest?: () => void } = {},
|
options: { sourcePath?: string; status?: number; onRequest?: () => void; userID?: number } = {},
|
||||||
) {
|
) {
|
||||||
const sourcePath = options.sourcePath ?? DEFAULT_SOURCE
|
const sourcePath = options.sourcePath ?? DEFAULT_SOURCE
|
||||||
server.use(
|
server.use(
|
||||||
recommendMediaHandler(
|
recommendMediaHandler(sourcePath, response as Record<string, unknown>, options.status ?? 200, options.onRequest),
|
||||||
sourcePath,
|
|
||||||
response as Record<string, unknown>,
|
|
||||||
options.status ?? 200,
|
|
||||||
options.onRequest,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return renderWithProviders(MediaRecommend, {
|
return renderWithProviders(MediaRecommend, {
|
||||||
initialRoute: '/dashboard',
|
initialRoute: '/dashboard',
|
||||||
initialState: {
|
initialState: {
|
||||||
|
user: { userID: options.userID ?? -1 },
|
||||||
globalSettings: {
|
globalSettings: {
|
||||||
data: { GLOBAL_IMAGE_CACHE: false },
|
data: { GLOBAL_IMAGE_CACHE: false },
|
||||||
initialized: true,
|
initialized: true,
|
||||||
@@ -107,14 +103,15 @@ describe('MediaRecommend', () => {
|
|||||||
expect(localStorage.getItem('MP_DASHBOARD_RECOMMEND_SOURCE')).toBe(DEFAULT_SOURCE)
|
expect(localStorage.getItem('MP_DASHBOARD_RECOMMEND_SOURCE')).toBe(DEFAULT_SOURCE)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('switches sources, persists the choice, and reuses the session cache', async () => {
|
it('switches sources, persists the choice, and revalidates restored snapshots', async () => {
|
||||||
const user = userEvent.setup()
|
const user = userEvent.setup()
|
||||||
const trendingRequested = vi.fn()
|
const trendingRequested = vi.fn()
|
||||||
const moviesRequested = vi.fn()
|
const moviesRequested = vi.fn()
|
||||||
server.use(
|
server.use(recommendMediaHandler(MOVIE_SOURCE, [createMediaInfo({ title: '热门电影内容' })], 200, moviesRequested))
|
||||||
recommendMediaHandler(MOVIE_SOURCE, [createMediaInfo({ title: '热门电影内容' })], 200, moviesRequested),
|
await renderMediaRecommend([createMediaInfo({ title: '趋势内容' })], {
|
||||||
)
|
onRequest: trendingRequested,
|
||||||
await renderMediaRecommend([createMediaInfo({ title: '趋势内容' })], { onRequest: trendingRequested })
|
userID: 7,
|
||||||
|
})
|
||||||
await waitFor(() => expect(trendingRequested).toHaveBeenCalledOnce())
|
await waitFor(() => expect(trendingRequested).toHaveBeenCalledOnce())
|
||||||
|
|
||||||
await user.click(getSourceMenuButton())
|
await user.click(getSourceMenuButton())
|
||||||
@@ -126,7 +123,42 @@ describe('MediaRecommend', () => {
|
|||||||
await user.click(getSourceMenuButton())
|
await user.click(getSourceMenuButton())
|
||||||
await user.click(await screen.findByText('流行趋势'))
|
await user.click(await screen.findByText('流行趋势'))
|
||||||
expect(await screen.findByText('趋势内容')).toBeInTheDocument()
|
expect(await screen.findByText('趋势内容')).toBeInTheDocument()
|
||||||
expect(trendingRequested).toHaveBeenCalledOnce()
|
await waitFor(() => expect(trendingRequested).toHaveBeenCalledTimes(2))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('restores the selected source snapshot before F5 revalidation completes', async () => {
|
||||||
|
const renderOptions = {
|
||||||
|
initialRoute: '/dashboard',
|
||||||
|
initialState: {
|
||||||
|
user: { userID: 7 },
|
||||||
|
globalSettings: {
|
||||||
|
data: { GLOBAL_IMAGE_CACHE: false },
|
||||||
|
initialized: true,
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const first = await renderMediaRecommend([createMediaInfo({ title: '快照推荐' })], { userID: 7 })
|
||||||
|
await screen.findByText('快照推荐')
|
||||||
|
first.unmount()
|
||||||
|
|
||||||
|
let resolveRequest: ((response: Response) => void) | undefined
|
||||||
|
const requested = vi.fn()
|
||||||
|
server.use(
|
||||||
|
http.get(recommendApiUrls.media(DEFAULT_SOURCE), () => {
|
||||||
|
requested()
|
||||||
|
return new Promise<Response>(resolve => {
|
||||||
|
resolveRequest = resolve
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const second = await renderWithProviders(MediaRecommend, renderOptions)
|
||||||
|
|
||||||
|
expect(await screen.findByText('快照推荐')).toBeInTheDocument()
|
||||||
|
await waitFor(() => expect(requested).toHaveBeenCalledOnce())
|
||||||
|
resolveRequest?.(HttpResponse.json([createMediaInfo({ title: '刷新推荐' })]))
|
||||||
|
expect(await screen.findByText('刷新推荐')).toBeInTheDocument()
|
||||||
|
second.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('supports arrows, pagination, touch gestures, and detail routes', async () => {
|
it('supports arrows, pagination, touch gestures, and detail routes', async () => {
|
||||||
@@ -254,9 +286,13 @@ describe('MediaRecommend', () => {
|
|||||||
it('does not restart autoplay when deactivated before the initial request settles', async () => {
|
it('does not restart autoplay when deactivated before the initial request settles', async () => {
|
||||||
let resolveRequest: ((response: Response) => void) | undefined
|
let resolveRequest: ((response: Response) => void) | undefined
|
||||||
server.use(
|
server.use(
|
||||||
http.get(recommendApiUrls.media(DEFAULT_SOURCE), () => new Promise<Response>(resolve => {
|
http.get(
|
||||||
resolveRequest = resolve
|
recommendApiUrls.media(DEFAULT_SOURCE),
|
||||||
})),
|
() =>
|
||||||
|
new Promise<Response>(resolve => {
|
||||||
|
resolveRequest = resolve
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
const KeepAliveHarness = defineComponent({
|
const KeepAliveHarness = defineComponent({
|
||||||
components: { MediaRecommend },
|
components: { MediaRecommend },
|
||||||
@@ -300,9 +336,13 @@ describe('MediaRecommend', () => {
|
|||||||
let resolveMovies: ((response: Response) => void) | undefined
|
let resolveMovies: ((response: Response) => void) | undefined
|
||||||
server.use(
|
server.use(
|
||||||
recommendMediaHandler(DEFAULT_SOURCE, [createMediaInfo({ title: '初始推荐' })]),
|
recommendMediaHandler(DEFAULT_SOURCE, [createMediaInfo({ title: '初始推荐' })]),
|
||||||
http.get(recommendApiUrls.media(MOVIE_SOURCE), () => new Promise<Response>(resolve => {
|
http.get(
|
||||||
resolveMovies = resolve
|
recommendApiUrls.media(MOVIE_SOURCE),
|
||||||
})),
|
() =>
|
||||||
|
new Promise<Response>(resolve => {
|
||||||
|
resolveMovies = resolve
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
const KeepAliveHarness = defineComponent({
|
const KeepAliveHarness = defineComponent({
|
||||||
components: { MediaRecommend },
|
components: { MediaRecommend },
|
||||||
@@ -363,14 +403,18 @@ describe('MediaRecommend', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('invalidates a pending request when switching back to a cached source', async () => {
|
it('invalidates a pending request when switching back to a cached source', async () => {
|
||||||
await renderMediaRecommend([createMediaInfo({ title: '初始结果' })])
|
await renderMediaRecommend([createMediaInfo({ title: '初始结果' })], { userID: 7 })
|
||||||
expect(await screen.findByText('初始结果')).toBeInTheDocument()
|
expect(await screen.findByText('初始结果')).toBeInTheDocument()
|
||||||
|
|
||||||
let resolveMovies: ((response: Response) => void) | undefined
|
let resolveMovies: ((response: Response) => void) | undefined
|
||||||
server.use(
|
server.use(
|
||||||
http.get(recommendApiUrls.media(MOVIE_SOURCE), () => new Promise<Response>(resolve => {
|
http.get(
|
||||||
resolveMovies = resolve
|
recommendApiUrls.media(MOVIE_SOURCE),
|
||||||
})),
|
() =>
|
||||||
|
new Promise<Response>(resolve => {
|
||||||
|
resolveMovies = resolve
|
||||||
|
}),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
const user = userEvent.setup()
|
const user = userEvent.setup()
|
||||||
await user.click(getSourceMenuButton())
|
await user.click(getSourceMenuButton())
|
||||||
|
|||||||
Reference in New Issue
Block a user