import { afterEach, describe, expect, it, vi } from 'vitest' import { BackendExplainContentProvider } from '@/data/providers/backendExplainContentProvider' const installRequest = (handler: (url: string) => unknown) => { vi.stubGlobal('uni', { request: ({ url, success }: { url: string, success: (response: unknown) => void }) => { success({ statusCode: 200, data: handler(url) }) } }) } describe('BackendExplainContentProvider hall stop paging', () => { afterEach(() => { vi.restoreAllMocks() vi.unstubAllGlobals() }) it('uses the paged hall endpoint and maps independent pages without outlines', async () => { const requests: string[] = [] installRequest((url) => { requests.push(url) if (url.includes('/catalog/halls?')) return { code: 0, data: [{ id: 'hall-1', name: '恐龙厅', floorId: 'f1' }] } const pageNo = new URL(url, 'http://localhost').searchParams.get('pageNo') return { code: 0, data: { total: 30, list: pageNo === '1' ? [{ stopId: 'stop-1', name: '对象一', externalCode: '0231', imageStatus: 'READY', coverImageUrl: '/one.jpg', outlineId: 'outline-1', linkedExhibits: [] }] : [{ stopId: 'stop-2', name: '对象二', imageStatus: 'MISSING', outlineId: 'outline-1', linkedExhibits: [] }] } } }) const provider = new BackendExplainContentProvider() const [first, second] = await Promise.all([ provider.listGuideStopsPageByHall('hall-1', 1, 20), provider.listGuideStopsPageByHall('hall-1', 2, 20) ]) expect(first).toMatchObject({ total: 30, pageNo: 1, pageSize: 20, hasMore: true }) expect(first.items[0]).toMatchObject({ id: 'stop-1', stopId: 'stop-1', externalCode: '0231', hallName: '恐龙厅', outlineId: 'outline-1' }) expect(second.items.map((item) => item.id)).toEqual(['stop-2']) expect(requests.filter((url) => url.includes('/stops/page'))).toHaveLength(2) expect(requests.some((url) => url.includes('/outlines'))).toBe(false) expect(requests.find((url) => url.includes('pageNo=1') && url.includes('pageSize=20') && url.includes('lang=zh-CN'))).toBeTruthy() }) it('clears failed inflight requests so the page can be retried', async () => { let attempts = 0 installRequest((url) => { if (url.includes('/catalog/halls?')) return { code: 0, data: [{ id: 'hall-1', name: '恐龙厅' }] } attempts += 1 return attempts === 1 ? { code: 500, msg: 'temporary failure' } : { code: 0, data: { list: [], total: 0 } } }) const provider = new BackendExplainContentProvider() await expect(provider.listGuideStopsPageByHall('hall-1', 1, 20)).rejects.toThrow('temporary failure') await expect(provider.listGuideStopsPageByHall('hall-1', 1, 20)).resolves.toMatchObject({ items: [], total: 0, hasMore: false }) expect(attempts).toBe(2) }) it('reuses the short-lived persistent catalog cache across provider instances', async () => { const values = new Map() vi.stubGlobal('window', { localStorage: { getItem: (key: string) => values.get(key) || null, setItem: (key: string, value: string) => values.set(key, value), removeItem: (key: string) => values.delete(key) } }) const requests: string[] = [] installRequest((url) => { requests.push(url) if (url.includes('/catalog/halls?')) return { code: 0, data: [{ id: 'hall-1', name: '恐龙厅' }] } return { code: 0, data: { list: [{ stopId: 'stop-1', name: '对象一' }], total: 1 } } }) const firstProvider = new BackendExplainContentProvider() const secondProvider = new BackendExplainContentProvider() await firstProvider.listGuideStopsPageByHall('hall-1', 1, 20) await secondProvider.listGuideStopsPageByHall('hall-1', 1, 20) expect(requests.filter((url) => url.includes('/catalog/halls?'))).toHaveLength(1) expect(requests.filter((url) => url.includes('/stops/page'))).toHaveLength(1) }) it('warns when a hall has guide stops but every compatibility outline reports zero', async () => { const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) installRequest((url) => { if (url.includes('/catalog/halls?')) { return { code: 0, data: [{ id: 'hall-1', name: '恐龙厅', stopCount: 2 }] } } if (url.includes('/catalog/halls/hall-1/outlines?')) { return { code: 0, data: [{ id: 'outline-1', name: '单元一', hallId: 'hall-1', stopCount: 0 }] } } throw new Error(`Unexpected request: ${url}`) }) const units = await new BackendExplainContentProvider().listTemporaryBusinessUnitsByHall('hall-1') expect(units).toHaveLength(1) expect(warn).toHaveBeenCalledWith( '一级单元与讲解点关联异常,一级单元仅作为兼容入口:', expect.objectContaining({ hallId: 'hall-1', hallStopCount: 2 }) ) }) })