修复展厅列表嵌入返回入口
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="explain-hall-select" :class="{ 'host-navigation': shouldUseHostNavigation }">
|
<view class="explain-hall-select" :class="{ 'host-navigation': shouldUseHostNavigation && !showInternalHeader }">
|
||||||
<view v-if="showInternalHeader" class="explain-page-header">
|
<view v-if="showInternalHeader" class="explain-page-header">
|
||||||
<view class="header-back" @tap="handleBack">
|
<view class="header-back" @tap="handleBack">
|
||||||
<text class="header-back-icon">‹</text>
|
<text class="header-back-icon">‹</text>
|
||||||
@@ -167,6 +167,7 @@ const props = withDefaults(defineProps<{
|
|||||||
hasMore?: boolean
|
hasMore?: boolean
|
||||||
error?: string
|
error?: string
|
||||||
loadMoreError?: string
|
loadMoreError?: string
|
||||||
|
forceInternalHeader?: boolean
|
||||||
}>(), {
|
}>(), {
|
||||||
halls: () => [],
|
halls: () => [],
|
||||||
guideStops: () => [],
|
guideStops: () => [],
|
||||||
@@ -176,7 +177,8 @@ const props = withDefaults(defineProps<{
|
|||||||
loadingMore: false,
|
loadingMore: false,
|
||||||
hasMore: false,
|
hasMore: false,
|
||||||
error: '',
|
error: '',
|
||||||
loadMoreError: ''
|
loadMoreError: '',
|
||||||
|
forceInternalHeader: false
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -218,7 +220,7 @@ const hallCardThemeMap: Record<string, { color: string; englishName: string }> =
|
|||||||
|
|
||||||
const filteredHalls = computed(() => props.halls)
|
const filteredHalls = computed(() => props.halls)
|
||||||
const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
|
const shouldUseHostNavigation = computed(() => isEmbeddedInWechatMiniProgram())
|
||||||
const showInternalHeader = computed(() => !shouldUseHostNavigation.value)
|
const showInternalHeader = computed(() => props.forceInternalHeader || !shouldUseHostNavigation.value)
|
||||||
const headerTitle = computed(() => {
|
const headerTitle = computed(() => {
|
||||||
if (props.stage === 'stop') return props.selectedHallName || '讲解对象'
|
if (props.stage === 'stop') return props.selectedHallName || '讲解对象'
|
||||||
return '展厅讲解'
|
return '展厅讲解'
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<ExplainHallSelect
|
<ExplainHallSelect
|
||||||
:halls="explainHallItems"
|
:halls="explainHallItems"
|
||||||
stage="hall"
|
stage="hall"
|
||||||
|
force-internal-header
|
||||||
:loading="explainLoading"
|
:loading="explainLoading"
|
||||||
:error="explainError"
|
:error="explainError"
|
||||||
@hall-click="handleExplainHallClick"
|
@hall-click="handleExplainHallClick"
|
||||||
|
|||||||
@@ -65,3 +65,52 @@ test('hall goes directly to paged guide objects without outline requests', async
|
|||||||
expect(requests.some((url) => url.includes('/outlines'))).toBe(false)
|
expect(requests.some((url) => url.includes('/outlines'))).toBe(false)
|
||||||
expect(requests.filter((url) => url.includes('/stops/page')).map((url) => new URL(url).searchParams.get('pageNo'))).toEqual(['1', '2'])
|
expect(requests.filter((url) => url.includes('/stops/page')).map((url) => new URL(url).searchParams.get('pageNo'))).toEqual(['1', '2'])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('embedded hall list exposes an H5 return control that keeps host markers', async ({ page }) => {
|
||||||
|
await page.route('**/app-api/gis/guide/catalog/**', async (route) => {
|
||||||
|
const url = route.request().url()
|
||||||
|
if (url.includes('/catalog/halls?')) {
|
||||||
|
await route.fulfill({ json: { code: 0, data: [{ id: 'hall-1', name: '恐龙厅', floorId: 'L1', stopCount: 1 }] } })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await route.fulfill({ json: { code: 0, data: {} } })
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.goto('/#/pages/explain/list?embedded=wechat-mini-program&weapp=1')
|
||||||
|
const back = page.locator('.header-back')
|
||||||
|
await expect(back).toHaveCount(1)
|
||||||
|
await expect(back).toBeVisible()
|
||||||
|
await expect(page.locator('.hall-overview-card')).toHaveCount(1)
|
||||||
|
for (const width of [375, 390, 430]) {
|
||||||
|
await page.setViewportSize({ width, height: 844 })
|
||||||
|
const layout = await page.locator('.explain-hall-select').evaluate((container) => {
|
||||||
|
const header = container.querySelector<HTMLElement>('.explain-page-header')
|
||||||
|
const card = container.querySelector<HTMLElement>('.hall-overview-card')
|
||||||
|
if (!header || !card) throw new Error('missing header or hall card')
|
||||||
|
const headerRect = header.getBoundingClientRect()
|
||||||
|
const cardRect = card.getBoundingClientRect()
|
||||||
|
return {
|
||||||
|
clientWidth: container.clientWidth,
|
||||||
|
scrollWidth: container.scrollWidth,
|
||||||
|
headerBottom: headerRect.bottom,
|
||||||
|
cardTop: cardRect.top
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(layout.scrollWidth).toBeLessThanOrEqual(layout.clientWidth)
|
||||||
|
expect(layout.cardTop).toBeGreaterThanOrEqual(layout.headerBottom)
|
||||||
|
}
|
||||||
|
await back.click()
|
||||||
|
await expect(page).toHaveURL(/#\/\?tab=guide&embedded=wechat-mini-program&weapp=1$/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ordinary H5 hall list return reaches the guide home', async ({ page }) => {
|
||||||
|
await page.route('**/app-api/gis/guide/catalog/**', (route) => (
|
||||||
|
route.fulfill({ json: { code: 0, data: [] } })
|
||||||
|
))
|
||||||
|
|
||||||
|
await page.goto('/#/pages/explain/list')
|
||||||
|
const back = page.locator('.header-back')
|
||||||
|
await expect(back).toHaveCount(1)
|
||||||
|
await back.click()
|
||||||
|
await expect(page).toHaveURL(/#\/\?tab=guide$/)
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,14 +1,46 @@
|
|||||||
// @vitest-environment happy-dom
|
// @vitest-environment happy-dom
|
||||||
|
|
||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||||
import ExplainHallSelect from '@/components/explain/ExplainHallSelect.vue'
|
import ExplainHallSelect from '@/components/explain/ExplainHallSelect.vue'
|
||||||
|
|
||||||
vi.mock('@/utils/hostEnvironment', () => ({
|
const hostEnvironmentMocks = vi.hoisted(() => ({
|
||||||
isEmbeddedInWechatMiniProgram: () => false
|
embeddedInWechatMiniProgram: false
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.mock('@/utils/hostEnvironment', () => ({
|
||||||
|
isEmbeddedInWechatMiniProgram: () => hostEnvironmentMocks.embeddedInWechatMiniProgram
|
||||||
|
}))
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
hostEnvironmentMocks.embeddedInWechatMiniProgram = false
|
||||||
|
})
|
||||||
|
|
||||||
describe('讲解展厅选择列表', () => {
|
describe('讲解展厅选择列表', () => {
|
||||||
|
it('微信嵌入态独立页面可强制显示 H5 返回入口,且不使用宿主导航布局', async () => {
|
||||||
|
hostEnvironmentMocks.embeddedInWechatMiniProgram = true
|
||||||
|
const wrapper = mount(ExplainHallSelect, {
|
||||||
|
props: {
|
||||||
|
forceInternalHeader: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(wrapper.findAll('.explain-page-header')).toHaveLength(1)
|
||||||
|
expect(wrapper.findAll('.header-back')).toHaveLength(1)
|
||||||
|
expect(wrapper.classes()).not.toContain('host-navigation')
|
||||||
|
|
||||||
|
await wrapper.get('.header-back').trigger('tap')
|
||||||
|
expect(wrapper.emitted('back')).toEqual([[]])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('微信嵌入态未强制时保留宿主导航布局,保护首页讲解 Tab', () => {
|
||||||
|
hostEnvironmentMocks.embeddedInWechatMiniProgram = true
|
||||||
|
const wrapper = mount(ExplainHallSelect)
|
||||||
|
|
||||||
|
expect(wrapper.find('.explain-page-header').exists()).toBe(false)
|
||||||
|
expect(wrapper.classes()).toContain('host-navigation')
|
||||||
|
})
|
||||||
|
|
||||||
it('展厅卡片只显示讲解对象数量,不暴露业务单元契约', async () => {
|
it('展厅卡片只显示讲解对象数量,不暴露业务单元契约', async () => {
|
||||||
const wrapper = mount(ExplainHallSelect, {
|
const wrapper = mount(ExplainHallSelect, {
|
||||||
props: {
|
props: {
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ const GuidePageFrameStub = defineComponent({
|
|||||||
|
|
||||||
const ExplainHallSelectStub = defineComponent({
|
const ExplainHallSelectStub = defineComponent({
|
||||||
name: 'ExplainHallSelect',
|
name: 'ExplainHallSelect',
|
||||||
|
props: {
|
||||||
|
forceInternalHeader: Boolean
|
||||||
|
},
|
||||||
emits: ['back'],
|
emits: ['back'],
|
||||||
template: '<button data-testid="explain-list-back" @click="$emit(\'back\')">返回</button>'
|
template: '<button data-testid="explain-list-back" @click="$emit(\'back\')">返回</button>'
|
||||||
})
|
})
|
||||||
@@ -103,6 +106,22 @@ describe('讲解展厅列表返回', () => {
|
|||||||
wrapper.unmount()
|
wrapper.unmount()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('独立页在微信嵌入态强制提供 H5 返回,并保留宿主识别参数', async () => {
|
||||||
|
hostEnvironmentMocks.embeddedInWechatMiniProgram = true
|
||||||
|
window.location.hash = '#/pages/explain/list?embedded=wechat-mini-program&weapp=1'
|
||||||
|
window.history.replaceState({}, '', window.location.href)
|
||||||
|
const wrapper = mountExplainList()
|
||||||
|
|
||||||
|
expect(wrapper.getComponent(ExplainHallSelectStub).props('forceInternalHeader')).toBe(true)
|
||||||
|
await wrapper.get('[data-testid="explain-list-back"]').trigger('click')
|
||||||
|
|
||||||
|
expect(uni.navigateBack).not.toHaveBeenCalled()
|
||||||
|
expect(uni.reLaunch).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
url: '/pages/index/index?tab=guide&embedded=wechat-mini-program&weapp=1'
|
||||||
|
}))
|
||||||
|
wrapper.unmount()
|
||||||
|
})
|
||||||
|
|
||||||
it('上一页不是首页时使用 reLaunch,避免回到不相关页面', async () => {
|
it('上一页不是首页时使用 reLaunch,避免回到不相关页面', async () => {
|
||||||
vi.stubGlobal('getCurrentPages', vi.fn(() => [
|
vi.stubGlobal('getCurrentPages', vi.fn(() => [
|
||||||
{ route: 'pages/search/index', options: {} },
|
{ route: 'pages/search/index', options: {} },
|
||||||
|
|||||||
Reference in New Issue
Block a user