This commit is contained in:
87
tests/unit/ArrivalPanel.spec.ts
Normal file
87
tests/unit/ArrivalPanel.spec.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import ArrivalPanel from '@/components/navigation/ArrivalPanel.vue'
|
||||
import { ARRIVAL_TARGETS } from '@/data/arrivalSearchTargets'
|
||||
|
||||
const selectedTarget = ARRIVAL_TARGETS[0]
|
||||
const defaultUserAgent = window.navigator.userAgent
|
||||
|
||||
const mountArrivalPanel = () => mount(ArrivalPanel, {
|
||||
props: {
|
||||
visible: true,
|
||||
activeType: 'bus',
|
||||
targets: [selectedTarget],
|
||||
selectedTargetId: selectedTarget.id,
|
||||
selectedTarget
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
window.history.replaceState({}, '', '/#/pages/index/index?tab=guide')
|
||||
Object.defineProperty(window.navigator, 'userAgent', {
|
||||
configurable: true,
|
||||
value: 'Mozilla/5.0 MicroMessenger/8.0.50 miniProgram'
|
||||
})
|
||||
Object.assign(window, {
|
||||
__wxjs_environment: 'miniprogram',
|
||||
wx: {
|
||||
miniProgram: {
|
||||
navigateTo: vi.fn()
|
||||
}
|
||||
}
|
||||
})
|
||||
vi.stubGlobal('uni', {
|
||||
showToast: vi.fn()
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(window.navigator, 'userAgent', {
|
||||
configurable: true,
|
||||
value: defaultUserAgent
|
||||
})
|
||||
delete (window as Window & { __wxjs_environment?: string }).__wxjs_environment
|
||||
delete (window as Window & { wx?: unknown }).wx
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('来馆面板 H5 实际渲染', () => {
|
||||
it('主按钮显示第三方导航,点击后展示三家地图选择层', async () => {
|
||||
const wrapper = mountArrivalPanel()
|
||||
const navigateTo = (
|
||||
window as Window & {
|
||||
wx?: { miniProgram?: { navigateTo?: ReturnType<typeof vi.fn> } }
|
||||
}
|
||||
).wx?.miniProgram?.navigateTo
|
||||
|
||||
expect(wrapper.get('.arrival-primary-text').text()).toBe('第三方导航')
|
||||
expect(wrapper.text()).not.toContain('打开地图导航')
|
||||
await wrapper.get('.arrival-primary').trigger('tap')
|
||||
|
||||
expect(wrapper.find('.provider-sheet').exists()).toBe(true)
|
||||
expect(wrapper.findAll('.provider-row')).toHaveLength(3)
|
||||
expect(wrapper.text()).toContain('高德地图')
|
||||
expect(wrapper.text()).toContain('百度地图')
|
||||
expect(wrapper.text()).toContain('腾讯地图')
|
||||
expect(navigateTo).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('诊断信息默认隐藏,仅在 debug-build=1 时显示 H5 构建边界', () => {
|
||||
const normalWrapper = mountArrivalPanel()
|
||||
expect(normalWrapper.find('[data-testid="arrival-build-diagnostics"]').exists()).toBe(false)
|
||||
normalWrapper.unmount()
|
||||
|
||||
window.location.hash = '#/pages/index/index?tab=guide&debug-build=1'
|
||||
const debugWrapper = mountArrivalPanel()
|
||||
const diagnostics = debugWrapper.get('[data-testid="arrival-build-diagnostics"]')
|
||||
|
||||
expect(diagnostics.text()).toContain('构建 ID:test-build')
|
||||
expect(diagnostics.text()).toContain('Git commit:test-commit')
|
||||
expect(diagnostics.text()).toContain('构建平台:h5')
|
||||
expect(diagnostics.text()).toContain('navigationMode:third-party-providers')
|
||||
expect(diagnostics.text()).toContain('location.href:')
|
||||
expect(diagnostics.text()).toContain('UA:')
|
||||
})
|
||||
})
|
||||
@@ -1,230 +1,59 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@/utils/hostEnvironment', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@/utils/hostEnvironment')>()
|
||||
return {
|
||||
...actual,
|
||||
isNativeWechatMiniProgram: () => false
|
||||
}
|
||||
})
|
||||
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
buildThirdPartyMapSearchUri,
|
||||
openWechatMiniProgramLocation
|
||||
openThirdPartyMapSearch
|
||||
} from '@/services/ThirdPartyMapSearchService'
|
||||
|
||||
const target = {
|
||||
latitude: 22.604321,
|
||||
longitude: 114.058765,
|
||||
name: '深圳自然博物馆',
|
||||
address: '深圳市龙华区民治街道'
|
||||
}
|
||||
|
||||
const createWindow = (overrides: Record<string, unknown> = {}) => ({
|
||||
location: { search: '', hash: '' },
|
||||
navigator: { userAgent: 'Mozilla/5.0 MicroMessenger/8.0.50' },
|
||||
...overrides
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers()
|
||||
vi.stubGlobal('document', undefined)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers()
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('微信小程序 web-view 地图桥接', () => {
|
||||
it('桥接延迟注入时快速调用两次也只跳转一次', async () => {
|
||||
const navigateTo = vi.fn((options: { success?: () => void }) => options.success?.())
|
||||
const testWindow = createWindow()
|
||||
vi.stubGlobal('window', testWindow)
|
||||
vi.stubGlobal('uni', { showToast: vi.fn() })
|
||||
|
||||
setTimeout(() => {
|
||||
Object.assign(testWindow, {
|
||||
__wxjs_environment: 'miniprogram',
|
||||
wx: { miniProgram: { navigateTo } }
|
||||
})
|
||||
}, 100)
|
||||
|
||||
const first = openWechatMiniProgramLocation(target)
|
||||
const second = openWechatMiniProgramLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(150)
|
||||
|
||||
await expect(first).resolves.toEqual({ status: 'opened' })
|
||||
await expect(second).resolves.toEqual({ status: 'opened' })
|
||||
expect(navigateTo).toHaveBeenCalledTimes(1)
|
||||
|
||||
const url = navigateTo.mock.calls[0]?.[0].url
|
||||
const params = new URLSearchParams(url.split('?')[1])
|
||||
expect(Object.fromEntries(params)).toEqual({
|
||||
latitude: String(target.latitude),
|
||||
longitude: String(target.longitude),
|
||||
name: target.name,
|
||||
address: target.address
|
||||
describe('H5 第三方地图服务', () => {
|
||||
it('安全编码中文、空格和特殊字符', () => {
|
||||
const uri = buildThirdPartyMapSearchUri('amap', {
|
||||
keyword: '深圳自然博物馆 A&B',
|
||||
region: '深圳 市'
|
||||
})
|
||||
|
||||
expect(uri).toContain('keyword=%E6%B7%B1%E5%9C%B3%E8%87%AA%E7%84%B6%E5%8D%9A%E7%89%A9%E9%A6%86%20A%26B')
|
||||
expect(uri).toContain('city=%E6%B7%B1%E5%9C%B3%20%E5%B8%82')
|
||||
})
|
||||
|
||||
it('普通浏览器返回 H5 分支并保留第三方地图 URI', async () => {
|
||||
const showToast = vi.fn()
|
||||
vi.stubGlobal('window', createWindow({
|
||||
navigator: { userAgent: 'Mozilla/5.0 Chrome/126.0' }
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
await expect(openWechatMiniProgramLocation(target)).resolves.toEqual({ status: 'not-embedded' })
|
||||
expect(showToast).not.toHaveBeenCalled()
|
||||
expect(buildThirdPartyMapSearchUri('amap', {
|
||||
it.each([
|
||||
['amap', 'https://uri.amap.com/search?'],
|
||||
['baidu', 'https://api.map.baidu.com/place/search?'],
|
||||
['tencent', 'https://apis.map.qq.com/uri/v1/search?']
|
||||
] as const)('%s 使用对应的地图网页入口', (provider, expectedPrefix) => {
|
||||
expect(buildThirdPartyMapSearchUri(provider, {
|
||||
keyword: '深圳自然博物馆',
|
||||
region: '深圳市'
|
||||
})).toContain('https://uri.amap.com/search?')
|
||||
})).toMatch(expectedPrefix)
|
||||
})
|
||||
|
||||
it('显式宿主环境缺少桥接时显示错误提示', async () => {
|
||||
it('浏览器中直接跳转到选中的第三方地图', () => {
|
||||
const location = { href: 'https://guide.example.com/' }
|
||||
vi.stubGlobal('window', { location })
|
||||
|
||||
openThirdPartyMapSearch('tencent', {
|
||||
keyword: '深圳自然博物馆',
|
||||
region: '深圳市'
|
||||
})
|
||||
|
||||
expect(location.href).toContain('https://apis.map.qq.com/uri/v1/search?')
|
||||
})
|
||||
|
||||
it('缺少浏览器环境时提供可见提示', () => {
|
||||
const showToast = vi.fn()
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' }
|
||||
}))
|
||||
vi.stubGlobal('window', undefined)
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
const result = openWechatMiniProgramLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(2600)
|
||||
openThirdPartyMapSearch('baidu', {
|
||||
keyword: '深圳自然博物馆',
|
||||
region: '深圳市'
|
||||
})
|
||||
|
||||
await expect(result).resolves.toEqual({ status: 'failed', reason: 'bridge-unavailable' })
|
||||
expect(showToast).toHaveBeenCalledWith({
|
||||
title: '小程序地图桥接加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
|
||||
it('微信 SDK 首次加载失败后再次点击会重新创建脚本', async () => {
|
||||
const showToast = vi.fn()
|
||||
let currentScript: Record<string, unknown> | null = null
|
||||
const appendChild = vi.fn((script: Record<string, unknown>) => {
|
||||
currentScript = script
|
||||
setTimeout(() => (script.onerror as (() => void) | undefined)?.(), 0)
|
||||
})
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' }
|
||||
}))
|
||||
vi.stubGlobal('document', {
|
||||
querySelector: () => currentScript,
|
||||
createElement: () => {
|
||||
const script: Record<string, unknown> = { dataset: {} }
|
||||
script.remove = () => {
|
||||
if (currentScript === script) currentScript = null
|
||||
}
|
||||
return script
|
||||
},
|
||||
head: { appendChild }
|
||||
})
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
const first = openWechatMiniProgramLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(2600)
|
||||
await expect(first).resolves.toEqual({ status: 'failed', reason: 'bridge-unavailable' })
|
||||
|
||||
const second = openWechatMiniProgramLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(2600)
|
||||
await expect(second).resolves.toEqual({ status: 'failed', reason: 'bridge-unavailable' })
|
||||
expect(appendChild).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('宿主页跳转失败时给出缺页提示', async () => {
|
||||
const showToast = vi.fn()
|
||||
const navigateTo = vi.fn((options: { fail?: (error: unknown) => void }) => {
|
||||
options.fail?.({ errMsg: 'navigateTo:fail page not found' })
|
||||
})
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' },
|
||||
wx: { miniProgram: { navigateTo } }
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
await expect(openWechatMiniProgramLocation(target)).resolves.toEqual({
|
||||
status: 'failed',
|
||||
reason: 'navigation-failed'
|
||||
})
|
||||
expect(showToast).toHaveBeenCalledWith({
|
||||
title: '宿主小程序未配置地图页',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
|
||||
it('宿主页没有回调时超时返回并显示提示', async () => {
|
||||
const showToast = vi.fn()
|
||||
let navigateOptions: { fail?: (error: unknown) => void } | undefined
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' },
|
||||
wx: {
|
||||
miniProgram: {
|
||||
navigateTo: vi.fn((options: { fail?: (error: unknown) => void }) => {
|
||||
navigateOptions = options
|
||||
})
|
||||
}
|
||||
}
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
const result = openWechatMiniProgramLocation(target)
|
||||
await vi.advanceTimersByTimeAsync(3100)
|
||||
|
||||
await expect(result).resolves.toEqual({ status: 'failed', reason: 'navigation-failed' })
|
||||
expect(showToast).toHaveBeenCalledWith({
|
||||
title: '宿主地图页响应超时',
|
||||
icon: 'none'
|
||||
})
|
||||
navigateOptions?.fail?.({ errMsg: 'navigateTo:fail page not found' })
|
||||
expect(showToast).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('非缺页类宿主跳转失败使用通用提示', async () => {
|
||||
const showToast = vi.fn()
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' },
|
||||
wx: {
|
||||
miniProgram: {
|
||||
navigateTo: vi.fn((options: { fail?: (error: unknown) => void }) => {
|
||||
options.fail?.({ errMsg: 'navigateTo:fail page stack full' })
|
||||
})
|
||||
}
|
||||
}
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
await expect(openWechatMiniProgramLocation(target)).resolves.toEqual({
|
||||
status: 'failed',
|
||||
reason: 'navigation-failed'
|
||||
})
|
||||
expect(showToast).toHaveBeenCalledWith({
|
||||
title: '宿主地图页跳转失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
|
||||
it('宿主桥接同步抛错时显示调用失败提示', async () => {
|
||||
const showToast = vi.fn()
|
||||
vi.stubGlobal('window', createWindow({
|
||||
location: { search: '?embedded=weapp', hash: '' },
|
||||
wx: {
|
||||
miniProgram: {
|
||||
navigateTo: vi.fn(() => {
|
||||
throw new Error('bridge unavailable')
|
||||
})
|
||||
}
|
||||
}
|
||||
}))
|
||||
vi.stubGlobal('uni', { showToast })
|
||||
|
||||
await expect(openWechatMiniProgramLocation(target)).resolves.toEqual({
|
||||
status: 'failed',
|
||||
reason: 'navigation-failed'
|
||||
})
|
||||
expect(showToast).toHaveBeenCalledWith({
|
||||
title: '宿主地图桥接调用失败',
|
||||
title: '请在 H5 浏览器中打开地图',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
getArrivalNavigationActionText,
|
||||
resolveArrivalNavigationMode
|
||||
} from '@/components/navigation/arrivalNavigationPolicy'
|
||||
|
||||
describe('来馆导航入口策略', () => {
|
||||
it('普通 H5 与小程序 web-view 都使用第三方地图选择层', () => {
|
||||
const mode = resolveArrivalNavigationMode(false)
|
||||
|
||||
expect(mode).toBe('third-party-providers')
|
||||
expect(getArrivalNavigationActionText(mode, false)).toBe('第三方导航')
|
||||
})
|
||||
|
||||
it('只有原生微信小程序使用微信位置能力', () => {
|
||||
const mode = resolveArrivalNavigationMode(true)
|
||||
|
||||
expect(mode).toBe('native-location')
|
||||
expect(getArrivalNavigationActionText(mode, false)).toBe('打开地图导航')
|
||||
expect(getArrivalNavigationActionText(mode, true)).toBe('正在打开...')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user