import { describe, it, expect, beforeEach, vi } from 'vitest'
import { detectBrowserProfile, CODEC_PROBES } from './browserProfile'
describe('detectBrowserProfile', () => {
let mockCanPlayType
beforeEach(() => {
mockCanPlayType = vi.fn()
vi.stubGlobal(
'Audio',
class {
canPlayType = mockCanPlayType
},
)
})
it('includes codecs that return "probably"', () => {
mockCanPlayType.mockImplementation((mime) => {
if (mime === 'audio/mpeg; codecs="mp3"') return 'probably'
if (mime === 'audio/ogg; codecs="opus"') return 'probably'
return ''
})
const profile = detectBrowserProfile()
expect(profile.name).toBe('NavidromeUI')
expect(profile.directPlayProfiles.length).toBe(2)
const codecs = profile.directPlayProfiles.flatMap((p) => p.audioCodecs)
expect(codecs).toContain('mp3')
expect(codecs).toContain('opus')
})
it('includes codecs that return "maybe"', () => {
mockCanPlayType.mockImplementation((mime) => {
if (mime === 'audio/flac') return 'maybe'
return ''
})
const profile = detectBrowserProfile()
const codecs = profile.directPlayProfiles.flatMap((p) => p.audioCodecs)
expect(codecs).toContain('flac')
})
it('excludes codecs that return empty string', () => {
mockCanPlayType.mockReturnValue('')
const profile = detectBrowserProfile()
expect(profile.directPlayProfiles).toEqual([])
})
it('sets protocol to "http" for all direct play profiles', () => {
mockCanPlayType.mockReturnValue('probably')
const profile = detectBrowserProfile()
profile.directPlayProfiles.forEach((p) => {
expect(p.protocols).toEqual(['http'])
})
})
it('filters transcoding profiles by canPlayType', () => {
mockCanPlayType.mockImplementation((mime) => {
if (mime === 'audio/mpeg; codecs="mp3"') return 'probably'
if (mime === 'audio/ogg; codecs="opus"') return 'probably'
return ''
})
const profile = detectBrowserProfile()
const codecs = profile.transcodingProfiles.map((p) => p.audioCodec)
expect(codecs).toEqual(['opus', 'mp3'])
expect(codecs).not.toContain('flac')
profile.transcodingProfiles.forEach((p) => {
expect(p.protocol).toBe('http')
})
})
it('always includes mp3 fallback in transcoding profiles', () => {
mockCanPlayType.mockReturnValue('')
const profile = detectBrowserProfile()
expect(profile.transcodingProfiles.length).toBe(1)
expect(profile.transcodingProfiles[0].audioCodec).toBe('mp3')
expect(profile.transcodingProfiles[0].protocol).toBe('http')
})
it('does not duplicate mp3 when canPlayType supports it', () => {
mockCanPlayType.mockReturnValue('probably')
const profile = detectBrowserProfile()
const mp3Count = profile.transcodingProfiles.filter(
(p) => p.audioCodec === 'mp3',
).length
expect(mp3Count).toBe(1)
})
it('preserves transcoding profile preference order', () => {
mockCanPlayType.mockReturnValue('probably')
const profile = detectBrowserProfile()
const codecs = profile.transcodingProfiles.map((p) => p.audioCodec)
expect(codecs).toEqual(['flac', 'opus', 'mp3'])
})
it('sets codecProfiles to empty array', () => {
mockCanPlayType.mockReturnValue('probably')
const profile = detectBrowserProfile()
expect(profile.codecProfiles).toEqual([])
})
it('matches codec when any mime variant returns "probably"', () => {
mockCanPlayType.mockImplementation((mime) => {
if (mime === 'audio/flac; codecs="flac"') return 'probably'
return ''
})
const profile = detectBrowserProfile()
const codecs = profile.directPlayProfiles.flatMap((p) => p.audioCodecs)
expect(codecs).toContain('flac')
})
it('includes platform info', () => {
const profile = detectBrowserProfile()
expect(typeof profile.platform).toBe('string')
})
describe('Safari restrictions', () => {
beforeEach(() => {
mockCanPlayType.mockReturnValue('probably')
})
it('still includes ogg in direct play profiles on Safari', () => {
vi.stubGlobal('navigator', {
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.2 Safari/605.1.15',
})
const profile = detectBrowserProfile()
const containers = profile.directPlayProfiles.flatMap((p) => p.containers)
expect(containers).toContain('ogg')
})
it('limits Safari transcoding to mp3 only', () => {
vi.stubGlobal('navigator', {
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.2 Safari/605.1.15',
})
const profile = detectBrowserProfile()
const codecs = profile.transcodingProfiles.map((p) => p.audioCodec)
expect(codecs).toEqual(['mp3'])
})
it('does NOT restrict transcoding on Chrome', () => {
vi.stubGlobal('navigator', {
userAgent:
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
})
const profile = detectBrowserProfile()
const codecs = profile.transcodingProfiles.map((p) => p.audioCodec)
expect(codecs).toContain('opus')
expect(codecs).toContain('flac')
})
it('applies same restrictions on iOS Safari', () => {
vi.stubGlobal('navigator', {
userAgent:
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
})
const profile = detectBrowserProfile()
const codecs = profile.transcodingProfiles.map((p) => p.audioCodec)
expect(codecs).toEqual(['mp3'])
})
})
})