import { describe, it, expect, vi, beforeEach } from 'vitest'; import { defineComponent } from 'vue'; import { mount as _mount } from '@vue/test-utils'; import { setupPinia } from '@tests/utils'; import { CreateTranslation } from '@/stores/class/transcript/transcriptParams'; import { useTranslation } from '@/components/composable/useTranslation'; import type { Emission } from '@/stores/class/general/emission'; vi.mock('@/api/transcriptionApi', () => ({ transcriptionApi: { getTranslation: vi.fn().mockResolvedValue('srt content'), getTranslations: vi.fn(), }, })); vi.mock('@/api/podcastApi', () => ({ podcastApi: { get: vi.fn(), }, })); vi.mock('@/api/organisationApi', () => ({ organisationApi: { getAttributes: vi.fn() } })); vi.mock('@/helper/language', () => ({ getLanguage: vi.fn().mockReturnValue('fr'), })); import { transcriptionApi, type PodcastTranslationData } from '@/api/transcriptionApi'; import { podcastApi } from '@/api/podcastApi'; import { organisationApi } from '@/api/organisationApi'; import { getLanguage } from '@/helper/language'; function makeTranslationData(overrides: Partial = {}): PodcastTranslationData { return { podcastId: 1, nativeLanguage: 'fr', translations: [], ...overrides }; } function makeEmission(): Emission { return { emissionId: 0, name: '', description: '', orga: { id: 'test-org', imageUrl: '', name: 'Test Org', }, beneficiaries: [], rubriqueIds: [], monetisable: 'UNDEFINED', seasonMode: 'NO_SEASON' as never, }; } function setOrgTranslationConfig(config: object) { vi.mocked(podcastApi.get).mockResolvedValue({ emission: makeEmission(), organisation: { id: 1 } } as never); setOrgAttributes(config); } function setOrgAttributes(orgConfig: object) { vi.mocked(organisationApi.getAttributes).mockResolvedValue({ 'translation-config': JSON.stringify(orgConfig) }); } describe('useTranslation', () => { let composable: ReturnType; beforeEach(() => { vi.clearAllMocks(); vi.mocked(transcriptionApi.getTranslation).mockResolvedValue('srt content'); vi.mocked(podcastApi.get).mockResolvedValue({ emission: makeEmission() } as never); vi.mocked(getLanguage).mockReturnValue('fr'); let result!: ReturnType; const pinia = setupPinia(); _mount(defineComponent({ setup() { result = useTranslation(); return {}; }, template: '
', }), { global: { plugins: [pinia] } }); composable = result; }); describe('convertSrtToPlainText', () => { it('converts a single SRT block to plain text', () => { const srt = '1\n00:00:01,000 --> 00:00:02,000\nHello world\n\n'; expect(composable.convertSrtToPlainText(srt)).toBe('Hello world '); }); it('joins multiple SRT blocks', () => { const srt = '1\n00:00:01,000 --> 00:00:02,000\nHello\n\n2\n00:00:03,000 --> 00:00:04,000\nworld\n\n'; expect(composable.convertSrtToPlainText(srt)).toBe('Hello world '); }); it('returns an empty string for empty input', () => { expect(composable.convertSrtToPlainText('')).toBe(''); }); }); describe('getMostRelevantLanguage', () => { it('bases its results on the language alternatives in the browser', async () => { vi.mocked(getLanguage).mockReturnValue('de'); Object.defineProperty(navigator, 'languages', { value: ['fr', 'it', 'de'], configurable: true }); const translationData = { nativeLanguage: 'it', podcastId: 0, translations: [] }; setOrgTranslationConfig({ createTranslation: { en: CreateTranslation.ALWAYS }, otherLanguage: CreateTranslation.NEVER, }); const result = await composable.getMostRelevantLanguage(translationData) expect(result.ready).toBe('it'); }); it('takes into consideration the "base" languages of variants', async () => { vi.mocked(getLanguage).mockReturnValue('fr-CH'); Object.defineProperty(navigator, 'languages', { value: ['fr-CH', 'it', 'de'], configurable: true }); const translationData = { nativeLanguage: 'fr', podcastId: 0, translations: [] }; setOrgTranslationConfig({ createTranslation: { en: CreateTranslation.ALWAYS }, otherLanguage: CreateTranslation.NEVER, }); const result = await composable.getMostRelevantLanguage(translationData) expect(result.ready).toBe('fr'); }); }); describe('getMostRelevantTranslation', () => { it('uses the native language when it matches the browser language', async () => { await composable.getMostRelevantTranslation(makeTranslationData({ nativeLanguage: 'fr' })); expect(transcriptionApi.getTranslation).toHaveBeenCalledWith(1, 'fr'); expect(podcastApi.get).not.toHaveBeenCalled(); }); it('uses the browser language when it is available in the org config', async () => { vi.mocked(getLanguage).mockReturnValue('en'); setOrgTranslationConfig({ createTranslation: { en: CreateTranslation.ALWAYS }, otherLanguage: CreateTranslation.NEVER, }); await composable.getMostRelevantTranslation(makeTranslationData({ nativeLanguage: 'fr' })); expect(transcriptionApi.getTranslation).toHaveBeenCalledWith(1, 'en'); }); it('falls back to native language when English is ON_DEMAND but not yet generated', async () => { vi.mocked(getLanguage).mockReturnValue('de'); setOrgTranslationConfig({ createTranslation: { en: CreateTranslation.ON_DEMAND }, otherLanguage: CreateTranslation.NEVER, }); await composable.getMostRelevantTranslation(makeTranslationData({ nativeLanguage: 'fr' })); expect(transcriptionApi.getTranslation).toHaveBeenCalledWith(1, 'fr'); }); it('falls back to native language when neither browser language nor English is available', async () => { vi.mocked(getLanguage).mockReturnValue('de'); setOrgTranslationConfig({ createTranslation: {}, otherLanguage: CreateTranslation.NEVER, }); await composable.getMostRelevantTranslation(makeTranslationData({ nativeLanguage: 'fr' })); expect(transcriptionApi.getTranslation).toHaveBeenCalledWith(1, 'fr'); }); it('falls back to native language when browser language is ON_DEMAND but not yet generated', async () => { vi.mocked(getLanguage).mockReturnValue('es'); setOrgTranslationConfig({ createTranslation: { es: CreateTranslation.ON_DEMAND }, otherLanguage: CreateTranslation.NEVER, }); await composable.getMostRelevantTranslation(makeTranslationData({ nativeLanguage: 'fr' })); expect(transcriptionApi.getTranslation).toHaveBeenCalledWith(1, 'fr'); }); it('fetches translation data first when called with a podcast ID', async () => { vi.mocked(transcriptionApi.getTranslations).mockResolvedValue(makeTranslationData()); await composable.getMostRelevantTranslation(1); expect(transcriptionApi.getTranslations).toHaveBeenCalledWith(1); expect(transcriptionApi.getTranslation).toHaveBeenCalledWith(1, 'fr'); }); }); });