189 lines
5.5 KiB
TypeScript
189 lines
5.5 KiB
TypeScript
// @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()
|
||
})
|
||
})
|