This commit is contained in:
@@ -1,26 +1,188 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
const indexPageSource = readFileSync(
|
||||
fileURLToPath(new URL('../../src/pages/index/index.vue', import.meta.url)),
|
||||
'utf8'
|
||||
)
|
||||
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 launchOverlaySource = indexPageSource.slice(
|
||||
indexPageSource.indexOf('v-if="launchOverlayVisible"'),
|
||||
indexPageSource.indexOf('</GuidePageFrame>')
|
||||
)
|
||||
const testState = vi.hoisted(() => ({
|
||||
threeMapMountCount: 0,
|
||||
threeMapUnmountCount: 0,
|
||||
onShowHandler: null as null | (() => Promise<void> | void)
|
||||
}))
|
||||
|
||||
describe('首页精简加载页', () => {
|
||||
it('仅展示图形标识、文字馆名和原生加载动效', () => {
|
||||
expect(launchOverlaySource).toContain('/static/guide/museum-brand-icons.webp')
|
||||
expect(launchOverlaySource).toContain('SHENZHEN NATURAL HISTORY MUSEUM')
|
||||
expect(launchOverlaySource).toContain('深圳自然博物馆')
|
||||
expect(launchOverlaySource).toContain('<progress')
|
||||
expect(launchOverlaySource).not.toContain('app-launch-loading-lqip.webp')
|
||||
expect(launchOverlaySource).not.toContain('app-launch-loading.webp')
|
||||
expect(launchOverlaySource).not.toContain('app-launch-kicker')
|
||||
expect(launchOverlaySource).not.toContain('app-launch-status')
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user