Some checks failed
CI / verify (push) Has been cancelled
- 使用真实讲解编号解析接口替换本地演示映射 - 成功后复用现有详情页并自动播放讲解 - 增加查询状态、业务错误提示和相关测试 Made-with: Proma
107 lines
4.7 KiB
TypeScript
107 lines
4.7 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest'
|
|
import type { GuideStopDetail } from '@/data/adapters/guideStopInfoAdapter'
|
|
import type { AudioPlayInfoRepository } from '@/repositories/AudioPlayInfoRepository'
|
|
import type { ExplainRepository } from '@/repositories/ExplainRepository'
|
|
import { ExplainUseCase } from '@/usecases/explainUseCase'
|
|
import { dataSourceConfig } from '@/config/dataSource'
|
|
|
|
const createDetail = (overrides: Partial<GuideStopDetail> = {}): GuideStopDetail => ({
|
|
id: 'stop-1',
|
|
version: 'standard',
|
|
name: 'Guide stop',
|
|
galleryUrls: [],
|
|
imageStatus: 'MISSING',
|
|
linkedExhibits: [{ id: 'exhibit-1', name: 'Linked exhibit', coverImageUrl: '/linked-exhibit.jpg' }],
|
|
isSharedStop: false,
|
|
textVariants: [
|
|
{ version: 'standard', languageCode: 'zh-CN', text: '普通话正文' },
|
|
{ version: 'standard', languageCode: 'en-US', text: 'English text' }
|
|
],
|
|
audioTracks: [
|
|
{ channelCode: 'standard.zh-CN.female', version: 'standard', languageCode: 'zh-CN', gender: 'female', playUrl: '/zh-female.mp3' },
|
|
{ channelCode: 'standard.yue-HK.female', version: 'standard', languageCode: 'yue-HK', gender: 'female', playUrl: '/yue-female.mp3' }
|
|
],
|
|
hasText: true,
|
|
hasAudio: true,
|
|
textVariantCount: 2,
|
|
audioTrackCount: 2,
|
|
...overrides
|
|
})
|
|
|
|
const createUseCase = (detail: GuideStopDetail) => {
|
|
const explain = {
|
|
getExhibitById: vi.fn().mockResolvedValue(null),
|
|
listExplainExhibits: vi.fn().mockResolvedValue([])
|
|
} as unknown as ExplainRepository
|
|
const audio = {
|
|
getStopDetail: vi.fn().mockResolvedValue(detail),
|
|
getStopInfoByCode: vi.fn().mockResolvedValue({
|
|
available: true,
|
|
resolvedStopId: 'resolved-stop-1',
|
|
playTargetId: 'resolved-stop-1',
|
|
targetId: 'resolved-stop-1'
|
|
})
|
|
} as unknown as AudioPlayInfoRepository
|
|
return { useCase: new ExplainUseCase(explain, audio), explain, audio }
|
|
}
|
|
|
|
describe('ExplainUseCase unified stop detail', () => {
|
|
it('resolves the museum code to the backend stop ID', async () => {
|
|
const { useCase, audio } = createUseCase(createDetail())
|
|
|
|
const result = await useCase.resolveExplainCode('1001', 'zh-CN')
|
|
|
|
expect(audio.getStopInfoByCode).toHaveBeenCalledWith('1001', 'zh-CN')
|
|
expect(result).toEqual({ code: '1001', stopId: 'resolved-stop-1' })
|
|
})
|
|
|
|
it('uses stopId once in remote mode without requesting a catalog fallback', async () => {
|
|
const originalMode = dataSourceConfig.explainContentMode
|
|
Object.assign(dataSourceConfig, { explainContentMode: 'remote' })
|
|
const { useCase, explain, audio } = createUseCase(createDetail())
|
|
|
|
const result = await useCase.enterExplainDetail({ exhibitId: 'stop-1', stopId: 'stop-1', hallName: '宇宙厅' })
|
|
|
|
expect(audio.getStopDetail).toHaveBeenCalledWith('stop-1', { version: 'standard' })
|
|
expect(explain.getExhibitById).not.toHaveBeenCalled()
|
|
expect(result.hallName).toBe('宇宙厅')
|
|
Object.assign(dataSourceConfig, { explainContentMode: originalMode })
|
|
})
|
|
|
|
it('does not use linked exhibit images when detail reports MISSING', async () => {
|
|
const { useCase } = createUseCase(createDetail())
|
|
const result = await useCase.enterExplainDetail({ exhibitId: 'stop-1', stopId: 'stop-1' })
|
|
|
|
expect(result.image).toBeUndefined()
|
|
expect(result.galleryUrls).toEqual([])
|
|
expect(result.linkedExhibits?.[0]?.coverImageUrl).toBe('/linked-exhibit.jpg')
|
|
})
|
|
|
|
it('uses local standard text and audio variants, with Chinese text fallback for Cantonese', async () => {
|
|
const { useCase, audio } = createUseCase(createDetail())
|
|
const result = await useCase.enterExplainDetail({ exhibitId: 'stop-1', stopId: 'stop-1', lang: 'yue-HK' })
|
|
const text = await useCase.loadExplainDetailText(result)
|
|
const selection = await useCase.selectAudioForExplainDetail(result, { voiceGender: 'male' })
|
|
|
|
expect(result.guideText).toBe('普通话正文')
|
|
expect(text.textInfo.text).toBe('普通话正文')
|
|
expect(selection.media?.url).toBe('/yue-female.mp3')
|
|
expect(audio.getStopDetail).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('uses the recommended track for the current detail version by default', async () => {
|
|
const { useCase } = createUseCase(createDetail({
|
|
recommendedTrackCode: 'standard.zh-CN.female',
|
|
audioTracks: [
|
|
{ channelCode: 'standard.zh-CN.female', version: 'standard', languageCode: 'zh-CN', gender: 'female', playUrl: '/zh-female.mp3' },
|
|
{ channelCode: 'standard.zh-CN.male', version: 'standard', languageCode: 'zh-CN', gender: 'male', playUrl: '/zh-male.mp3' }
|
|
]
|
|
}))
|
|
|
|
const result = await useCase.enterExplainDetail({ exhibitId: 'stop-1', stopId: 'stop-1' })
|
|
const selection = await useCase.selectAudioForExplainDetail(result)
|
|
|
|
expect(selection.media?.url).toBe('/zh-female.mp3')
|
|
})
|
|
})
|