Files
frontend-miniapp/tests/unit/ExplainListNavigation.spec.ts
lyf 04c6ccd40b
Some checks failed
CI / verify (push) Has been cancelled
修复讲解展厅列表返回导览首页
2026-07-14 15:23:19 +08:00

241 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// @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 ExplainListPage from '@/pages/explain/list.vue'
const hostEnvironmentMocks = vi.hoisted(() => ({
embeddedInWechatMiniProgram: false
}))
const testState = vi.hoisted(() => ({
onLoadHandler: null as null | (() => void),
onBackPressHandler: null as null | (() => boolean)
}))
vi.mock('@dcloudio/uni-app', () => ({
onLoad: (handler: () => void) => {
testState.onLoadHandler = handler
},
onBackPress: (handler: () => boolean) => {
testState.onBackPressHandler = handler
}
}))
vi.mock('@/utils/hostEnvironment', () => ({
isEmbeddedInWechatMiniProgram: () => hostEnvironmentMocks.embeddedInWechatMiniProgram
}))
vi.mock('@/usecases/explainUseCase', () => ({
explainUseCase: {
loadExplainHalls: async () => [],
loadExplainHallSummaries: async () => ({})
}
}))
const GuidePageFrameStub = defineComponent({
name: 'GuidePageFrame',
emits: ['back'],
template: '<main><button data-testid="frame-back" @click="$emit(\'back\')">顶部返回</button><slot /></main>'
})
const ExplainHallSelectStub = defineComponent({
name: 'ExplainHallSelect',
emits: ['back'],
template: '<button data-testid="explain-list-back" @click="$emit(\'back\')">返回</button>'
})
const mountExplainList = () => mount(ExplainListPage, {
global: {
stubs: {
GuidePageFrame: GuidePageFrameStub,
ExplainHallSelect: ExplainHallSelectStub
}
}
})
beforeEach(() => {
hostEnvironmentMocks.embeddedInWechatMiniProgram = false
testState.onLoadHandler = null
testState.onBackPressHandler = null
window.location.hash = '#/pages/explain/list'
window.history.replaceState({}, '', window.location.href)
vi.stubGlobal('uni', {
navigateBack: vi.fn(),
reLaunch: vi.fn(),
showToast: vi.fn()
})
vi.stubGlobal('getCurrentPages', vi.fn(() => []))
})
afterEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
describe('讲解展厅列表返回', () => {
it('首页通过内部页面栈进入时,顶部返回使用 navigateBack 回到导览首页', async () => {
vi.stubGlobal('getCurrentPages', vi.fn(() => [
{ route: 'pages/index/index', options: { tab: 'guide' } },
{ route: 'pages/explain/list', options: {} }
]))
const wrapper = mountExplainList()
testState.onLoadHandler?.()
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('页面栈只有展厅列表时使用 reLaunch 回到导览首页', async () => {
const wrapper = mountExplainList()
await wrapper.get('[data-testid="explain-list-back"]').trigger('click')
expect(uni.navigateBack).not.toHaveBeenCalled()
expect(uni.reLaunch).toHaveBeenCalledWith(expect.objectContaining({
url: '/pages/index/index?tab=guide'
}))
wrapper.unmount()
})
it('上一页不是首页时使用 reLaunch避免回到不相关页面', async () => {
vi.stubGlobal('getCurrentPages', vi.fn(() => [
{ route: 'pages/search/index', options: {} },
{ route: 'pages/explain/list', options: {} }
]))
const wrapper = mountExplainList()
await wrapper.get('[data-testid="explain-list-back"]').trigger('click')
expect(uni.navigateBack).not.toHaveBeenCalled()
expect(uni.reLaunch).toHaveBeenCalledWith(expect.objectContaining({
url: '/pages/index/index?tab=guide'
}))
wrapper.unmount()
})
it('上一页是首页但不是导览 Tab 时重启到导览首页', async () => {
vi.stubGlobal('getCurrentPages', vi.fn(() => [
{ route: 'pages/index/index', options: { tab: 'explain' } },
{ route: 'pages/explain/list', options: {} }
]))
const wrapper = mountExplainList()
await wrapper.get('[data-testid="explain-list-back"]').trigger('click')
expect(uni.navigateBack).not.toHaveBeenCalled()
expect(uni.reLaunch).toHaveBeenCalledWith(expect.objectContaining({
url: '/pages/index/index?tab=guide'
}))
wrapper.unmount()
})
it('navigateBack 失败后重启到导览首页', async () => {
vi.stubGlobal('getCurrentPages', vi.fn(() => [
{ route: 'pages/index/index', options: { tab: 'guide' } },
{ route: 'pages/explain/list', options: {} }
]))
vi.stubGlobal('uni', {
navigateBack: vi.fn((options: { fail?: () => void }) => options.fail?.()),
reLaunch: vi.fn(),
showToast: vi.fn()
})
const wrapper = mountExplainList()
await wrapper.get('[data-testid="explain-list-back"]').trigger('click')
expect(uni.reLaunch).toHaveBeenCalledWith(expect.objectContaining({
url: '/pages/index/index?tab=guide'
}))
wrapper.unmount()
})
it('重启导览首页失败时显示明确提示', async () => {
vi.stubGlobal('uni', {
navigateBack: vi.fn(),
reLaunch: vi.fn((options: { fail?: () => void }) => options.fail?.()),
showToast: vi.fn()
})
const wrapper = mountExplainList()
await wrapper.get('[data-testid="explain-list-back"]').trigger('click')
expect(uni.showToast).toHaveBeenCalledWith({
title: '返回导览首页失败,请重试',
icon: 'none'
})
wrapper.unmount()
})
it('根栈只创建一条历史标记,浏览器返回会回到导览首页', async () => {
hostEnvironmentMocks.embeddedInWechatMiniProgram = true
window.location.hash = '#/pages/explain/list?embedded=wechat-mini-program&weapp=1'
window.history.replaceState({}, '', window.location.href)
const wrapper = mountExplainList()
const pushState = vi.spyOn(window.history, 'pushState')
testState.onLoadHandler?.()
testState.onLoadHandler?.()
await flushPromises()
window.dispatchEvent(new PopStateEvent('popstate', { state: {} }))
await flushPromises()
expect(uni.navigateBack).not.toHaveBeenCalled()
expect(pushState).toHaveBeenCalledTimes(1)
const url = vi.mocked(uni.reLaunch).mock.calls[0]?.[0]?.url || ''
const query = new URLSearchParams(url.split('?')[1])
expect(url.split('?')[0]).toBe('/pages/index/index')
expect(query.get('tab')).toBe('guide')
expect(query.get('embedded')).toBe('wechat-mini-program')
expect(query.get('weapp')).toBe('1')
wrapper.unmount()
})
it('普通 H5 浏览器返回也会回到导览首页,且不调用 history.back', async () => {
const historyBack = vi.spyOn(window.history, 'back')
const wrapper = mountExplainList()
testState.onLoadHandler?.()
window.dispatchEvent(new PopStateEvent('popstate', { state: {} }))
await flushPromises()
expect(uni.reLaunch).toHaveBeenCalledWith(expect.objectContaining({
url: '/pages/index/index?tab=guide'
}))
expect(historyBack).not.toHaveBeenCalled()
wrapper.unmount()
})
it('正常内部页面栈不创建历史标记,并保留框架原有浏览器返回', async () => {
vi.stubGlobal('getCurrentPages', vi.fn(() => [
{ route: 'pages/index/index', options: { tab: 'guide' } },
{ route: 'pages/explain/list', options: {} }
]))
const wrapper = mountExplainList()
const pushState = vi.spyOn(window.history, 'pushState')
testState.onLoadHandler?.()
window.dispatchEvent(new PopStateEvent('popstate', { state: {} }))
await flushPromises()
expect(pushState).not.toHaveBeenCalled()
expect(uni.reLaunch).not.toHaveBeenCalled()
wrapper.unmount()
})
it('平台返回事件复用同一返回策略并阻止默认退出', () => {
const wrapper = mountExplainList()
expect(testState.onBackPressHandler?.()).toBe(true)
expect(uni.reLaunch).toHaveBeenCalledWith(expect.objectContaining({
url: '/pages/index/index?tab=guide'
}))
wrapper.unmount()
})
})