Files
frontend-miniapp/tests/unit/IndexLaunchOverlay.spec.ts
lyf 6db0b71562
Some checks failed
CI / verify (push) Has been cancelled
修复首页模型加载超时切换室外地图
2026-07-16 00:05:18 +08:00

189 lines
5.5 KiB
TypeScript
Raw Permalink 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, nextTick, onMounted, onUnmounted } from 'vue'
import { flushPromises, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import IndexPage from '@/pages/index/index.vue'
const testState = vi.hoisted(() => ({
threeMapMountCount: 0,
threeMapUnmountCount: 0,
onShowHandler: null as null | (() => Promise<void> | void)
}))
vi.mock('@dcloudio/uni-app', () => ({
onLoad: vi.fn(),
onShow: (handler: () => Promise<void> | void) => {
testState.onShowHandler = handler
}
}))
vi.mock('@/utils/hostEnvironment', () => ({
isEmbeddedInWechatMiniProgram: () => false
}))
const modelSource = {
loadPackage: async () => ({ overviewModelUrl: '', floors: [] }),
loadFloorPois: async () => []
}
vi.mock('@/usecases/guideUseCase', () => ({
guideUseCase: {
getAssetBaseUrl: () => '/static/nav-assets',
getModelSource: () => modelSource,
normalizeFloorId: (value: string) => value,
getFloors: async () => []
}
}))
vi.mock('@/usecases/guideRouteUseCase', () => ({
guideRouteUseCase: {
listTargets: async () => [],
searchTargets: async () => [],
getRouteReadiness: async () => ({ ready: false, message: '' }),
planRoute: async () => null
}
}))
vi.mock('@/usecases/explainUseCase', () => ({
explainUseCase: {
loadExplainHalls: async () => [],
loadExplainHallSummaries: async () => ({}),
listHalls: async () => [],
searchExplain: async () => []
}
}))
const GuidePageFrameStub = defineComponent({
name: 'GuidePageFrame',
template: '<main><slot /></main>'
})
const GuideMapShellStub = defineComponent({
name: 'GuideMapShell',
props: {
mapType: { type: String, default: 'indoor' }
},
emits: ['initial-model-ready', 'initial-model-failed', 'mode-change'],
setup() {
onMounted(() => {
testState.threeMapMountCount += 1
})
onUnmounted(() => {
testState.threeMapUnmountCount += 1
})
return {}
},
template: `
<section data-testid="guide-map-shell">
<div v-if="mapType === 'indoor'" data-testid="three-map"></div>
<div v-else data-testid="tencent-map"></div>
</section>
`
})
const stubs = {
GuidePageFrame: GuidePageFrameStub,
GuideMapShell: GuideMapShellStub,
PoiSearchPanel: true,
RoutePlannerPanel: true,
ArrivalPanel: true,
ExplainHallSelect: true
}
const mountIndex = async () => {
const wrapper = mount(IndexPage, { global: { stubs } })
await flushPromises()
return wrapper
}
const initialModelReady = { view: 'overview' as const, floorId: 'L1' }
const initialModelFailed = { view: 'overview' as const, floorId: 'L1', message: 'network failed' }
beforeEach(() => {
vi.useFakeTimers()
testState.threeMapMountCount = 0
testState.threeMapUnmountCount = 0
testState.onShowHandler = null
window.history.replaceState({}, '', '/#/pages/index/index?tab=guide')
vi.stubGlobal('uni', {
navigateTo: vi.fn(),
navigateBack: vi.fn(),
reLaunch: vi.fn(),
showToast: vi.fn(),
hideKeyboard: vi.fn()
})
})
afterEach(() => {
vi.useRealTimers()
vi.restoreAllMocks()
vi.unstubAllGlobals()
})
describe('首页启动遮罩与馆内模型生命周期', () => {
it('12 秒超时后仍保持 indoor且不会挂载 TencentMap', async () => {
const wrapper = await mountIndex()
await vi.advanceTimersByTimeAsync(12000)
await vi.advanceTimersByTimeAsync(360)
expect(wrapper.getComponent(GuideMapShellStub).props('mapType')).toBe('indoor')
expect(wrapper.get('[data-testid="three-map"]').exists()).toBe(true)
expect(wrapper.find('[data-testid="tencent-map"]').exists()).toBe(false)
expect(testState.threeMapMountCount).toBe(1)
expect(testState.threeMapUnmountCount).toBe(0)
wrapper.unmount()
})
it('超时后的迟到 initialModelReady 仍可完成当前 ThreeMap 加载', async () => {
const wrapper = await mountIndex()
const shell = wrapper.getComponent(GuideMapShellStub)
await vi.advanceTimersByTimeAsync(12000)
shell.vm.$emit('initial-model-ready', initialModelReady)
await nextTick()
await vi.advanceTimersByTimeAsync(360)
expect(shell.props('mapType')).toBe('indoor')
expect(wrapper.find('.app-launch-overlay').exists()).toBe(false)
expect(testState.threeMapMountCount).toBe(1)
expect(testState.threeMapUnmountCount).toBe(0)
wrapper.unmount()
})
it('initialModelFailed 后保留 ThreeMap重试就绪不产生额外挂载', async () => {
const wrapper = await mountIndex()
const shell = wrapper.getComponent(GuideMapShellStub)
shell.vm.$emit('initial-model-failed', initialModelFailed)
await nextTick()
await vi.advanceTimersByTimeAsync(360)
expect(shell.props('mapType')).toBe('indoor')
expect(wrapper.get('[data-testid="three-map"]').exists()).toBe(true)
expect(wrapper.find('[data-testid="tencent-map"]').exists()).toBe(false)
shell.vm.$emit('initial-model-ready', initialModelReady)
await nextTick()
expect(testState.threeMapMountCount).toBe(1)
expect(testState.threeMapUnmountCount).toBe(0)
wrapper.unmount()
})
it('仅在用户主动切换到馆外 2D 时才挂载 TencentMap', async () => {
const wrapper = await mountIndex()
const shell = wrapper.getComponent(GuideMapShellStub)
shell.vm.$emit('mode-change', '2d')
await nextTick()
expect(shell.props('mapType')).toBe('outdoor')
expect(wrapper.get('[data-testid="tencent-map"]').exists()).toBe(true)
shell.vm.$emit('mode-change', '3d')
await nextTick()
expect(shell.props('mapType')).toBe('indoor')
wrapper.unmount()
})
})