@@ -55,15 +55,8 @@ test('hall goes directly to paged guide objects without outline requests', async
|
||||
expect(layout.first.width).toBeCloseTo(layout.listWidth, 0)
|
||||
expect(layout.second.y).toBeGreaterThan(layout.first.bottom)
|
||||
}
|
||||
const search = page.getByRole('textbox')
|
||||
await search.fill('讲解对象21')
|
||||
await expect(page.getByText('讲解对象21', { exact: true })).toBeVisible()
|
||||
await search.fill('')
|
||||
const audioFilter = page.getByText('有音频', { exact: true })
|
||||
await audioFilter.click()
|
||||
await expect(page.getByText('讲解对象1', { exact: true })).toBeVisible()
|
||||
await expect(page.getByText('讲解对象2', { exact: true })).not.toBeVisible()
|
||||
await page.getByText('全部', { exact: true }).click()
|
||||
await expect(page.getByRole('textbox')).toHaveCount(0)
|
||||
await expect(page.locator('.filter-chip')).toHaveCount(0)
|
||||
await page.locator('.explain-scroll').hover()
|
||||
await page.mouse.wheel(0, 10000)
|
||||
await expect(page.getByText('讲解对象21', { exact: true })).toBeVisible()
|
||||
|
||||
@@ -13,29 +13,26 @@ const stops = [
|
||||
const mountCatalog = () => mount(ExplainGuideStopCatalog, { props: { guideStops: stops, selectedHallName: '宇宙厅', hasMore: true } })
|
||||
|
||||
describe('ExplainGuideStopCatalog', () => {
|
||||
it('renders a stable single-column list with real availability copy and no arrows', () => {
|
||||
it('renders a stable single-column list with one truthful availability tag and no arrows', () => {
|
||||
const wrapper = mountCatalog()
|
||||
expect(wrapper.find('.stop-list').exists()).toBe(true)
|
||||
expect(wrapper.find('.stop-grid').exists()).toBe(false)
|
||||
expect(wrapper.findAll('.stop-card')).toHaveLength(3)
|
||||
expect(wrapper.findAll('.stop-cover.placeholder')).toHaveLength(2)
|
||||
expect(wrapper.text()).toContain('音频可播放')
|
||||
expect(wrapper.text()).toContain('图文可查看')
|
||||
expect(wrapper.text()).toContain('暂无可用内容')
|
||||
expect(wrapper.findAll('.stop-status')).toHaveLength(3)
|
||||
expect(wrapper.text()).toContain('音频')
|
||||
expect(wrapper.text()).toContain('图文')
|
||||
expect(wrapper.text()).toContain('暂无内容')
|
||||
expect(wrapper.find('.hall-arrow').exists()).toBe(false)
|
||||
expect(wrapper.find('.stop-name').classes()).toContain('stop-name')
|
||||
})
|
||||
|
||||
it('searches and uses only data-driven fallback filters when guide levels are absent', async () => {
|
||||
it('does not expose search or category controls in the focused list', () => {
|
||||
const wrapper = mountCatalog()
|
||||
expect(wrapper.text()).not.toContain('核心展品')
|
||||
expect(wrapper.get('input').attributes('placeholder')).toBe('搜索讲解对象')
|
||||
await wrapper.get('input').setValue('图文')
|
||||
expect(wrapper.findAll('.stop-card')).toHaveLength(1)
|
||||
expect(wrapper.emitted('requestAll')).toHaveLength(1)
|
||||
await wrapper.get('input').setValue('')
|
||||
await wrapper.get('.filter-chip:nth-child(2)').trigger('tap')
|
||||
expect(wrapper.findAll('.stop-card')).toHaveLength(1)
|
||||
expect(wrapper.find('input').exists()).toBe(false)
|
||||
expect(wrapper.find('.catalog-controls').exists()).toBe(false)
|
||||
expect(wrapper.find('.filter-chip').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('keeps loading and empty states available for the single-column list', async () => {
|
||||
@@ -47,14 +44,12 @@ describe('ExplainGuideStopCatalog', () => {
|
||||
expect(wrapper.find('.stop-list').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('emits card, back, retry, retry-more, and request-all actions', async () => {
|
||||
it('emits card, back, retry, and retry-more actions', async () => {
|
||||
const wrapper = mountCatalog()
|
||||
await wrapper.get('.stop-card').trigger('tap')
|
||||
await wrapper.get('.header-back').trigger('tap')
|
||||
await wrapper.get('.filter-chip:nth-child(2)').trigger('tap')
|
||||
expect(wrapper.emitted('guideStopClick')?.[0][0]).toMatchObject({ id: 'ready' })
|
||||
expect(wrapper.emitted('back')).toHaveLength(1)
|
||||
expect(wrapper.emitted('requestAll')).toHaveLength(1)
|
||||
|
||||
await wrapper.setProps({ error: '失败' })
|
||||
await wrapper.get('.state-retry').trigger('tap')
|
||||
|
||||
@@ -4,6 +4,7 @@ import { defineComponent } from 'vue'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import ExplainGuideStopListPage from '@/pages/explain/guide-stop-list.vue'
|
||||
import { explainUseCase } from '@/usecases/explainUseCase'
|
||||
|
||||
const testState = vi.hoisted(() => ({
|
||||
onLoadHandler: null as null | ((options?: Record<string, string>) => void)
|
||||
@@ -29,17 +30,18 @@ const GuidePageFrameStub = defineComponent({
|
||||
template: '<main><button data-testid="frame-back" @click="$emit(\'back\')">返回</button><slot /></main>'
|
||||
})
|
||||
|
||||
const ExplainHallSelectStub = defineComponent({
|
||||
name: 'ExplainHallSelect',
|
||||
emits: ['back'],
|
||||
template: '<button data-testid="list-back" @click="$emit(\'back\')">返回</button>'
|
||||
const ExplainGuideStopCatalogStub = defineComponent({
|
||||
name: 'ExplainGuideStopCatalog',
|
||||
props: ['guideStops', 'selectedHallName', 'loading', 'loadingMore', 'hasMore', 'error', 'loadMoreError'],
|
||||
emits: ['back', 'retry', 'retryMore', 'guideStopClick'],
|
||||
template: '<button data-testid="list-back" @click="$emit(\'back\')">返回</button><button data-testid="retry-more" @click="$emit(\'retryMore\')">重试更多</button>'
|
||||
})
|
||||
|
||||
const mountGuideStopList = () => mount(ExplainGuideStopListPage, {
|
||||
global: {
|
||||
stubs: {
|
||||
GuidePageFrame: GuidePageFrameStub,
|
||||
ExplainHallSelect: ExplainHallSelectStub
|
||||
ExplainGuideStopCatalog: ExplainGuideStopCatalogStub
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -53,6 +55,8 @@ beforeEach(() => {
|
||||
setNavigationBarTitle: vi.fn()
|
||||
})
|
||||
vi.stubGlobal('getCurrentPages', vi.fn(() => []))
|
||||
vi.mocked(explainUseCase.listGuideStopsPageByHall).mockReset()
|
||||
vi.mocked(explainUseCase.listGuideStopsPageByHall).mockResolvedValue({ items: [], total: 0, pageNo: 1, pageSize: 20, hasMore: false })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -61,6 +65,34 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('讲解对象列表返回', () => {
|
||||
it('ignores a delayed first-page response after unmount', async () => {
|
||||
let resolvePage: ((page: { items: Array<{ id: string, name: string }>, total: number, pageNo: number, pageSize: number, hasMore: boolean }) => void) | undefined
|
||||
vi.mocked(explainUseCase.listGuideStopsPageByHall).mockImplementationOnce(() => new Promise((resolve) => { resolvePage = resolve }))
|
||||
const wrapper = mountGuideStopList()
|
||||
testState.onLoadHandler?.({ hallId: 'hall-1' })
|
||||
wrapper.unmount()
|
||||
resolvePage?.({ items: [{ id: 'late', name: '延迟' }], total: 1, pageNo: 1, pageSize: 20, hasMore: false })
|
||||
await flushPromises()
|
||||
expect(explainUseCase.listGuideStopsPageByHall).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('permits retry-more recovery after a failed page', async () => {
|
||||
vi.mocked(explainUseCase.listGuideStopsPageByHall)
|
||||
.mockResolvedValueOnce({ items: [{ id: 'one', name: '一号' }], total: 2, pageNo: 1, pageSize: 20, hasMore: true })
|
||||
.mockRejectedValueOnce(new Error('temporary'))
|
||||
.mockResolvedValueOnce({ items: [{ id: 'two', name: '二号' }], total: 2, pageNo: 2, pageSize: 20, hasMore: false })
|
||||
const wrapper = mountGuideStopList()
|
||||
testState.onLoadHandler?.({ hallId: 'hall-1' })
|
||||
await flushPromises()
|
||||
await wrapper.get('[data-testid="retry-more"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(explainUseCase.listGuideStopsPageByHall).toHaveBeenCalledTimes(2)
|
||||
await wrapper.get('[data-testid="retry-more"]').trigger('click')
|
||||
await flushPromises()
|
||||
expect(explainUseCase.listGuideStopsPageByHall).toHaveBeenCalledTimes(3)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('上一页是独立展厅列表时回到该列表', async () => {
|
||||
vi.stubGlobal('getCurrentPages', vi.fn(() => [
|
||||
{ route: 'pages/explain/list', options: {} },
|
||||
|
||||
@@ -28,14 +28,14 @@ describe('讲解展厅选择列表', () => {
|
||||
const cards = wrapper.findAll('.hall-card')
|
||||
expect(cards).toHaveLength(1)
|
||||
expect(wrapper.text()).toContain('生命演化厅')
|
||||
expect(wrapper.text()).toContain('6 个讲解对象')
|
||||
expect(wrapper.text()).not.toContain('6 个讲解对象')
|
||||
expect(wrapper.text()).not.toContain('业务单元')
|
||||
|
||||
await cards[0]?.trigger('tap')
|
||||
expect(wrapper.emitted('hallClick')).toEqual([['hall-evolution']])
|
||||
})
|
||||
|
||||
it('展厅阶段按卡片网格展示英文名和进入入口', () => {
|
||||
it('展厅阶段按主题色卡展示展厅名称和图标', () => {
|
||||
const wrapper = mount(ExplainHallSelect, {
|
||||
props: {
|
||||
stage: 'hall',
|
||||
@@ -52,10 +52,11 @@ describe('讲解展厅选择列表', () => {
|
||||
|
||||
const card = wrapper.get('.hall-overview-card')
|
||||
expect(wrapper.get('.header-title').text()).toBe('展厅讲解')
|
||||
expect(card.attributes('style')).toContain('--hall-card-color: #c7a15c')
|
||||
expect(card.text()).toContain('Dinosaur Hall')
|
||||
expect(card.text()).toContain('L-2 · 35 个讲解对象')
|
||||
expect(card.get('.hall-go-entry').text()).toBe('GO ›')
|
||||
expect(card.attributes('style')).toContain('--hall-card-color: #d9b06c')
|
||||
expect(card.text()).toContain('恐龙厅')
|
||||
expect(card.text()).not.toContain('Dinosaur Hall')
|
||||
expect(card.text()).not.toContain('35 个讲解对象')
|
||||
expect(card.find('.hall-go-entry').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('READY 讲解点不显示可讲解标签,非 READY 讲解点保留图文标签和点击事件', async () => {
|
||||
|
||||
Reference in New Issue
Block a user