适配讲解详情聚合接口
This commit is contained in:
@@ -9,6 +9,7 @@ const mocks = vi.hoisted(() => ({
|
||||
onHideHandler: null as null | (() => void),
|
||||
onUnloadHandler: null as null | (() => void),
|
||||
enterExplainDetail: vi.fn(),
|
||||
selectExplainDetailLanguage: vi.fn(),
|
||||
loadExplainDetailText: vi.fn(),
|
||||
selectAudioForExplainDetail: vi.fn(),
|
||||
play: vi.fn(),
|
||||
@@ -43,6 +44,7 @@ vi.mock('@dcloudio/uni-app', () => ({
|
||||
vi.mock('@/usecases/explainUseCase', () => ({
|
||||
explainUseCase: {
|
||||
enterExplainDetail: mocks.enterExplainDetail,
|
||||
selectExplainDetailLanguage: mocks.selectExplainDetailLanguage,
|
||||
loadExplainDetailText: mocks.loadExplainDetailText,
|
||||
selectAudioForExplainDetail: mocks.selectAudioForExplainDetail
|
||||
}
|
||||
@@ -110,6 +112,10 @@ describe('讲解详情音频优先布局', () => {
|
||||
audioState.currentTime.value = 0
|
||||
audioState.duration.value = 0
|
||||
mocks.enterExplainDetail.mockResolvedValue(exhibit)
|
||||
mocks.selectExplainDetailLanguage.mockImplementation((source, lang) => ({
|
||||
...source,
|
||||
audioLanguage: lang
|
||||
}))
|
||||
mocks.loadExplainDetailText.mockResolvedValue({ available: false })
|
||||
mocks.selectAudioForExplainDetail.mockResolvedValue({
|
||||
playable: true,
|
||||
@@ -191,7 +197,7 @@ describe('讲解详情音频优先布局', () => {
|
||||
expect(wrapper.find('.detail-language-bar').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('深链语言不受支持时回退至中文并规范化地址', async () => {
|
||||
it('深链语言不受支持时本地回退至中文并规范化地址', async () => {
|
||||
mocks.enterExplainDetail.mockImplementation(async (request: { lang: string }) => ({
|
||||
...exhibit,
|
||||
audioLanguage: request.lang,
|
||||
@@ -205,8 +211,9 @@ describe('讲解详情音频优先布局', () => {
|
||||
await mocks.onLoadHandler?.({ id: exhibit.id, lang: 'en-US', targetType: 'STOP', targetId: 'stop-1' })
|
||||
await flushPromises()
|
||||
|
||||
expect(mocks.enterExplainDetail).toHaveBeenNthCalledWith(1, expect.objectContaining({ lang: 'en-US' }))
|
||||
expect(mocks.enterExplainDetail).toHaveBeenNthCalledWith(2, expect.objectContaining({ lang: 'zh-CN' }))
|
||||
expect(mocks.enterExplainDetail).toHaveBeenCalledTimes(1)
|
||||
expect(mocks.enterExplainDetail).toHaveBeenCalledWith(expect.objectContaining({ lang: 'en-US' }))
|
||||
expect(mocks.selectExplainDetailLanguage).toHaveBeenCalledWith(expect.any(Object), 'zh-CN')
|
||||
expect(wrapper.get('.language-option').classes()).toContain('active')
|
||||
expect(wrapper.get('.language-option-text').text()).toBe('中文')
|
||||
expect(replaceState).toHaveBeenCalledWith(null, '', expect.stringContaining('lang=zh-CN'))
|
||||
@@ -244,6 +251,34 @@ describe('讲解详情音频优先布局', () => {
|
||||
expect(wrapper.get('.detail-audio-play').attributes('disabled')).toBeDefined()
|
||||
})
|
||||
|
||||
it('切换语言时使用初次详情响应中的变体,不重复请求详情或正文', async () => {
|
||||
const localizedExhibit = {
|
||||
...exhibit,
|
||||
guideText: '粤语通道使用普通话文案。',
|
||||
audioUrl: '/assets/astrolabe-yue.mp3',
|
||||
audioDuration: 83,
|
||||
audioLanguage: 'yue-HK',
|
||||
audioStatus: 'READY',
|
||||
audioHasText: true,
|
||||
supportedLanguages: ['zh-CN', 'yue-HK', 'en-US']
|
||||
}
|
||||
mocks.selectExplainDetailLanguage.mockReturnValue(localizedExhibit)
|
||||
const wrapper = mount(ExhibitDetail, {
|
||||
global: { stubs: { GuidePageFrame: GuidePageFrameStub } }
|
||||
})
|
||||
|
||||
await mocks.onLoadHandler?.({ id: exhibit.id, lang: 'en-US', targetType: 'STOP', targetId: 'stop-1' })
|
||||
await flushPromises()
|
||||
await wrapper.findAll('.language-option').find((node) => node.text() === '粤语')?.trigger('tap')
|
||||
await flushPromises()
|
||||
|
||||
expect(mocks.enterExplainDetail).toHaveBeenCalledTimes(1)
|
||||
expect(mocks.loadExplainDetailText).not.toHaveBeenCalled()
|
||||
expect(mocks.selectExplainDetailLanguage).toHaveBeenCalledWith(expect.any(Object), 'yue-HK')
|
||||
expect(wrapper.get('.section-text').text()).toContain('粤语通道使用普通话文案')
|
||||
expect(wrapper.get('.detail-audio-subtitle').text()).toContain('粤语')
|
||||
})
|
||||
|
||||
it('无音频时禁用播放按钮并显示原因', async () => {
|
||||
mocks.enterExplainDetail.mockResolvedValue({
|
||||
...exhibit,
|
||||
|
||||
@@ -25,6 +25,7 @@ const createStopInfo = (overrides: Partial<GuideStopInfo> = {}): GuideStopInfo =
|
||||
hasAudio: false,
|
||||
hasText: false,
|
||||
supportedLanguages: [],
|
||||
languageVariants: {},
|
||||
audioStatus: 'MISSING',
|
||||
...overrides
|
||||
})
|
||||
@@ -99,4 +100,60 @@ describe('ExplainUseCase stop image policy', () => {
|
||||
expect(detail.image).toBe('/guide-stop.jpg')
|
||||
expect(detail.galleryUrls).toEqual(['/guide-stop-gallery.jpg'])
|
||||
})
|
||||
|
||||
it('uses stop-info language variants locally without requesting play-info or text-info', async () => {
|
||||
const audio = {
|
||||
getStopInfo: vi.fn().mockResolvedValue(createStopInfo({
|
||||
hasAudio: true,
|
||||
hasText: true,
|
||||
supportedLanguages: ['zh-CN', 'yue-HK'],
|
||||
languageVariants: {
|
||||
'zh-CN': {
|
||||
lang: 'zh-CN',
|
||||
enabled: true,
|
||||
playable: true,
|
||||
playUrl: '/audio/zh.mp3',
|
||||
duration: 50,
|
||||
hasText: true,
|
||||
textAvailable: true,
|
||||
text: '普通话讲解词',
|
||||
fallback: false,
|
||||
audioStatus: 'READY'
|
||||
},
|
||||
'yue-HK': {
|
||||
lang: 'yue-HK',
|
||||
enabled: true,
|
||||
playable: true,
|
||||
playUrl: '/audio/yue.mp3',
|
||||
duration: 52,
|
||||
hasText: true,
|
||||
textAvailable: true,
|
||||
text: '粤语通道使用普通话文案',
|
||||
fallback: false,
|
||||
audioStatus: 'READY'
|
||||
}
|
||||
} as GuideStopInfo['languageVariants']
|
||||
})),
|
||||
getPlayInfo: vi.fn(),
|
||||
getTextInfo: vi.fn()
|
||||
} as unknown as AudioPlayInfoRepository
|
||||
const explain = {
|
||||
getExhibitById: vi.fn(),
|
||||
listExplainExhibits: vi.fn()
|
||||
} as unknown as ExplainRepository
|
||||
const useCase = new ExplainUseCase(explain, audio)
|
||||
|
||||
const detail = await useCase.enterExplainDetail({
|
||||
exhibitId: 'stop-1', targetType: 'STOP', targetId: 'stop-1', lang: 'zh-CN'
|
||||
})
|
||||
const cantonese = useCase.selectExplainDetailLanguage(detail, 'yue-HK')
|
||||
|
||||
expect(detail.guideText).toBe('普通话讲解词')
|
||||
expect(detail.audioUrl).toBe('/audio/zh.mp3')
|
||||
expect(cantonese.guideText).toBe('粤语通道使用普通话文案')
|
||||
expect(cantonese.audioUrl).toBe('/audio/yue.mp3')
|
||||
expect(cantonese.audioDuration).toBe(52)
|
||||
expect((audio as any).getPlayInfo).not.toHaveBeenCalled()
|
||||
expect((audio as any).getTextInfo).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -35,7 +35,36 @@ describe('全局讲解播放器语言切换', () => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('切换到粤语时立即中断旧音频,并使用 yue-HK 播放地址', async () => {
|
||||
it('详情页提供语言变体播放地址时直接切换,不请求 play-info', async () => {
|
||||
await player.play({
|
||||
id: 'mandarin-audio',
|
||||
name: '测试讲解',
|
||||
audioUrl: '/museum-assets/audio/mandarin.mp3',
|
||||
language: 'zh-CN'
|
||||
}, {
|
||||
source: {
|
||||
targetType: 'STOP',
|
||||
targetId: '1823450596800289',
|
||||
lang: 'zh-CN'
|
||||
}
|
||||
})
|
||||
|
||||
await expect(player.switchLanguage('yue-HK', {
|
||||
id: 'cantonese-audio',
|
||||
name: '测试讲解',
|
||||
audioUrl: '/museum-assets/audio/cantonese.mp3',
|
||||
language: 'yue-HK'
|
||||
})).resolves.toBe(true)
|
||||
|
||||
expect(repositoryMocks.getPlayInfo).not.toHaveBeenCalled()
|
||||
expect(player.currentSource.value?.lang).toBe('yue-HK')
|
||||
expect(player.currentAudio.value).toEqual(expect.objectContaining({
|
||||
language: 'yue-HK',
|
||||
audioUrl: '/museum-assets/audio/cantonese.mp3'
|
||||
}))
|
||||
})
|
||||
|
||||
it('未提供详情语言变体时保留 play-info 刷新回退', async () => {
|
||||
repositoryMocks.getPlayInfo.mockResolvedValue({
|
||||
playable: true,
|
||||
targetType: 'STOP',
|
||||
|
||||
78
tests/unit/GuideStopInfoAdapter.spec.ts
Normal file
78
tests/unit/GuideStopInfoAdapter.spec.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { toGuideStopInfo } from '@/data/adapters/guideStopInfoAdapter'
|
||||
|
||||
describe('guide stop-info adapter', () => {
|
||||
it('normalizes aggregated language variants and exposes only displayable languages', () => {
|
||||
const result = toGuideStopInfo({
|
||||
available: true,
|
||||
targetType: 'STOP',
|
||||
targetId: '9001',
|
||||
lang: 'zh-CN',
|
||||
imageStatus: 'READY',
|
||||
coverImageUrl: '/museum-assets/cover.webp',
|
||||
languageVariants: [
|
||||
{
|
||||
lang: 'zh',
|
||||
enabled: true,
|
||||
playable: true,
|
||||
audioStatus: 'READY',
|
||||
playUrl: '/museum-assets/audio/zh.mp3',
|
||||
duration: '50',
|
||||
audioId: 12,
|
||||
hasText: true,
|
||||
textAvailable: true,
|
||||
text: '普通话讲解词',
|
||||
textLength: '6',
|
||||
textHash: 'zh-hash'
|
||||
},
|
||||
{
|
||||
lang: 'yue-HK',
|
||||
enabled: true,
|
||||
playable: false,
|
||||
audioStatus: 'MISSING',
|
||||
textAvailable: true,
|
||||
text: '粤语使用普通话文案',
|
||||
reason: 'NO_PUBLISHED_AUDIO'
|
||||
},
|
||||
{
|
||||
lang: 'en-US',
|
||||
enabled: false,
|
||||
playable: true,
|
||||
playUrl: '/museum-assets/audio/en.mp3'
|
||||
}
|
||||
]
|
||||
}, {
|
||||
targetType: 'STOP',
|
||||
targetId: '9001',
|
||||
lang: 'zh-CN'
|
||||
})
|
||||
|
||||
expect(result.supportedLanguages).toEqual(['zh-CN', 'yue-HK'])
|
||||
expect(result.languageVariants['zh-CN']).toMatchObject({
|
||||
playable: true,
|
||||
playUrl: '/museum-assets/audio/zh.mp3',
|
||||
duration: 50,
|
||||
audioId: '12',
|
||||
text: '普通话讲解词',
|
||||
textLength: 6
|
||||
})
|
||||
expect(result.languageVariants['yue-HK']).toMatchObject({
|
||||
playable: false,
|
||||
textAvailable: true,
|
||||
reason: 'NO_PUBLISHED_AUDIO'
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps supportedLanguages as the compatibility fallback when variants are absent', () => {
|
||||
const result = toGuideStopInfo({
|
||||
supportedLanguages: ['zh-CN', 'en-US']
|
||||
}, {
|
||||
targetType: 'STOP',
|
||||
targetId: '9001',
|
||||
lang: 'zh-CN'
|
||||
})
|
||||
|
||||
expect(result.supportedLanguages).toEqual(['zh-CN', 'en-US'])
|
||||
expect(result.languageVariants).toEqual({})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user