1625 lines
56 KiB
TypeScript
1625 lines
56 KiB
TypeScript
// @vitest-environment happy-dom
|
|
|
|
import { defineComponent, 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'
|
|
import type { GuideRouteResult } from '@/domain/museum'
|
|
import type { GuideRoutePlanRequest, GuideRoutePlanResult } from '@/usecases/guideRouteUseCase'
|
|
|
|
const testState = vi.hoisted(() => ({
|
|
mapMountCount: 0,
|
|
mapUnmountCount: 0,
|
|
resetToViewBaselineCalls: [] as Array<{
|
|
view: 'overview' | 'floor'
|
|
floorId?: string
|
|
reason: 'poi-detail-close' | 'manual-reset' | 'floor-reset'
|
|
}>,
|
|
searchMountCount: 0,
|
|
searchUnmountCount: 0,
|
|
searchRestoreCount: 0,
|
|
searchCollapseAfterDetailCount: 0,
|
|
searchResetCount: 0,
|
|
onShowHandler: null as null | (() => Promise<void> | void),
|
|
halls: [] as Array<{ id: string; poiId?: string; location?: { poiId?: string }; name: string }>,
|
|
hallResolutionPromises: [] as Array<Promise<Array<{
|
|
id: string
|
|
poiId?: string
|
|
location?: { poiId?: string }
|
|
name: string
|
|
}>>>,
|
|
explainResults: [] as Array<{
|
|
type: 'hall' | 'exhibit'
|
|
hallId?: string
|
|
exhibitId?: string
|
|
}>,
|
|
switchFloorCalls: [] as string[],
|
|
routeTargets: [] as Array<{
|
|
poiId: string
|
|
name: string
|
|
floorId: string
|
|
floorLabel: string
|
|
categoryLabel?: string
|
|
routeNodeId: string
|
|
}>,
|
|
routePlanResult: {
|
|
route: null,
|
|
error: ''
|
|
} as GuideRoutePlanResult,
|
|
routePlanRequests: [] as GuideRoutePlanRequest[]
|
|
}))
|
|
|
|
const hostEnvironmentMocks = vi.hoisted(() => ({
|
|
embeddedInWechatMiniProgram: false
|
|
}))
|
|
|
|
const outdoorMapMocks = vi.hoisted(() => ({
|
|
getOutdoorMapProfile: vi.fn(async () => ({
|
|
enabled: true,
|
|
h5Enabled: true,
|
|
available: true,
|
|
state: 'AVAILABLE',
|
|
message: '腾讯地图服务探测正常',
|
|
quotaStatus: 'NOT_QUERYABLE',
|
|
availableModes: ['DRIVING', 'WALKING', 'TRANSIT'],
|
|
jsMapEnabled: true,
|
|
jsMapKey: 'test-js-key'
|
|
})),
|
|
refreshOutdoorMapProfile: vi.fn(async () => ({
|
|
enabled: true,
|
|
h5Enabled: true,
|
|
available: true,
|
|
state: 'AVAILABLE',
|
|
message: '腾讯地图服务探测正常',
|
|
quotaStatus: 'NOT_QUERYABLE',
|
|
availableModes: ['DRIVING', 'WALKING', 'TRANSIT'],
|
|
jsMapEnabled: true,
|
|
jsMapKey: 'test-js-key'
|
|
})),
|
|
subscribeOutdoorMapAvailability: vi.fn(() => () => {}),
|
|
searchOutdoorPlaces: vi.fn(async () => ({
|
|
items: [{
|
|
id: 'bus-stop-museum',
|
|
title: '深圳自然博物馆[公交站]',
|
|
address: 'M574路',
|
|
category: '公交站',
|
|
latitude: 22.69258,
|
|
longitude: 114.36372,
|
|
distance: 80
|
|
}]
|
|
})),
|
|
suggestOutdoorPlaces: vi.fn(async () => ({
|
|
items: [{
|
|
id: 'manual-start-suggestion',
|
|
title: '深圳北站',
|
|
address: '深圳市龙华区',
|
|
category: '火车站',
|
|
latitude: 22.609,
|
|
longitude: 114.03
|
|
}]
|
|
})),
|
|
reverseGeocodeOutdoorLocation: vi.fn(async () => ({
|
|
address: '深圳市坪山区龙坪路 1 号',
|
|
latitude: 22.688,
|
|
longitude: 114.359
|
|
})),
|
|
planOutdoorRoute: vi.fn()
|
|
}))
|
|
|
|
const browserLocationMocks = vi.hoisted(() => ({
|
|
getBrowserGcjLocation: vi.fn(),
|
|
getBrowserLocationPermissionState: vi.fn()
|
|
}))
|
|
|
|
vi.mock('@dcloudio/uni-app', () => ({
|
|
onLoad: vi.fn(),
|
|
onShow: (handler: () => Promise<void> | void) => {
|
|
testState.onShowHandler = handler
|
|
}
|
|
}))
|
|
|
|
vi.mock('@/utils/hostEnvironment', () => ({
|
|
isEmbeddedInWechatMiniProgram: () => hostEnvironmentMocks.embeddedInWechatMiniProgram
|
|
}))
|
|
|
|
vi.mock('@/services/outdoor/OutdoorMapAppService', () => outdoorMapMocks)
|
|
|
|
vi.mock('@/services/outdoor/BrowserLocationService', () => ({
|
|
BrowserLocationError: class BrowserLocationError extends Error {
|
|
reason: string
|
|
|
|
constructor(reason: string) {
|
|
super(reason)
|
|
this.reason = reason
|
|
}
|
|
},
|
|
getBrowserGcjLocation: browserLocationMocks.getBrowserGcjLocation,
|
|
getBrowserLocationPermissionState: browserLocationMocks.getBrowserLocationPermissionState
|
|
}))
|
|
|
|
const floors = [
|
|
{ id: 'L1', label: '1F', order: 1 },
|
|
{ id: 'L2', label: '2F', order: 2 }
|
|
]
|
|
|
|
const modelSource = {
|
|
loadPackage: async () => ({ overviewModelUrl: '', floors: [] }),
|
|
loadFloorPois: async () => []
|
|
}
|
|
|
|
vi.mock('@/usecases/guideUseCase', () => ({
|
|
guideUseCase: {
|
|
getAssetBaseUrl: () => '/static/nav-assets',
|
|
getModelSource: () => modelSource,
|
|
normalizeFloorId: (value: string) => (
|
|
value === '1F' ? 'L1' : value === '2F' ? 'L2' : value
|
|
),
|
|
getFloors: async () => floors
|
|
}
|
|
}))
|
|
|
|
vi.mock('@/usecases/guideRouteUseCase', () => ({
|
|
guideRouteUseCase: {
|
|
listTargets: async () => testState.routeTargets,
|
|
searchTargets: async () => testState.routeTargets,
|
|
getRouteReadiness: async () => ({ ready: true, message: '' }),
|
|
planRoute: async (request: GuideRoutePlanRequest) => {
|
|
testState.routePlanRequests.push(request)
|
|
return testState.routePlanResult
|
|
}
|
|
}
|
|
}))
|
|
|
|
vi.mock('@/usecases/explainUseCase', () => ({
|
|
explainUseCase: {
|
|
loadExplainHalls: async () => [],
|
|
loadExplainHallSummaries: async () => ({}),
|
|
listHalls: async () => (
|
|
testState.hallResolutionPromises.shift() || Promise.resolve(testState.halls)
|
|
),
|
|
searchExplain: async () => testState.explainResults
|
|
}
|
|
}))
|
|
|
|
const GuidePageFrameStub = defineComponent({
|
|
name: 'GuidePageFrame',
|
|
template: '<main><slot /></main>'
|
|
})
|
|
|
|
const GuideMapShellStub = defineComponent({
|
|
name: 'GuideMapShell',
|
|
props: {
|
|
activeFloor: { type: String, default: '' },
|
|
indoorView: { type: String, default: '' },
|
|
visiblePoiIds: { type: Array, default: null },
|
|
targetFocusRequest: { type: Object, default: null },
|
|
mapType: { type: String, default: 'indoor' },
|
|
outdoorMarkers: { type: Array, default: () => [] },
|
|
activeOutdoorMarkerId: { type: String, default: '' },
|
|
showFloor: { type: Boolean, default: false },
|
|
floorBottom: { type: String, default: '' },
|
|
disableAutoExit: { type: Boolean, default: false },
|
|
routeStartSelectionActive: { type: Boolean, default: false },
|
|
routeSelectablePoiIds: { type: Array, default: () => [] }
|
|
},
|
|
emits: ['floor-change', 'poi-click', 'selection-clear', 'indoor-view-change', 'auto-switch', 'semantic-exit-outdoor', 'route-start-candidate', 'route-start-candidate-rejected', 'route-roaming-progress', 'map-tap'],
|
|
setup() {
|
|
onMounted(() => {
|
|
testState.mapMountCount += 1
|
|
})
|
|
onUnmounted(() => {
|
|
testState.mapUnmountCount += 1
|
|
})
|
|
return {}
|
|
},
|
|
methods: {
|
|
resetToViewBaseline(options: {
|
|
view: 'overview' | 'floor'
|
|
floorId?: string
|
|
reason: 'poi-detail-close' | 'manual-reset' | 'floor-reset'
|
|
}) {
|
|
testState.resetToViewBaselineCalls.push(options)
|
|
return true
|
|
},
|
|
switchFloor(floorId: string) {
|
|
testState.switchFloorCalls.push(floorId)
|
|
}
|
|
},
|
|
template: '<section data-testid="guide-map"><slot name="overlay" /><slot /></section>'
|
|
})
|
|
|
|
const PoiSearchPanelStub = defineComponent({
|
|
name: 'PoiSearchPanel',
|
|
props: {
|
|
currentFloorId: { type: String, default: '' },
|
|
currentFloorLabel: { type: String, default: '' }
|
|
},
|
|
emits: [
|
|
'expanded-change',
|
|
'category-mode-change',
|
|
'results-change',
|
|
'result-tap',
|
|
'cancel'
|
|
],
|
|
setup() {
|
|
onMounted(() => {
|
|
testState.searchMountCount += 1
|
|
})
|
|
onUnmounted(() => {
|
|
testState.searchUnmountCount += 1
|
|
})
|
|
return {}
|
|
},
|
|
methods: {
|
|
collapseHomePanel() {},
|
|
resetSearchState() {
|
|
testState.searchResetCount += 1
|
|
},
|
|
returnToCollapsedAfterDetail() {
|
|
testState.searchCollapseAfterDetailCount += 1
|
|
},
|
|
restoreResultListScroll() {
|
|
testState.searchRestoreCount += 1
|
|
}
|
|
},
|
|
template: '<div data-testid="poi-search"></div>'
|
|
})
|
|
|
|
const RoutePlannerPanelStub = defineComponent({
|
|
name: 'RoutePlannerPanel',
|
|
props: {
|
|
endPoint: { type: Object, default: null },
|
|
startCandidate: { type: Object, default: null }
|
|
},
|
|
emits: ['confirm-start', 'cancel-start', 'simulate-guide', 'back'],
|
|
template: '<section data-testid="route-planner"></section>'
|
|
})
|
|
|
|
const ArrivalPanelStub = defineComponent({
|
|
name: 'ArrivalPanel',
|
|
props: {
|
|
mode: { type: String, default: 'browse' },
|
|
activeType: { type: String, default: null },
|
|
targets: { type: Array, default: () => [] },
|
|
selectedTarget: { type: Object, default: null }
|
|
},
|
|
emits: ['type-change', 'select-target', 'navigate', 'back', 'retry', 'close'],
|
|
template: '<section data-testid="arrival-panel"></section>'
|
|
})
|
|
|
|
const OutdoorArrivalRoutePanelStub = defineComponent({
|
|
name: 'OutdoorArrivalRoutePanel',
|
|
props: {
|
|
phase: { type: String, default: 'locating' },
|
|
locationHelpVisible: { type: Boolean, default: false },
|
|
startPoint: { type: Object, default: null },
|
|
destination: { type: Object, default: null },
|
|
loading: { type: Boolean, default: false },
|
|
routeError: { type: String, default: '' },
|
|
manualSearchResults: { type: Array, default: () => [] }
|
|
},
|
|
emits: [
|
|
'retry-location',
|
|
'manual-start-request',
|
|
'manual-map-request',
|
|
'manual-start-confirm',
|
|
'manual-start-search',
|
|
'confirm-start',
|
|
'retry-route',
|
|
'back'
|
|
],
|
|
template: '<section data-testid="outdoor-route-panel"></section>'
|
|
})
|
|
|
|
const stubs = {
|
|
GuidePageFrame: GuidePageFrameStub,
|
|
GuideMapShell: GuideMapShellStub,
|
|
PoiSearchPanel: PoiSearchPanelStub,
|
|
RoutePlannerPanel: RoutePlannerPanelStub,
|
|
ArrivalPanel: ArrivalPanelStub,
|
|
OutdoorArrivalRoutePanel: OutdoorArrivalRoutePanelStub,
|
|
ExplainHallSelect: true
|
|
}
|
|
|
|
const poi = {
|
|
id: 'poi-l2-restroom',
|
|
name: '二层卫生间',
|
|
floorId: 'L2',
|
|
floorLabel: '2F',
|
|
primaryCategory: { id: 'restroom', label: '卫生间' },
|
|
categories: [{ id: 'restroom', label: '卫生间' }],
|
|
positionGltf: [12, 3, 24] as [number, number, number],
|
|
accessible: true
|
|
}
|
|
|
|
const categoryContext = {
|
|
origin: 'home' as const,
|
|
keyword: '卫生间',
|
|
categoryId: 'restroom' as const,
|
|
floorId: 'L2',
|
|
floorLabel: '2F',
|
|
resultCount: 5,
|
|
floorResultCount: 2,
|
|
visiblePoiIds: ['poi-l2-restroom', 'poi-l2-restroom-2'],
|
|
listScrollTop: 126
|
|
}
|
|
|
|
const mountIndex = async () => {
|
|
const wrapper = mount(IndexPage, {
|
|
global: { stubs }
|
|
})
|
|
await flushPromises()
|
|
return wrapper
|
|
}
|
|
|
|
const confirmLatestNavigation = () => {
|
|
const navigation = vi.mocked(uni.navigateTo).mock.calls.at(-1)?.[0]
|
|
navigation?.success?.({ errMsg: 'navigateTo:ok' } as never)
|
|
}
|
|
|
|
beforeEach(() => {
|
|
hostEnvironmentMocks.embeddedInWechatMiniProgram = false
|
|
testState.mapMountCount = 0
|
|
testState.mapUnmountCount = 0
|
|
testState.resetToViewBaselineCalls = []
|
|
testState.switchFloorCalls = []
|
|
testState.routePlanResult = { route: null, error: '' }
|
|
testState.routePlanRequests = []
|
|
testState.searchMountCount = 0
|
|
testState.searchUnmountCount = 0
|
|
testState.searchRestoreCount = 0
|
|
testState.searchCollapseAfterDetailCount = 0
|
|
testState.searchResetCount = 0
|
|
testState.onShowHandler = null
|
|
testState.halls = []
|
|
testState.hallResolutionPromises = []
|
|
testState.explainResults = []
|
|
testState.routeTargets = []
|
|
outdoorMapMocks.searchOutdoorPlaces.mockClear()
|
|
outdoorMapMocks.suggestOutdoorPlaces.mockClear()
|
|
outdoorMapMocks.reverseGeocodeOutdoorLocation.mockClear()
|
|
outdoorMapMocks.planOutdoorRoute.mockClear()
|
|
browserLocationMocks.getBrowserGcjLocation.mockReset()
|
|
browserLocationMocks.getBrowserLocationPermissionState.mockReset()
|
|
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.restoreAllMocks()
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
describe('首页搜索与地图闭环', () => {
|
|
it('微信内嵌展开搜索只写入一条覆盖层历史记录', async () => {
|
|
hostEnvironmentMocks.embeddedInWechatMiniProgram = true
|
|
window.location.hash = '#/pages/index/index?tab=guide&embedded=wechat-mini-program'
|
|
window.history.replaceState({}, '', window.location.href)
|
|
const wrapper = await mountIndex()
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
const pushState = vi.spyOn(window.history, 'pushState')
|
|
|
|
search.vm.$emit('expanded-change', true)
|
|
await flushPromises()
|
|
expect(pushState).toHaveBeenCalledTimes(1)
|
|
expect(pushState).toHaveBeenLastCalledWith(
|
|
expect.objectContaining({ museumGuideOverlay: 'poi-search' }),
|
|
'',
|
|
window.location.href
|
|
)
|
|
|
|
search.vm.$emit('expanded-change', true)
|
|
await flushPromises()
|
|
expect(pushState).toHaveBeenCalledTimes(1)
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('微信内嵌搜索返回消费 popstate 并回到首页收缩态', async () => {
|
|
hostEnvironmentMocks.embeddedInWechatMiniProgram = true
|
|
window.location.hash = '#/pages/index/index?tab=guide&embedded=wechat-mini-program'
|
|
window.history.replaceState({}, '', window.location.href)
|
|
const wrapper = await mountIndex()
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
const currentUrl = window.location.href
|
|
|
|
search.vm.$emit('expanded-change', true)
|
|
await flushPromises()
|
|
window.dispatchEvent(new PopStateEvent('popstate', { state: {} }))
|
|
await flushPromises()
|
|
|
|
expect(testState.searchResetCount).toBeGreaterThan(0)
|
|
expect(window.location.href).toBe(currentUrl)
|
|
expect(wrapper.getComponent(GuideMapShellStub).props('visiblePoiIds')).toBeNull()
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('微信内嵌搜索多次展开和收缩不会累积覆盖层历史', async () => {
|
|
hostEnvironmentMocks.embeddedInWechatMiniProgram = true
|
|
window.location.hash = '#/pages/index/index?tab=guide&embedded=wechat-mini-program'
|
|
window.history.replaceState({}, '', window.location.href)
|
|
const wrapper = await mountIndex()
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
let currentHistoryState: Record<string, unknown> = {}
|
|
vi.spyOn(window.history, 'state', 'get').mockImplementation(() => currentHistoryState)
|
|
const pushState = vi.spyOn(window.history, 'pushState').mockImplementation((state) => {
|
|
currentHistoryState = state as Record<string, unknown>
|
|
})
|
|
const historyBack = vi.spyOn(window.history, 'back').mockImplementation(() => {
|
|
currentHistoryState = {}
|
|
window.dispatchEvent(new PopStateEvent('popstate', { state: {} }))
|
|
})
|
|
|
|
search.vm.$emit('expanded-change', true)
|
|
search.vm.$emit('expanded-change', false)
|
|
await flushPromises()
|
|
search.vm.$emit('expanded-change', true)
|
|
search.vm.$emit('expanded-change', false)
|
|
await flushPromises()
|
|
|
|
expect(pushState).toHaveBeenCalledTimes(2)
|
|
expect(historyBack).toHaveBeenCalledTimes(2)
|
|
expect(currentHistoryState).not.toMatchObject({ museumGuideOverlay: 'poi-search' })
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('普通 H5 展开搜索不写入覆盖层历史,也不消费浏览器返回', async () => {
|
|
const wrapper = await mountIndex()
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
const pushState = vi.spyOn(window.history, 'pushState')
|
|
|
|
search.vm.$emit('expanded-change', true)
|
|
window.dispatchEvent(new PopStateEvent('popstate', { state: {} }))
|
|
await flushPromises()
|
|
|
|
expect(pushState).not.toHaveBeenCalled()
|
|
expect(testState.searchResetCount).toBe(0)
|
|
wrapper.unmount()
|
|
})
|
|
|
|
it('同步首页标签时保留内嵌宿主参数', async () => {
|
|
window.location.hash = '#/pages/index/index?tab=guide&embedded=wechat-mini-program'
|
|
|
|
await mountIndex()
|
|
|
|
expect(window.location.hash).toBe('#/pages/index/index?tab=guide&embedded=wechat-mini-program')
|
|
})
|
|
|
|
it('首页来馆入口默认聚焦博物馆,仅在选择类别后读取并联动附近点位', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
const arrivalAction = wrapper.findAll('.guide-quick-action')[0]
|
|
const guideAction = wrapper.findAll('.guide-quick-action')[1]
|
|
|
|
expect(wrapper.findAll('.guide-quick-action').map((item) => item.text())).toEqual([
|
|
'来馆',
|
|
'导览',
|
|
'讲解'
|
|
])
|
|
|
|
await guideAction.trigger('tap')
|
|
await flushPromises()
|
|
expect(map.props('indoorView')).toBe('overview')
|
|
map.vm.$emit('floor-change', 'L1')
|
|
await flushPromises()
|
|
expect(map.props('indoorView')).toBe('floor')
|
|
expect(wrapper.findAll('.guide-quick-action')[1]?.text()).toBe('导览')
|
|
|
|
await guideAction.trigger('tap')
|
|
await flushPromises()
|
|
expect(map.props('indoorView')).toBe('overview')
|
|
expect(wrapper.findAll('.guide-quick-action')[1]?.text()).toBe('导览')
|
|
|
|
await arrivalAction.trigger('tap')
|
|
await flushPromises()
|
|
expect(map.props('mapType')).toBe('outdoor')
|
|
const arrivalPanel = wrapper.getComponent(ArrivalPanelStub)
|
|
expect(arrivalPanel.props('mode')).toBe('browse')
|
|
expect(arrivalPanel.props('activeType')).toBeNull()
|
|
expect(arrivalPanel.props('targets')).toEqual([])
|
|
expect(map.props('outdoorMarkers')).toEqual([expect.objectContaining({
|
|
id: 'museum-destination',
|
|
title: '深圳自然博物馆'
|
|
})])
|
|
expect(map.props('activeOutdoorMarkerId')).toBe('museum-destination')
|
|
|
|
arrivalPanel.vm.$emit('type-change', 'bus')
|
|
await flushPromises()
|
|
expect(arrivalPanel.props('activeType')).toBe('bus')
|
|
expect(arrivalPanel.props('targets')).toEqual([expect.objectContaining({
|
|
id: 'bus-stop-museum',
|
|
title: '深圳自然博物馆[公交站]'
|
|
})])
|
|
expect(map.props('outdoorMarkers')).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ id: 'museum-destination', markerKind: 'museum' }),
|
|
expect.objectContaining({ id: 'bus-stop-museum', markerKind: 'bus' })
|
|
]))
|
|
|
|
const busTarget = arrivalPanel.props('targets')[0] as { id: string; type: string }
|
|
arrivalPanel.vm.$emit('select-target', busTarget)
|
|
await flushPromises()
|
|
expect(arrivalPanel.props('mode')).toBe('detail')
|
|
expect(map.props('activeOutdoorMarkerId')).toBe('bus-stop-museum')
|
|
expect(wrapper.find('.guide-quick-actions').classes()).toContain('is-outdoor')
|
|
expect(wrapper.findAll('.guide-quick-action').map((item) => item.text())).toEqual([
|
|
'来馆',
|
|
'导览',
|
|
'讲解'
|
|
])
|
|
expect(wrapper.findAll('.guide-quick-action')[0]?.classes()).toContain('active')
|
|
})
|
|
|
|
it('腾讯地图状态不可用时隐藏来馆入口并保持馆内导览', async () => {
|
|
outdoorMapMocks.refreshOutdoorMapProfile.mockResolvedValueOnce({
|
|
enabled: true,
|
|
h5Enabled: false,
|
|
available: false,
|
|
state: 'PROVIDER_UNAVAILABLE',
|
|
message: '腾讯地图服务暂时不可用',
|
|
quotaStatus: 'NOT_QUERYABLE',
|
|
availableModes: [],
|
|
jsMapEnabled: false,
|
|
jsMapKey: null
|
|
})
|
|
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
expect(wrapper.findAll('.guide-quick-action').map((item) => item.text())).toEqual(['导览', '讲解'])
|
|
map.vm.$emit('semantic-exit-outdoor')
|
|
await flushPromises()
|
|
expect(map.props('mapType')).toBe('indoor')
|
|
})
|
|
|
|
it('地铁和停车场分类同样在地图上显示可点击的高亮标记', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
const arrivalAction = wrapper.findAll('.guide-quick-action')[0]
|
|
|
|
await arrivalAction.trigger('tap')
|
|
await flushPromises()
|
|
const arrivalPanel = wrapper.getComponent(ArrivalPanelStub)
|
|
|
|
arrivalPanel.vm.$emit('type-change', 'metro')
|
|
await flushPromises()
|
|
expect(map.props('outdoorMarkers')).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ id: 'museum-destination', markerKind: 'museum' }),
|
|
expect.objectContaining({ id: 'bus-stop-museum', markerKind: 'metro' })
|
|
]))
|
|
|
|
arrivalPanel.vm.$emit('type-change', 'parking')
|
|
await flushPromises()
|
|
expect(map.props('outdoorMarkers')).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ id: 'museum-destination', markerKind: 'museum' }),
|
|
expect.objectContaining({ id: 'bus-stop-museum', markerKind: 'parking' })
|
|
]))
|
|
})
|
|
|
|
it('来馆分类在当前页面会复用短时缓存,不重复查询腾讯地点服务', async () => {
|
|
const wrapper = await mountIndex()
|
|
await wrapper.findAll('.guide-quick-action')[0].trigger('tap')
|
|
await flushPromises()
|
|
const arrivalPanel = wrapper.getComponent(ArrivalPanelStub)
|
|
|
|
arrivalPanel.vm.$emit('type-change', 'bus')
|
|
await flushPromises()
|
|
arrivalPanel.vm.$emit('type-change', 'metro')
|
|
await flushPromises()
|
|
arrivalPanel.vm.$emit('type-change', 'bus')
|
|
await flushPromises()
|
|
|
|
expect(outdoorMapMocks.searchOutdoorPlaces).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('缩小建筑外观切换到来馆时展示初始分类查询栏', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('semantic-exit-outdoor')
|
|
await flushPromises()
|
|
|
|
expect(map.props('mapType')).toBe('outdoor')
|
|
const arrivalPanel = wrapper.getComponent(ArrivalPanelStub)
|
|
expect(arrivalPanel.props('mode')).toBe('browse')
|
|
expect(arrivalPanel.props('activeType')).toBeNull()
|
|
expect(arrivalPanel.props('targets')).toEqual([])
|
|
expect(map.props('outdoorMarkers')).toEqual([expect.objectContaining({
|
|
id: 'museum-destination',
|
|
title: '深圳自然博物馆'
|
|
})])
|
|
})
|
|
|
|
it('来馆目的地确认后,实时定位成功即自动调用腾讯路线规划', async () => {
|
|
browserLocationMocks.getBrowserGcjLocation.mockResolvedValue({
|
|
latitude: 22.688,
|
|
longitude: 114.359,
|
|
accuracyMeters: 18,
|
|
timestamp: Date.now()
|
|
})
|
|
outdoorMapMocks.planOutdoorRoute.mockResolvedValue({
|
|
mode: 'TRANSIT',
|
|
distance: 1850,
|
|
duration: 840,
|
|
polyline: []
|
|
})
|
|
|
|
const wrapper = await mountIndex()
|
|
await wrapper.findAll('.guide-quick-action')[0].trigger('tap')
|
|
await flushPromises()
|
|
const arrivalPanel = wrapper.getComponent(ArrivalPanelStub)
|
|
const destination = {
|
|
id: 'bus-stop-museum',
|
|
type: 'bus',
|
|
title: '深圳自然博物馆[公交站]',
|
|
subtitle: 'M574路',
|
|
keyword: '深圳自然博物馆[公交站]',
|
|
region: '深圳市',
|
|
latitude: 22.69258,
|
|
longitude: 114.36372
|
|
}
|
|
|
|
arrivalPanel.vm.$emit('navigate', destination)
|
|
await flushPromises()
|
|
|
|
const routePanel = wrapper.getComponent(OutdoorArrivalRoutePanelStub)
|
|
expect(routePanel.props('phase')).toBe('route')
|
|
expect(routePanel.props('startPoint')).toMatchObject({ name: '我的位置' })
|
|
expect(outdoorMapMocks.planOutdoorRoute).toHaveBeenCalledWith(expect.objectContaining({
|
|
from: expect.objectContaining({ name: '我的位置' }),
|
|
to: expect.objectContaining({ id: 'bus-stop-museum' }),
|
|
mode: 'TRANSIT'
|
|
}))
|
|
})
|
|
|
|
it('定位不可用时保留重新检测、输入地点和地图选点入口', async () => {
|
|
browserLocationMocks.getBrowserGcjLocation.mockRejectedValue(new Error('未获得位置权限'))
|
|
const wrapper = await mountIndex()
|
|
await wrapper.findAll('.guide-quick-action')[0].trigger('tap')
|
|
await flushPromises()
|
|
const arrivalPanel = wrapper.getComponent(ArrivalPanelStub)
|
|
const destination = {
|
|
id: 'bus-stop-museum',
|
|
type: 'bus',
|
|
title: '深圳自然博物馆[公交站]',
|
|
subtitle: 'M574路',
|
|
keyword: '深圳自然博物馆[公交站]',
|
|
region: '深圳市',
|
|
latitude: 22.69258,
|
|
longitude: 114.36372
|
|
}
|
|
|
|
arrivalPanel.vm.$emit('navigate', destination)
|
|
await flushPromises()
|
|
|
|
const routePanel = wrapper.getComponent(OutdoorArrivalRoutePanelStub)
|
|
expect(routePanel.props('phase')).toBe('permission-needed')
|
|
|
|
browserLocationMocks.getBrowserLocationPermissionState.mockResolvedValue('denied')
|
|
routePanel.vm.$emit('retry-location')
|
|
await flushPromises()
|
|
expect(routePanel.props('locationHelpVisible')).toBe(true)
|
|
|
|
routePanel.vm.$emit('manual-start-request')
|
|
await flushPromises()
|
|
expect(routePanel.props('phase')).toBe('manual-search')
|
|
|
|
routePanel.vm.$emit('manual-map-request')
|
|
await flushPromises()
|
|
expect(routePanel.props('phase')).toBe('manual-map')
|
|
|
|
routePanel.vm.$emit('manual-start-confirm', {
|
|
title: '深圳北站',
|
|
latitude: 22.609,
|
|
longitude: 114.03
|
|
})
|
|
await flushPromises()
|
|
expect(routePanel.props('phase')).toBe('confirm-start')
|
|
expect(routePanel.props('startPoint')).toMatchObject({ name: '深圳北站' })
|
|
})
|
|
|
|
it('手动起点使用输入提示,地图选点会补充可读地址并标记当前位置', async () => {
|
|
browserLocationMocks.getBrowserGcjLocation.mockRejectedValue(new Error('未获得位置权限'))
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
await wrapper.findAll('.guide-quick-action')[0].trigger('tap')
|
|
await flushPromises()
|
|
const arrivalPanel = wrapper.getComponent(ArrivalPanelStub)
|
|
const destination = {
|
|
id: 'bus-stop-museum',
|
|
type: 'bus',
|
|
title: '深圳自然博物馆[公交站]',
|
|
subtitle: 'M574路',
|
|
keyword: '深圳自然博物馆[公交站]',
|
|
region: '深圳市',
|
|
latitude: 22.69258,
|
|
longitude: 114.36372
|
|
}
|
|
|
|
arrivalPanel.vm.$emit('navigate', destination)
|
|
await flushPromises()
|
|
const routePanel = wrapper.getComponent(OutdoorArrivalRoutePanelStub)
|
|
routePanel.vm.$emit('manual-start-request')
|
|
await flushPromises()
|
|
routePanel.vm.$emit('manual-start-search', '深圳北')
|
|
await flushPromises()
|
|
expect(outdoorMapMocks.suggestOutdoorPlaces).toHaveBeenCalledWith(expect.objectContaining({ keyword: '深圳北' }))
|
|
|
|
routePanel.vm.$emit('manual-map-request')
|
|
await flushPromises()
|
|
map.vm.$emit('map-tap', { latitude: 22.688, longitude: 114.359 })
|
|
await flushPromises()
|
|
|
|
expect(outdoorMapMocks.reverseGeocodeOutdoorLocation).toHaveBeenCalledWith({ latitude: 22.688, longitude: 114.359 })
|
|
expect(routePanel.props('phase')).toBe('confirm-start')
|
|
expect(routePanel.props('startPoint')).toMatchObject({ name: '深圳市坪山区龙坪路 1 号' })
|
|
expect(map.props('outdoorMarkers')).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ id: 'outdoor-route-start', markerKind: 'user' })
|
|
]))
|
|
})
|
|
|
|
it('切换手动起点方式后忽略迟到的输入提示结果', async () => {
|
|
browserLocationMocks.getBrowserGcjLocation.mockRejectedValue(new Error('未获得位置权限'))
|
|
let resolveSuggestion: ((value: { items: Array<Record<string, unknown>> }) => void) | null = null
|
|
outdoorMapMocks.suggestOutdoorPlaces.mockImplementationOnce(() => new Promise((resolve) => {
|
|
resolveSuggestion = resolve
|
|
}))
|
|
const wrapper = await mountIndex()
|
|
await wrapper.findAll('.guide-quick-action')[0].trigger('tap')
|
|
await flushPromises()
|
|
const arrivalPanel = wrapper.getComponent(ArrivalPanelStub)
|
|
arrivalPanel.vm.$emit('navigate', {
|
|
id: 'bus-stop-museum',
|
|
type: 'bus',
|
|
title: '深圳自然博物馆[公交站]',
|
|
subtitle: 'M574路',
|
|
keyword: '深圳自然博物馆[公交站]',
|
|
region: '深圳市',
|
|
latitude: 22.69258,
|
|
longitude: 114.36372
|
|
})
|
|
await flushPromises()
|
|
const routePanel = wrapper.getComponent(OutdoorArrivalRoutePanelStub)
|
|
routePanel.vm.$emit('manual-start-request')
|
|
await flushPromises()
|
|
routePanel.vm.$emit('manual-start-search', '深圳北')
|
|
await flushPromises()
|
|
routePanel.vm.$emit('manual-map-request')
|
|
resolveSuggestion?.({ items: [{ id: 'late-result', title: '迟到结果' }] })
|
|
await flushPromises()
|
|
|
|
expect(routePanel.props('phase')).toBe('manual-map')
|
|
expect(routePanel.props('manualSearchResults')).toEqual([])
|
|
})
|
|
|
|
it('向搜索面板传入当前楼层,并将分类结果 ID 精确传给地图', async () => {
|
|
const wrapper = await mountIndex()
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
expect(search.props('currentFloorId')).toBe('L1')
|
|
expect(search.props('currentFloorLabel')).toBe('1F')
|
|
expect(map.props('disableAutoExit')).toBe(false)
|
|
|
|
search.vm.$emit('category-mode-change', true)
|
|
search.vm.$emit('results-change', {
|
|
...categoryContext,
|
|
active: true
|
|
})
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(map.props('activeFloor')).toBe('L1')
|
|
expect(map.props('indoorView')).toBe('overview')
|
|
expect(map.props('visiblePoiIds')).toEqual(categoryContext.visiblePoiIds)
|
|
expect(map.props('disableAutoExit')).toBe(true)
|
|
expect(testState.switchFloorCalls).toEqual(['L1', 'L2'])
|
|
|
|
map.vm.$emit('floor-change', 'L2')
|
|
await wrapper.vm.$nextTick()
|
|
expect(map.props('activeFloor')).toBe('L2')
|
|
expect(map.props('indoorView')).toBe('floor')
|
|
|
|
search.vm.$emit('results-change', {
|
|
...categoryContext,
|
|
floorId: 'L1',
|
|
floorLabel: '1F',
|
|
visiblePoiIds: [],
|
|
active: true,
|
|
pending: true
|
|
})
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(map.props('activeFloor')).toBe('L2')
|
|
map.vm.$emit('floor-change', 'L1')
|
|
await wrapper.vm.$nextTick()
|
|
expect(map.props('activeFloor')).toBe('L1')
|
|
expect(map.props('visiblePoiIds')).toEqual([])
|
|
expect(map.props('disableAutoExit')).toBe(true)
|
|
|
|
search.vm.$emit('results-change', {
|
|
...categoryContext,
|
|
active: false,
|
|
visiblePoiIds: []
|
|
})
|
|
search.vm.$emit('category-mode-change', false)
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(map.props('disableAutoExit')).toBe(false)
|
|
})
|
|
|
|
it('楼层数据按高到低返回时,首次进入仍将 1F 作为自动缩放的默认目标', async () => {
|
|
const originalFloors = [...floors]
|
|
floors.splice(0, floors.length,
|
|
{ id: 'F5', label: 'F5', order: 5 },
|
|
{ id: 'F2', label: 'F2', order: 2 },
|
|
{ id: 'F1', label: 'F1', order: 1 }
|
|
)
|
|
|
|
try {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
expect(map.props('activeFloor')).toBe('F1')
|
|
expect(wrapper.getComponent(PoiSearchPanelStub).props('currentFloorId')).toBe('F1')
|
|
} finally {
|
|
floors.splice(0, floors.length, ...originalFloors)
|
|
}
|
|
})
|
|
|
|
it('分类结果在首页显示统一地点操作卡并定位目标', async () => {
|
|
const wrapper = await mountIndex()
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
search.vm.$emit('category-mode-change', true)
|
|
search.vm.$emit('results-change', {
|
|
...categoryContext,
|
|
active: true
|
|
})
|
|
search.vm.$emit('result-tap', poi, categoryContext)
|
|
await flushPromises()
|
|
|
|
expect(map.props('targetFocusRequest')).toMatchObject({ poiId: poi.id, floorId: 'L2' })
|
|
expect(wrapper.find('.poi-card-title').text()).toBe(poi.name)
|
|
expect(wrapper.findAll('.poi-action')).toHaveLength(1)
|
|
expect(wrapper.find('[data-action-id="navigate"]').text()).toContain('去这里')
|
|
expect(vi.mocked(uni.navigateTo)).not.toHaveBeenCalled()
|
|
expect(map.props('activeFloor')).toBe('L1')
|
|
map.vm.$emit('floor-change', 'L2')
|
|
await wrapper.vm.$nextTick()
|
|
expect(map.props('activeFloor')).toBe('L2')
|
|
expect(testState.searchMountCount).toBe(1)
|
|
expect(testState.searchUnmountCount).toBe(0)
|
|
})
|
|
|
|
it('全屏搜索结果同样在首页显示地点操作卡', async () => {
|
|
const wrapper = await mountIndex()
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
const fullSearchContext = {
|
|
...categoryContext,
|
|
categoryId: '' as const,
|
|
keyword: '二层卫生间'
|
|
}
|
|
|
|
search.vm.$emit('result-tap', poi, fullSearchContext)
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.poi-card-title').text()).toBe(poi.name)
|
|
expect(wrapper.find('[data-action-id="navigate"]').exists()).toBe(true)
|
|
expect(vi.mocked(uni.navigateTo)).not.toHaveBeenCalled()
|
|
expect(map.props('visiblePoiIds')).toBeNull()
|
|
expect(map.props('targetFocusRequest')).toMatchObject({ poiId: poi.id })
|
|
expect(map.props('activeFloor')).toBe('L1')
|
|
map.vm.$emit('floor-change', 'L2')
|
|
await wrapper.vm.$nextTick()
|
|
expect(map.props('activeFloor')).toBe('L2')
|
|
})
|
|
|
|
it('搜索展开期间若渲染器提交外观,立即重新请求当前楼层并等待提交', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
|
|
map.vm.$emit('floor-change', 'L1')
|
|
search.vm.$emit('expanded-change', true)
|
|
await flushPromises()
|
|
testState.switchFloorCalls = []
|
|
|
|
map.vm.$emit('auto-switch', {
|
|
from: 'floor',
|
|
to: 'overview',
|
|
trigger: 'zoom-out',
|
|
distance: 320
|
|
})
|
|
await flushPromises()
|
|
|
|
expect(map.props('indoorView')).toBe('overview')
|
|
expect(map.props('disableAutoExit')).toBe(true)
|
|
expect(testState.switchFloorCalls).toEqual(['L1'])
|
|
|
|
map.vm.$emit('floor-change', 'L1')
|
|
await wrapper.vm.$nextTick()
|
|
expect(map.props('indoorView')).toBe('floor')
|
|
})
|
|
|
|
it('页面重新显示时要求搜索面板恢复原列表位置', async () => {
|
|
await mountIndex()
|
|
await testState.onShowHandler?.()
|
|
await flushPromises()
|
|
|
|
expect(testState.searchRestoreCount).toBe(1)
|
|
})
|
|
|
|
it('搜索结果不直接跳转详情页,并保持当前搜索状态', async () => {
|
|
const wrapper = await mountIndex()
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
search.vm.$emit('category-mode-change', true)
|
|
search.vm.$emit('results-change', { ...categoryContext, active: true })
|
|
search.vm.$emit('result-tap', poi, categoryContext)
|
|
await flushPromises()
|
|
|
|
expect(vi.mocked(uni.navigateTo)).not.toHaveBeenCalled()
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(true)
|
|
expect(map.props('visiblePoiIds')).toEqual(categoryContext.visiblePoiIds)
|
|
expect(map.props('targetFocusRequest')).toMatchObject({ poiId: poi.id })
|
|
})
|
|
|
|
it('模型点选展厅后查看详情,返回时恢复该楼层视觉基线且不重挂载地图', async () => {
|
|
testState.halls = [{ id: 'hall-l2', poiId: poi.id, name: '二层展厅' }]
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
kind: 'hall',
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
})
|
|
await flushPromises()
|
|
|
|
const viewHallAction = wrapper.find('[data-action-id="detail"]')
|
|
expect(viewHallAction.exists()).toBe(true)
|
|
expect(viewHallAction.text()).toContain('讲解')
|
|
await viewHallAction.trigger('tap')
|
|
await flushPromises()
|
|
|
|
expect(vi.mocked(uni.navigateTo).mock.calls[0]?.[0]?.url).toContain('/pages/explain/guide-stop-list?hallId=hall-l2')
|
|
await testState.onShowHandler?.()
|
|
await flushPromises()
|
|
expect(testState.resetToViewBaselineCalls).toHaveLength(0)
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(true)
|
|
|
|
confirmLatestNavigation()
|
|
await testState.onShowHandler?.()
|
|
await flushPromises()
|
|
|
|
expect(testState.resetToViewBaselineCalls).toEqual([{
|
|
view: 'floor',
|
|
floorId: 'L2',
|
|
reason: 'poi-detail-close'
|
|
}])
|
|
expect(map.props('activeFloor')).toBe('L2')
|
|
expect(map.props('visiblePoiIds')).toBeNull()
|
|
expect(map.props('targetFocusRequest')).toBeNull()
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(false)
|
|
expect(testState.mapMountCount).toBe(1)
|
|
expect(testState.mapUnmountCount).toBe(0)
|
|
|
|
await testState.onShowHandler?.()
|
|
await flushPromises()
|
|
expect(testState.resetToViewBaselineCalls).toHaveLength(1)
|
|
})
|
|
|
|
it('模型点选展厅显示去这里和详情,并在详情返回时复位原楼层', async () => {
|
|
testState.halls = [{ id: 'hall-l2', poiId: poi.id, name: '二层展厅' }]
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
kind: 'hall',
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
})
|
|
await flushPromises()
|
|
|
|
const actions = wrapper.findAll('.poi-action')
|
|
expect(actions).toHaveLength(2)
|
|
expect(wrapper.text()).not.toContain('相关讲解')
|
|
await wrapper.find('[data-action-id="detail"]').trigger('tap')
|
|
await flushPromises()
|
|
|
|
expect(vi.mocked(uni.navigateTo).mock.calls[0]?.[0]?.url).toContain('/pages/explain/guide-stop-list?hallId=hall-l2')
|
|
confirmLatestNavigation()
|
|
await testState.onShowHandler?.()
|
|
await flushPromises()
|
|
|
|
expect(testState.resetToViewBaselineCalls).toEqual([{
|
|
view: 'floor',
|
|
floorId: 'L2',
|
|
reason: 'poi-detail-close'
|
|
}])
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(false)
|
|
})
|
|
|
|
it('同一个展厅从地图和搜索进入时使用相同详情路由和 hallId', async () => {
|
|
const hallPoi = {
|
|
...poi,
|
|
kind: 'hall' as const,
|
|
hallId: 'hall-l2',
|
|
primaryCategory: { id: 'exhibition_hall', label: '展厅' }
|
|
}
|
|
testState.halls = [{ id: 'hall-l2', poiId: poi.id, name: '二层展厅' }]
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...hallPoi,
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
})
|
|
await flushPromises()
|
|
await wrapper.find('[data-action-id="detail"]').trigger('tap')
|
|
await flushPromises()
|
|
const mapUrl = vi.mocked(uni.navigateTo).mock.calls.at(-1)?.[0]?.url as string
|
|
vi.mocked(uni.navigateTo).mock.calls.at(-1)?.[0]?.fail?.({ errMsg: 'navigateTo:fail test cleanup' })
|
|
|
|
search.vm.$emit('result-tap', hallPoi, categoryContext)
|
|
await flushPromises()
|
|
await wrapper.find('[data-action-id="detail"]').trigger('tap')
|
|
await flushPromises()
|
|
const searchUrl = vi.mocked(uni.navigateTo).mock.calls.at(-1)?.[0]?.url as string
|
|
const mapRoute = mapUrl.split('?')[0]
|
|
const searchRoute = searchUrl.split('?')[0]
|
|
const mapQuery = new URLSearchParams(mapUrl.split('?')[1])
|
|
const searchQuery = new URLSearchParams(searchUrl.split('?')[1])
|
|
|
|
expect(mapRoute).toBe('/pages/explain/guide-stop-list')
|
|
expect(searchRoute).toBe(mapRoute)
|
|
expect(mapQuery.get('hallId')).toBe('hall-l2')
|
|
expect(searchQuery.get('hallId')).toBe(mapQuery.get('hallId'))
|
|
expect(searchUrl).toBe(mapUrl)
|
|
})
|
|
|
|
it('普通设施从地图和搜索进入时只提供去这里', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
primaryCategory: 'restroom',
|
|
primaryCategoryZh: '卫生间'
|
|
})
|
|
await flushPromises()
|
|
expect(wrapper.findAll('.poi-action')).toHaveLength(1)
|
|
expect(wrapper.find('[data-action-id="detail"]').exists()).toBe(false)
|
|
await wrapper.find('[data-action-id="navigate"]').trigger('tap')
|
|
await flushPromises()
|
|
expect(vi.mocked(uni.navigateTo)).not.toHaveBeenCalled()
|
|
|
|
search.vm.$emit('result-tap', poi, categoryContext)
|
|
await flushPromises()
|
|
|
|
expect(wrapper.findAll('.poi-action')).toHaveLength(1)
|
|
expect(wrapper.find('[data-action-id="detail"]').exists()).toBe(false)
|
|
expect(wrapper.find('[data-action-id="navigate"]').exists()).toBe(true)
|
|
expect(vi.mocked(uni.navigateTo)).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('去这里后仅接受已接入路网的地图点位作为起点,并要求确认', async () => {
|
|
const crossFloorStart = {
|
|
...poi,
|
|
id: 'poi-l1-start',
|
|
name: '一层服务台',
|
|
floorId: 'L1',
|
|
floorLabel: '1F'
|
|
}
|
|
testState.routeTargets = [{
|
|
poiId: poi.id,
|
|
name: poi.name,
|
|
floorId: poi.floorId,
|
|
floorLabel: poi.floorLabel,
|
|
categoryLabel: poi.primaryCategory.label,
|
|
routeNodeId: 'route-node-restroom-l2'
|
|
}, {
|
|
routeTargetId: 'anchor-service-l1',
|
|
poiId: 'route-node-service-l1',
|
|
sourceId: crossFloorStart.id,
|
|
name: crossFloorStart.name,
|
|
floorId: crossFloorStart.floorId,
|
|
floorLabel: crossFloorStart.floorLabel,
|
|
categoryLabel: crossFloorStart.primaryCategory.label,
|
|
routeNodeId: 'route-node-service-l1'
|
|
}]
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
primaryCategory: 'restroom',
|
|
primaryCategoryZh: '卫生间'
|
|
})
|
|
await flushPromises()
|
|
await wrapper.find('[data-action-id="navigate"]').trigger('tap')
|
|
await flushPromises()
|
|
|
|
const planner = wrapper.getComponent(RoutePlannerPanelStub)
|
|
expect(planner.props('endPoint')).toMatchObject({ poiId: poi.id })
|
|
expect(map.props('routeStartSelectionActive')).toBe(true)
|
|
expect(map.props('routeSelectablePoiIds')).toEqual([poi.id, 'route-node-service-l1'])
|
|
expect(map.props('showFloor')).toBe(true)
|
|
expect(map.props('floorBottom')).toBe('193px')
|
|
|
|
map.vm.$emit('floor-change', crossFloorStart.floorId)
|
|
map.vm.$emit('route-start-candidate', {
|
|
...crossFloorStart,
|
|
primaryCategory: 'restroom',
|
|
primaryCategoryZh: '卫生间'
|
|
})
|
|
await flushPromises()
|
|
|
|
expect(planner.props('startCandidate')).toMatchObject({
|
|
routeTargetId: 'anchor-service-l1',
|
|
poiId: 'route-node-service-l1',
|
|
floorId: 'L1'
|
|
})
|
|
expect(map.props('routeStartSelectionActive')).toBe(false)
|
|
planner.vm.$emit('cancel-start')
|
|
await flushPromises()
|
|
expect(planner.props('startCandidate')).toBeNull()
|
|
expect(map.props('routeStartSelectionActive')).toBe(true)
|
|
|
|
// A POI click delivered after a floor switch still belongs to the active
|
|
// route-start session and must not reopen its normal "go here" card.
|
|
map.vm.$emit('poi-click', crossFloorStart)
|
|
await flushPromises()
|
|
expect(planner.props('startCandidate')).toMatchObject({
|
|
routeTargetId: 'anchor-service-l1',
|
|
poiId: 'route-node-service-l1'
|
|
})
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(false)
|
|
|
|
planner.vm.$emit('cancel-start')
|
|
await flushPromises()
|
|
|
|
// A space can be visible and selectable without being listed as a formal
|
|
// navigable place. Its coordinate must survive the delayed poi-click path
|
|
// so the backend can snap it to the nearest graph node.
|
|
const office = {
|
|
...crossFloorStart,
|
|
id: 'space-office-5',
|
|
name: '办公室',
|
|
floorId: 'L5',
|
|
floorLabel: '5F',
|
|
spaceId: '354513947405226300',
|
|
positionGltf: [-119.394396, 32.4643, 39.492556] as [number, number, number]
|
|
}
|
|
map.vm.$emit('floor-change', office.floorId)
|
|
map.vm.$emit('poi-click', office)
|
|
await flushPromises()
|
|
|
|
expect(planner.props('startCandidate')).toMatchObject({
|
|
poiId: office.id,
|
|
sourceId: office.spaceId,
|
|
floorId: office.floorId,
|
|
positionGltf: office.positionGltf,
|
|
coordinateFallback: true
|
|
})
|
|
})
|
|
|
|
it('取消馆内导览后回到发起导览时的当前楼层和地点卡片', async () => {
|
|
testState.routeTargets = [{
|
|
poiId: poi.id,
|
|
name: poi.name,
|
|
floorId: poi.floorId,
|
|
floorLabel: poi.floorLabel,
|
|
categoryLabel: poi.primaryCategory.label,
|
|
routeNodeId: 'route-node-restroom-l2'
|
|
}]
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
primaryCategory: 'restroom',
|
|
primaryCategoryZh: '卫生间'
|
|
})
|
|
await flushPromises()
|
|
await wrapper.find('[data-action-id="navigate"]').trigger('tap')
|
|
await flushPromises()
|
|
|
|
wrapper.getComponent(RoutePlannerPanelStub).vm.$emit('back')
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(true)
|
|
expect(wrapper.text()).toContain(poi.name)
|
|
expect(map.props('activeFloor')).toBe('L2')
|
|
expect(testState.resetToViewBaselineCalls.at(-1)).toMatchObject({
|
|
floorId: 'L2',
|
|
reason: 'floor-reset'
|
|
})
|
|
})
|
|
|
|
it('模拟导览完成后回到发起导览时的当前楼层和地点卡片', async () => {
|
|
const startPoint = {
|
|
poiId: 'poi-l1-service',
|
|
name: '一层服务台',
|
|
floorId: 'L1',
|
|
floorLabel: '1F',
|
|
categoryLabel: '服务设施',
|
|
routeNodeId: 'route-node-service-l1',
|
|
positionGltf: [1, 0, 1] as [number, number, number]
|
|
}
|
|
testState.routeTargets = [{
|
|
poiId: poi.id,
|
|
name: poi.name,
|
|
floorId: poi.floorId,
|
|
floorLabel: poi.floorLabel,
|
|
categoryLabel: poi.primaryCategory.label,
|
|
routeNodeId: 'route-node-restroom-l2'
|
|
}, startPoint]
|
|
testState.routePlanResult = {
|
|
route: {
|
|
id: 'route-l1-l2',
|
|
start: {
|
|
poiId: startPoint.poiId,
|
|
name: startPoint.name,
|
|
floorId: startPoint.floorId,
|
|
floorLabel: startPoint.floorLabel,
|
|
routeNodeId: startPoint.routeNodeId,
|
|
position: [1, 0, 1]
|
|
},
|
|
end: {
|
|
poiId: poi.id,
|
|
name: poi.name,
|
|
floorId: poi.floorId,
|
|
floorLabel: poi.floorLabel,
|
|
routeNodeId: 'route-node-restroom-l2',
|
|
position: [12, 3, 24]
|
|
},
|
|
distanceMeters: 38,
|
|
nodeIds: ['route-node-service-l1', 'route-node-restroom-l2'],
|
|
points: [],
|
|
floorSegments: [{
|
|
floorId: 'L1',
|
|
floorLabel: '1F',
|
|
points: [{ nodeId: 'route-node-service-l1', floorId: 'L1', position: [1, 0, 1] }]
|
|
}],
|
|
connectorPoints: []
|
|
} satisfies GuideRouteResult,
|
|
error: ''
|
|
}
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
primaryCategory: 'restroom',
|
|
primaryCategoryZh: '卫生间'
|
|
})
|
|
await flushPromises()
|
|
await wrapper.find('[data-action-id="navigate"]').trigger('tap')
|
|
await flushPromises()
|
|
|
|
const planner = wrapper.getComponent(RoutePlannerPanelStub)
|
|
map.vm.$emit('route-start-candidate', startPoint)
|
|
await flushPromises()
|
|
planner.vm.$emit('confirm-start', startPoint)
|
|
await flushPromises()
|
|
expect(testState.routePlanRequests.at(-1)).toMatchObject({
|
|
startPoiId: startPoint.poiId,
|
|
startTarget: {
|
|
poiId: startPoint.poiId,
|
|
floorId: startPoint.floorId,
|
|
positionGltf: startPoint.positionGltf,
|
|
routeNodeId: startPoint.routeNodeId
|
|
}
|
|
})
|
|
planner.vm.$emit('simulate-guide')
|
|
await flushPromises()
|
|
map.vm.$emit('route-roaming-progress', { remainingMeters: 0, progress: 1 })
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(true)
|
|
expect(wrapper.text()).toContain(poi.name)
|
|
expect(map.props('activeFloor')).toBe('L2')
|
|
})
|
|
|
|
it('非展厅空间没有关联讲解时只保留去这里', async () => {
|
|
testState.halls = [{ id: 'hall-biology', poiId: 'another-poi', name: '生物厅' }]
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
id: 'space-l2-temporary',
|
|
name: '生物厅临展空间',
|
|
kind: 'space',
|
|
hallId: 'space-l2-temporary',
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
})
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(true)
|
|
expect(wrapper.find('.poi-card-actions').exists()).toBe(true)
|
|
expect(wrapper.findAll('.poi-action')).toHaveLength(1)
|
|
expect(wrapper.find('[data-action-id="detail"]').exists()).toBe(false)
|
|
await wrapper.find('[data-action-id="navigate"]').trigger('tap')
|
|
expect(vi.mocked(uni.navigateTo)).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('没有进入讲解内容库的展厅语义点位也只保留去这里', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
id: 'hall-l1-dynamic-cinema',
|
|
name: '动感多维影院',
|
|
kind: 'hall',
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
})
|
|
await flushPromises()
|
|
|
|
expect(wrapper.findAll('.poi-action')).toHaveLength(1)
|
|
expect(wrapper.find('[data-action-id="navigate"]').exists()).toBe(true)
|
|
expect(wrapper.find('[data-action-id="detail"]').exists()).toBe(false)
|
|
})
|
|
|
|
it('快速切换点位时忽略上一个点位较晚返回的展厅关联结果', async () => {
|
|
let resolveFirst!: (halls: typeof testState.halls) => void
|
|
let resolveSecond!: (halls: typeof testState.halls) => void
|
|
testState.hallResolutionPromises = [
|
|
new Promise((resolve) => { resolveFirst = resolve }),
|
|
new Promise((resolve) => { resolveSecond = resolve })
|
|
]
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
const firstPoi = {
|
|
...poi,
|
|
id: 'poi-hall-first',
|
|
kind: 'hall' as const,
|
|
hallId: 'hall-first',
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
}
|
|
const secondPoi = {
|
|
...poi,
|
|
id: 'poi-hall-second',
|
|
kind: 'hall' as const,
|
|
hallId: 'hall-second',
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
}
|
|
|
|
map.vm.$emit('poi-click', firstPoi)
|
|
map.vm.$emit('poi-click', secondPoi)
|
|
resolveSecond([{ id: 'hall-second', poiId: secondPoi.id, name: '第二展厅' }])
|
|
await flushPromises()
|
|
resolveFirst([{ id: 'hall-first', poiId: firstPoi.id, name: '第一展厅' }])
|
|
await flushPromises()
|
|
|
|
expect(wrapper.text()).toContain(secondPoi.name)
|
|
await wrapper.find('[data-action-id="detail"]').trigger('tap')
|
|
await flushPromises()
|
|
const url = vi.mocked(uni.navigateTo).mock.calls[0]?.[0]?.url as string
|
|
expect(new URLSearchParams(url.split('?')[1]).get('hallId')).toBe('hall-second')
|
|
})
|
|
|
|
it('展厅编号名称规范化后仍能关联真实讲解展厅', async () => {
|
|
testState.halls = [{ id: 'hall-biology', name: '生物厅' }]
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
id: 'hall-space-biology',
|
|
name: '展厅_6生物厅',
|
|
kind: 'space',
|
|
hallId: 'space-biology',
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
})
|
|
await flushPromises()
|
|
|
|
const actions = wrapper.findAll('.poi-action')
|
|
expect(actions).toHaveLength(2)
|
|
await wrapper.find('[data-action-id="detail"]').trigger('tap')
|
|
await flushPromises()
|
|
expect(vi.mocked(uni.navigateTo).mock.calls[0]?.[0]?.url).toContain(
|
|
'/pages/explain/guide-stop-list?hallId=hall-biology'
|
|
)
|
|
})
|
|
|
|
it('模型点选展厅的详情入口进入讲解对象列表并保留一次性返回上下文', async () => {
|
|
testState.halls = [{ id: 'hall-l2', poiId: poi.id, name: '二层展厅' }]
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
kind: 'hall',
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
})
|
|
await flushPromises()
|
|
await wrapper.find('[data-action-id="detail"]').trigger('tap')
|
|
await flushPromises()
|
|
|
|
expect(vi.mocked(uni.navigateTo).mock.calls[0]?.[0]?.url).toContain('/pages/explain/guide-stop-list?hallId=hall-l2')
|
|
confirmLatestNavigation()
|
|
await testState.onShowHandler?.()
|
|
await flushPromises()
|
|
expect(testState.resetToViewBaselineCalls[0]).toMatchObject({ view: 'floor', floorId: 'L2' })
|
|
})
|
|
|
|
it('模型点选详情打开失败时取消返回上下文,不触发楼层复位', async () => {
|
|
testState.halls = [{ id: 'hall-l2', poiId: poi.id, name: '二层展厅' }]
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
kind: 'hall',
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
})
|
|
await flushPromises()
|
|
await wrapper.find('[data-action-id="detail"]').trigger('tap')
|
|
await flushPromises()
|
|
|
|
const navigation = vi.mocked(uni.navigateTo).mock.calls[0]?.[0]
|
|
navigation?.fail?.({ errMsg: 'navigateTo:fail simulated' })
|
|
await testState.onShowHandler?.()
|
|
await flushPromises()
|
|
|
|
expect(testState.resetToViewBaselineCalls).toHaveLength(0)
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(true)
|
|
expect(map.props('activeFloor')).toBe('L2')
|
|
})
|
|
|
|
it('地图点位卡隐藏首页 dock 时仍保留搜索组件,关闭后可继续使用原列表', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
id: poi.id,
|
|
name: poi.name,
|
|
floorId: poi.floorId,
|
|
primaryCategory: 'restroom',
|
|
primaryCategoryZh: '卫生间',
|
|
iconType: 'restroom',
|
|
positionGltf: poi.positionGltf
|
|
})
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.get('[data-testid="poi-search"]').exists()).toBe(true)
|
|
expect(testState.searchUnmountCount).toBe(0)
|
|
|
|
map.vm.$emit('selection-clear')
|
|
await wrapper.vm.$nextTick()
|
|
expect(wrapper.getComponent(PoiSearchPanelStub).exists()).toBe(true)
|
|
expect(testState.searchMountCount).toBe(1)
|
|
expect(testState.resetToViewBaselineCalls).toHaveLength(0)
|
|
})
|
|
|
|
it('直接点选普通设施后显示统一名称和楼层卡片,关闭后恢复该楼层视觉基线且不重挂载地图', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
primaryCategory: 'restroom',
|
|
primaryCategoryZh: '卫生间'
|
|
})
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.find('.poi-card-collapsed').exists()).toBe(false)
|
|
expect(wrapper.find('.poi-card-title').text()).toBe(poi.name)
|
|
expect(wrapper.find('.poi-card-subtitle').text()).toBe('2F')
|
|
await wrapper.find('.poi-card-close').trigger('tap')
|
|
await flushPromises()
|
|
|
|
expect(testState.resetToViewBaselineCalls).toEqual([{
|
|
view: 'floor',
|
|
floorId: poi.floorId,
|
|
reason: 'floor-reset'
|
|
}])
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(false)
|
|
expect(map.props('visiblePoiIds')).toBeNull()
|
|
expect(map.props('targetFocusRequest')).toBeNull()
|
|
expect(testState.mapMountCount).toBe(1)
|
|
expect(testState.mapUnmountCount).toBe(0)
|
|
})
|
|
|
|
it('直接点选展厅后关闭卡片,复用楼层基线复位而非详情返回语义', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', {
|
|
...poi,
|
|
kind: 'hall',
|
|
primaryCategory: 'exhibition_hall',
|
|
primaryCategoryZh: '展厅'
|
|
})
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.find('.poi-card-close').exists()).toBe(true)
|
|
await wrapper.find('.poi-card-close').trigger('tap')
|
|
await flushPromises()
|
|
|
|
expect(testState.resetToViewBaselineCalls).toEqual([{
|
|
view: 'floor',
|
|
floorId: poi.floorId,
|
|
reason: 'floor-reset'
|
|
}])
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(false)
|
|
expect(testState.mapMountCount).toBe(1)
|
|
expect(testState.mapUnmountCount).toBe(0)
|
|
})
|
|
|
|
it('renderer 清选和返回建筑外观不会触发楼层复位', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
map.vm.$emit('poi-click', { ...poi, primaryCategory: 'restroom' })
|
|
map.vm.$emit('selection-clear')
|
|
map.vm.$emit('indoor-view-change', 'overview')
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(testState.resetToViewBaselineCalls).toHaveLength(0)
|
|
})
|
|
|
|
it('导览地图不渲染任何遮挡模型的临时提示条', async () => {
|
|
const wrapper = await mountIndex()
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
|
|
expect(wrapper.find('.indoor-gesture-hint').exists()).toBe(false)
|
|
map.vm.$emit('auto-switch', {
|
|
from: 'floor',
|
|
to: 'overview',
|
|
trigger: 'zoom-out',
|
|
distance: 120
|
|
})
|
|
await wrapper.vm.$nextTick()
|
|
|
|
expect(wrapper.find('.indoor-gesture-hint').exists()).toBe(false)
|
|
})
|
|
|
|
it('单结果分类显示地点操作卡,取消后清除定位目标', async () => {
|
|
const wrapper = await mountIndex()
|
|
const search = wrapper.getComponent(PoiSearchPanelStub)
|
|
const map = wrapper.getComponent(GuideMapShellStub)
|
|
const singleContext = {
|
|
...categoryContext,
|
|
floorResultCount: 1,
|
|
visiblePoiIds: [poi.id]
|
|
}
|
|
|
|
search.vm.$emit('category-mode-change', true)
|
|
search.vm.$emit('results-change', { ...singleContext, active: true })
|
|
search.vm.$emit('result-tap', poi, singleContext)
|
|
await flushPromises()
|
|
|
|
expect(wrapper.find('.guide-poi-card').exists()).toBe(true)
|
|
expect(map.props('targetFocusRequest')).toMatchObject({ poiId: poi.id })
|
|
expect(vi.mocked(uni.navigateTo)).not.toHaveBeenCalled()
|
|
|
|
search.vm.$emit('cancel')
|
|
await wrapper.vm.$nextTick()
|
|
expect(map.props('visiblePoiIds')).toBeNull()
|
|
expect(map.props('targetFocusRequest')).toBeNull()
|
|
})
|
|
})
|