// @vitest-environment happy-dom import { defineComponent } from 'vue' import { flushPromises, mount } from '@vue/test-utils' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import ExplainGuideStopListPage from '@/pages/explain/guide-stop-list.vue' import { explainUseCase } from '@/usecases/explainUseCase' const testState = vi.hoisted(() => ({ onLoadHandler: null as null | ((options?: Record) => void) })) vi.mock('@dcloudio/uni-app', () => ({ onLoad: (handler: (options?: Record) => void) => { testState.onLoadHandler = handler } })) vi.mock('@/usecases/explainUseCase', () => ({ explainUseCase: { listGuideStopsPageByHall: vi.fn().mockResolvedValue({ items: [], total: 0, pageNo: 1, pageSize: 20, hasMore: false }) } })) const GuidePageFrameStub = defineComponent({ name: 'GuidePageFrame', emits: ['back'], template: '
' }) const ExplainGuideStopCatalogStub = defineComponent({ name: 'ExplainGuideStopCatalog', props: ['guideStops', 'selectedHallName', 'loading', 'loadingMore', 'hasMore', 'error', 'loadMoreError'], emits: ['back', 'retry', 'retryMore', 'guideStopClick'], template: '' }) const mountGuideStopList = () => mount(ExplainGuideStopListPage, { global: { stubs: { GuidePageFrame: GuidePageFrameStub, ExplainGuideStopCatalog: ExplainGuideStopCatalogStub } } }) beforeEach(() => { testState.onLoadHandler = null vi.stubGlobal('uni', { navigateBack: vi.fn(), reLaunch: vi.fn(), navigateTo: vi.fn(), setNavigationBarTitle: vi.fn() }) vi.stubGlobal('getCurrentPages', vi.fn(() => [])) vi.mocked(explainUseCase.listGuideStopsPageByHall).mockReset() vi.mocked(explainUseCase.listGuideStopsPageByHall).mockResolvedValue({ items: [], total: 0, pageNo: 1, pageSize: 20, hasMore: false }) }) afterEach(() => { vi.restoreAllMocks() vi.unstubAllGlobals() }) describe('讲解对象列表返回', () => { it('ignores a delayed first-page response after unmount', async () => { let resolvePage: ((page: { items: Array<{ id: string, name: string }>, total: number, pageNo: number, pageSize: number, hasMore: boolean }) => void) | undefined vi.mocked(explainUseCase.listGuideStopsPageByHall).mockImplementationOnce(() => new Promise((resolve) => { resolvePage = resolve })) const wrapper = mountGuideStopList() testState.onLoadHandler?.({ hallId: 'hall-1' }) wrapper.unmount() resolvePage?.({ items: [{ id: 'late', name: '延迟' }], total: 1, pageNo: 1, pageSize: 20, hasMore: false }) await flushPromises() expect(explainUseCase.listGuideStopsPageByHall).toHaveBeenCalledTimes(1) }) it('permits retry-more recovery after a failed page', async () => { vi.mocked(explainUseCase.listGuideStopsPageByHall) .mockResolvedValueOnce({ items: [{ id: 'one', name: '一号' }], total: 2, pageNo: 1, pageSize: 20, hasMore: true }) .mockRejectedValueOnce(new Error('temporary')) .mockResolvedValueOnce({ items: [{ id: 'two', name: '二号' }], total: 2, pageNo: 2, pageSize: 20, hasMore: false }) const wrapper = mountGuideStopList() testState.onLoadHandler?.({ hallId: 'hall-1' }) await flushPromises() await wrapper.get('[data-testid="retry-more"]').trigger('click') await flushPromises() expect(explainUseCase.listGuideStopsPageByHall).toHaveBeenCalledTimes(2) await wrapper.get('[data-testid="retry-more"]').trigger('click') await flushPromises() expect(explainUseCase.listGuideStopsPageByHall).toHaveBeenCalledTimes(3) wrapper.unmount() }) it('上一页是独立展厅列表时回到该列表', async () => { vi.stubGlobal('getCurrentPages', vi.fn(() => [ { route: 'pages/explain/list', options: {} }, { route: 'pages/explain/guide-stop-list', options: {} } ])) const wrapper = mountGuideStopList() testState.onLoadHandler?.({ hallId: 'hall-1', hallName: '恐龙厅' }) await flushPromises() await wrapper.get('[data-testid="frame-back"]').trigger('click') expect(uni.navigateBack).toHaveBeenCalledWith(expect.objectContaining({ delta: 1 })) expect(uni.reLaunch).not.toHaveBeenCalled() wrapper.unmount() }) it('上一页是首页讲解 Tab 时回到首页中的展厅列表', async () => { vi.stubGlobal('getCurrentPages', vi.fn(() => [ { route: 'pages/index/index', options: { tab: 'explain' } }, { route: 'pages/explain/guide-stop-list', options: {} } ])) const wrapper = mountGuideStopList() await wrapper.get('[data-testid="list-back"]').trigger('click') expect(uni.navigateBack).toHaveBeenCalledWith(expect.objectContaining({ delta: 1 })) expect(uni.reLaunch).not.toHaveBeenCalled() wrapper.unmount() }) it('深链或不相关上一页直接回退至独立展厅列表', async () => { const wrapper = mountGuideStopList() await wrapper.get('[data-testid="list-back"]').trigger('click') expect(uni.navigateBack).not.toHaveBeenCalled() expect(uni.reLaunch).toHaveBeenCalledWith({ url: '/pages/explain/list' }) wrapper.unmount() }) it('页面栈返回失败时回退至独立展厅列表', async () => { vi.stubGlobal('getCurrentPages', vi.fn(() => [ { route: 'pages/explain/list', options: {} }, { route: 'pages/explain/guide-stop-list', options: {} } ])) vi.stubGlobal('uni', { navigateBack: vi.fn((options: { fail?: () => void }) => options.fail?.()), reLaunch: vi.fn(), navigateTo: vi.fn(), setNavigationBarTitle: vi.fn() }) const wrapper = mountGuideStopList() await wrapper.get('[data-testid="list-back"]').trigger('click') expect(uni.reLaunch).toHaveBeenCalledWith({ url: '/pages/explain/list' }) wrapper.unmount() }) })