376 lines
11 KiB
TypeScript
376 lines
11 KiB
TypeScript
// @vitest-environment happy-dom
|
|
|
|
import { defineComponent } from 'vue'
|
|
import { mount } from '@vue/test-utils'
|
|
import { describe, expect, it, vi } from 'vitest'
|
|
import GuideMapShell from '@/components/navigation/GuideMapShell.vue'
|
|
import {
|
|
clearGuidePerformanceRecords,
|
|
getGuidePerformanceRecords
|
|
} from '@/services/performance/guidePerformance'
|
|
|
|
const ThreeMapStub = defineComponent({
|
|
name: 'ThreeMap',
|
|
props: {
|
|
initialFloorId: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
initialView: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
visiblePoiIds: {
|
|
type: Array,
|
|
default: null
|
|
},
|
|
targetFocus: {
|
|
type: Object,
|
|
default: null
|
|
}
|
|
},
|
|
emits: ['floor-change', 'target-focus', 'auto-switch', 'render-mode-fallback'],
|
|
setup(_, { expose }) {
|
|
expose({
|
|
switchFloor: vi.fn(),
|
|
showMultiFloor: vi.fn(async () => true),
|
|
getGuideViewportState: vi.fn(() => ({
|
|
scene: 'floor',
|
|
floorId: 'L1',
|
|
centerX: 29.0598,
|
|
centerZ: 23.9424,
|
|
visibleWorldSpan: 270.39,
|
|
revision: 1
|
|
}))
|
|
})
|
|
return {}
|
|
},
|
|
template: '<div data-testid="three-map"></div>'
|
|
})
|
|
|
|
const modelSource = {
|
|
loadPackage: async () => ({ overviewModelUrl: '', floors: [] }),
|
|
loadFloorPois: async () => []
|
|
}
|
|
|
|
const mountShell = (visiblePoiIds: string[] | null) => mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [
|
|
{ id: 'L1', label: '1F' },
|
|
{ id: 'L2', label: '2F' }
|
|
],
|
|
activeFloor: 'L1',
|
|
visiblePoiIds
|
|
},
|
|
global: {
|
|
stubs: {
|
|
ThreeMap: ThreeMapStub
|
|
}
|
|
}
|
|
})
|
|
|
|
describe('GuideMapShell 点位过滤契约', () => {
|
|
it('馆内渲染模式使用单个开关,二维外观不显示楼层列表', async () => {
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [{ id: 'L1', label: '1F' }, { id: 'L2', label: '2F' }],
|
|
activeFloor: 'L1',
|
|
indoorView: 'overview',
|
|
indoorInitialView: 'overview',
|
|
indoorRenderMode: 'two-d',
|
|
showIndoorRenderModeToggle: true,
|
|
showFloor: true,
|
|
showFloorHeader: true,
|
|
showLayerModeToggle: true
|
|
},
|
|
global: { stubs: { ThreeMap: ThreeMapStub } }
|
|
})
|
|
|
|
const toggle = wrapper.get('[data-testid="indoor-render-mode-toggle"]')
|
|
expect(toggle.text()).toBe('2D')
|
|
expect(wrapper.findAll('[data-testid="indoor-render-mode-toggle"]')).toHaveLength(1)
|
|
expect(wrapper.find('.floor-switcher').exists()).toBe(false)
|
|
expect(wrapper.find('.floor-header').exists()).toBe(false)
|
|
expect(wrapper.find('.layer-mode-toggle').exists()).toBe(false)
|
|
|
|
await toggle.trigger('tap')
|
|
expect(wrapper.emitted('indoorRenderModeChange')).toEqual([['three-d']])
|
|
expect(wrapper.emitted('sceneViewportChange')).toBeUndefined()
|
|
})
|
|
|
|
it('二维单层视图仍显示楼层栏,楼层栏只取决于业务场景', () => {
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [{ id: 'L1', label: '1F' }, { id: 'L2', label: '2F' }],
|
|
activeFloor: 'L1',
|
|
indoorView: 'floor',
|
|
indoorRenderMode: 'two-d',
|
|
showFloor: true,
|
|
showFloorHeader: true
|
|
},
|
|
global: { stubs: { ThreeMap: ThreeMapStub } }
|
|
})
|
|
|
|
expect(wrapper.find('.floor-switcher').exists()).toBe(true)
|
|
expect(wrapper.find('.floor-header').exists()).toBe(false)
|
|
})
|
|
|
|
it('外观路线和起点选择状态也不会显示室内楼层栏', () => {
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [{ id: 'L1', label: '1F' }, { id: 'L2', label: '2F' }],
|
|
activeFloor: 'L1',
|
|
indoorView: 'overview',
|
|
showFloor: true,
|
|
showRoute: true,
|
|
routeStartSelectionActive: true
|
|
},
|
|
global: { stubs: { ThreeMap: ThreeMapStub } }
|
|
})
|
|
|
|
expect(wrapper.find('.floor-switcher').exists()).toBe(false)
|
|
})
|
|
|
|
it.each([
|
|
['不过滤时透传 null', null],
|
|
['无结果时透传空列表', []],
|
|
['有结果时透传精确 ID', ['poi-a', 'poi-b']]
|
|
])('%s', (_label, visiblePoiIds) => {
|
|
const wrapper = mountShell(visiblePoiIds as string[] | null)
|
|
expect(wrapper.getComponent(ThreeMapStub).props('visiblePoiIds')).toEqual(visiblePoiIds)
|
|
})
|
|
|
|
it('原样转发跨楼层与目标聚焦结果', async () => {
|
|
const wrapper = mountShell(['poi-l2'])
|
|
const renderer = wrapper.getComponent(ThreeMapStub)
|
|
const focusResult = {
|
|
requestId: 3,
|
|
poiId: 'poi-l2',
|
|
floorId: 'L2',
|
|
status: 'focused' as const
|
|
}
|
|
|
|
renderer.vm.$emit('floor-change', 'L2')
|
|
renderer.vm.$emit('target-focus', focusResult)
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.emitted('floorChange')).toEqual([['L2']])
|
|
expect(wrapper.emitted('targetFocus')).toEqual([[focusResult]])
|
|
})
|
|
|
|
it('忽略旧渲染版本的 WebP 回退事件', async () => {
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [{ id: 'L1', label: '1F' }, { id: 'L2', label: '2F' }],
|
|
activeFloor: 'L1',
|
|
indoorView: 'floor',
|
|
sceneRevision: 2
|
|
},
|
|
global: { stubs: { ThreeMap: ThreeMapStub } }
|
|
})
|
|
const renderer = wrapper.getComponent(ThreeMapStub)
|
|
|
|
renderer.vm.$emit('render-mode-fallback', {
|
|
mode: 'two-d',
|
|
view: 'floor',
|
|
floorId: 'L2',
|
|
reason: 'model-load-timeout',
|
|
sceneRevision: 1
|
|
})
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.emitted('indoorRenderModeFallback')).toBeUndefined()
|
|
})
|
|
|
|
it('动态楼层业务视图不会覆盖首次外观模型初始化视图', () => {
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [
|
|
{ id: 'L1', label: '1F' },
|
|
{ id: 'L2', label: '2F' }
|
|
],
|
|
activeFloor: 'L2',
|
|
indoorView: 'floor',
|
|
indoorInitialView: 'overview'
|
|
},
|
|
global: {
|
|
stubs: {
|
|
ThreeMap: ThreeMapStub
|
|
}
|
|
}
|
|
})
|
|
|
|
const renderer = wrapper.getComponent(ThreeMapStub)
|
|
expect(renderer.props('initialFloorId')).toBe('L2')
|
|
expect(renderer.props('initialView')).toBe('overview')
|
|
})
|
|
|
|
it('外观状态点击当前业务楼层仍请求渲染器,且等待 floor-change 才提交业务视图', async () => {
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [{ id: 'L2', label: '2F' }],
|
|
activeFloor: 'L2',
|
|
indoorView: 'overview',
|
|
indoorInitialView: 'overview'
|
|
},
|
|
global: { stubs: { ThreeMap: ThreeMapStub } }
|
|
})
|
|
const renderer = wrapper.getComponent(ThreeMapStub)
|
|
|
|
await (wrapper.vm as any).switchFloor('L2')
|
|
expect((renderer.vm as any).switchFloor).toHaveBeenCalledWith('L2')
|
|
expect(wrapper.emitted('indoorViewChange')).toBeUndefined()
|
|
|
|
renderer.vm.$emit('floor-change', 'L2')
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.emitted('indoorViewChange')).toEqual([['floor']])
|
|
})
|
|
|
|
it('measures a manual floor switch until ThreeMap commits the target floor', async () => {
|
|
clearGuidePerformanceRecords()
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [
|
|
{ id: 'L1', label: '1F' },
|
|
{ id: 'L2', label: '2F' }
|
|
],
|
|
activeFloor: 'L1',
|
|
indoorView: 'floor'
|
|
},
|
|
global: { stubs: { ThreeMap: ThreeMapStub } }
|
|
})
|
|
const renderer = wrapper.getComponent(ThreeMapStub)
|
|
|
|
;(wrapper.vm as any).switchFloor('L2')
|
|
renderer.vm.$emit('floor-change', 'L2')
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(getGuidePerformanceRecords()).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({
|
|
kind: 'interaction',
|
|
name: 'floor-switch',
|
|
outcome: 'success',
|
|
detail: expect.objectContaining({ floorId: 'L2', source: 'manual' })
|
|
})
|
|
]))
|
|
})
|
|
|
|
it('起点选择阶段处于外观视图时仍隐藏室内楼层栏', () => {
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [
|
|
{ id: 'L1', label: '1F' },
|
|
{ id: 'L2', label: '2F' }
|
|
],
|
|
activeFloor: 'L1',
|
|
indoorView: 'overview',
|
|
showFloor: true,
|
|
routeStartSelectionActive: true
|
|
},
|
|
global: { stubs: { ThreeMap: ThreeMapStub } }
|
|
})
|
|
|
|
expect(wrapper.find('.floor-switcher').exists()).toBe(false)
|
|
})
|
|
|
|
it('ThreeMap 自动切换由父级提交后隐藏楼层栏', async () => {
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [
|
|
{ id: 'L1', label: '1F' },
|
|
{ id: 'L2', label: '2F' }
|
|
],
|
|
activeFloor: 'L1',
|
|
indoorView: 'floor',
|
|
showFloor: true
|
|
},
|
|
global: { stubs: { ThreeMap: ThreeMapStub } }
|
|
})
|
|
const renderer = wrapper.getComponent(ThreeMapStub)
|
|
expect(wrapper.find('.floor-switcher').exists()).toBe(true)
|
|
|
|
renderer.vm.$emit('auto-switch', {
|
|
from: 'floor',
|
|
to: 'overview',
|
|
trigger: 'zoom-out',
|
|
distance: 320
|
|
})
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.find('.floor-switcher').exists()).toBe(true)
|
|
expect(wrapper.emitted('autoSwitch')).toHaveLength(1)
|
|
|
|
await wrapper.setProps({ indoorView: 'overview' })
|
|
expect(wrapper.find('.floor-switcher').exists()).toBe(false)
|
|
})
|
|
|
|
it('多层视图只显示单层切换按钮,不显示单层楼层列表', () => {
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [
|
|
{ id: 'L1', label: '1F' },
|
|
{ id: 'L2', label: '2F' }
|
|
],
|
|
activeFloor: 'L2',
|
|
indoorView: 'multi',
|
|
layerMode: 'multi',
|
|
showFloor: true,
|
|
showFloorHeader: true
|
|
},
|
|
global: { stubs: { ThreeMap: ThreeMapStub } }
|
|
})
|
|
|
|
expect(wrapper.find('.floor-header-label').text()).toBe('单层')
|
|
expect(wrapper.find('.floor-list').exists()).toBe(false)
|
|
})
|
|
|
|
it('父组件楼层短暂为空时,仍用最后提交的楼层返回单层', async () => {
|
|
const wrapper = mount(GuideMapShell, {
|
|
props: {
|
|
mapType: 'indoor',
|
|
indoorModelSource: modelSource,
|
|
floors: [
|
|
{ id: 'L1', label: '1F' },
|
|
{ id: 'L2', label: '2F' }
|
|
],
|
|
activeFloor: 'L1',
|
|
indoorView: 'floor',
|
|
layerMode: 'single',
|
|
showFloor: true,
|
|
showFloorHeader: true
|
|
},
|
|
global: { stubs: { ThreeMap: ThreeMapStub } }
|
|
})
|
|
const renderer = wrapper.getComponent(ThreeMapStub)
|
|
|
|
await (wrapper.vm as any).setLayerMode('multi')
|
|
await wrapper.setProps({ indoorView: 'multi', layerMode: 'multi', activeFloor: '' })
|
|
await (wrapper.vm as any).setLayerMode('single')
|
|
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
|
|
expect((renderer.vm as any).switchFloor).toHaveBeenCalledWith('L1')
|
|
})
|
|
})
|