@@ -1,102 +1,89 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { GuideStopInfo } from '@/data/adapters/guideStopInfoAdapter'
|
||||
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 createStopInfo = (overrides: Partial<GuideStopInfo> = {}): GuideStopInfo => ({
|
||||
available: true,
|
||||
targetType: 'STOP',
|
||||
targetId: 'stop-1',
|
||||
lang: 'zh-CN',
|
||||
title: 'Guide stop',
|
||||
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'
|
||||
}
|
||||
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' }
|
||||
],
|
||||
playTargetType: 'STOP',
|
||||
playTargetId: 'stop-1',
|
||||
hasAudio: false,
|
||||
hasText: false,
|
||||
supportedLanguages: [],
|
||||
audioStatus: 'MISSING',
|
||||
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 = (stopInfo: GuideStopInfo) => {
|
||||
const createUseCase = (detail: GuideStopDetail) => {
|
||||
const explain = {
|
||||
getExhibitById: vi.fn().mockResolvedValue(null),
|
||||
listExplainExhibits: vi.fn().mockResolvedValue([])
|
||||
} as unknown as ExplainRepository
|
||||
const audioPlayInfo = {
|
||||
getStopInfo: vi.fn().mockResolvedValue(stopInfo)
|
||||
} as unknown as AudioPlayInfoRepository
|
||||
|
||||
return new ExplainUseCase(explain, audioPlayInfo)
|
||||
const audio = { getStopDetail: vi.fn().mockResolvedValue(detail) } as unknown as AudioPlayInfoRepository
|
||||
return { useCase: new ExplainUseCase(explain, audio), explain, audio }
|
||||
}
|
||||
|
||||
describe('ExplainUseCase stop image policy', () => {
|
||||
it('uses stop-info first in remote mode without requesting a catalog fallback', async () => {
|
||||
describe('ExplainUseCase unified stop detail', () => {
|
||||
it('uses stopId once in remote mode without requesting a catalog fallback', async () => {
|
||||
const originalMode = dataSourceConfig.explainContentMode
|
||||
Object.assign(dataSourceConfig, { explainContentMode: 'remote' })
|
||||
const explain = {
|
||||
getExhibitById: vi.fn(),
|
||||
listExplainExhibits: vi.fn(),
|
||||
listExplainExhibitsByHall: vi.fn()
|
||||
} as unknown as ExplainRepository
|
||||
const audio = { getStopInfo: vi.fn().mockResolvedValue(createStopInfo()) } as unknown as AudioPlayInfoRepository
|
||||
const { useCase, explain, audio } = createUseCase(createDetail())
|
||||
|
||||
await new ExplainUseCase(explain, audio).enterExplainDetail({
|
||||
exhibitId: 'stop-1', targetType: 'STOP', targetId: 'stop-1', hallName: '宇宙厅'
|
||||
})
|
||||
const result = await useCase.enterExplainDetail({ exhibitId: 'stop-1', stopId: 'stop-1', hallName: '宇宙厅' })
|
||||
|
||||
expect(audio.getStopInfo).toHaveBeenCalledTimes(1)
|
||||
expect(audio.getStopDetail).toHaveBeenCalledWith('stop-1', { version: 'standard' })
|
||||
expect(explain.getExhibitById).not.toHaveBeenCalled()
|
||||
expect(explain.listExplainExhibits).not.toHaveBeenCalled()
|
||||
expect(result.hallName).toBe('宇宙厅')
|
||||
Object.assign(dataSourceConfig, { explainContentMode: originalMode })
|
||||
})
|
||||
|
||||
it('delegates hall exhibits and business-unit stops without loading full exhibits', async () => {
|
||||
const explain = {
|
||||
listExplainExhibitsByHall: vi.fn().mockResolvedValue([]),
|
||||
listGuideStopsByBusinessUnit: vi.fn().mockResolvedValue([])
|
||||
} as unknown as ExplainRepository
|
||||
const useCase = new ExplainUseCase(explain, {} as AudioPlayInfoRepository)
|
||||
await useCase.listExhibitsByHallId('hall-1')
|
||||
await useCase.listGuideStopsByBusinessUnit('hall-1', 'outline-1')
|
||||
expect(explain.listExplainExhibitsByHall).toHaveBeenCalledWith('hall-1')
|
||||
expect(explain.listGuideStopsByBusinessUnit).toHaveBeenCalledWith('hall-1', 'outline-1')
|
||||
})
|
||||
it('does not use linked exhibit images when stop-info reports MISSING', async () => {
|
||||
const detail = await createUseCase(createStopInfo()).enterExplainDetail({
|
||||
exhibitId: 'stop-1',
|
||||
targetType: 'STOP',
|
||||
targetId: 'stop-1'
|
||||
})
|
||||
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(detail.image).toBeUndefined()
|
||||
expect(detail.galleryUrls).toEqual([])
|
||||
expect(detail.linkedExhibits?.[0]?.coverImageUrl).toBe('/linked-exhibit.jpg')
|
||||
expect(result.image).toBeUndefined()
|
||||
expect(result.galleryUrls).toEqual([])
|
||||
expect(result.linkedExhibits?.[0]?.coverImageUrl).toBe('/linked-exhibit.jpg')
|
||||
})
|
||||
|
||||
it('uses the stop image when stop-info reports READY', async () => {
|
||||
const detail = await createUseCase(createStopInfo({
|
||||
imageStatus: 'READY',
|
||||
coverImageUrl: '/guide-stop.jpg',
|
||||
galleryUrls: ['/guide-stop-gallery.jpg']
|
||||
})).enterExplainDetail({
|
||||
exhibitId: 'stop-1',
|
||||
targetType: 'STOP',
|
||||
targetId: 'stop-1'
|
||||
})
|
||||
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(detail.image).toBe('/guide-stop.jpg')
|
||||
expect(detail.galleryUrls).toEqual(['/guide-stop-gallery.jpg'])
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user