Add signed node-scoped demo catalogs and retained app media sessions
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { defineComponent, nextTick, ref, shallowRef } from 'vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
const fixture = vi.hoisted(() => ({
|
||||
allowed: true,
|
||||
player: { updateExternal: vi.fn(), detachExternal: vi.fn(), releaseExternal: vi.fn(), setExternalVisible: vi.fn() },
|
||||
launcher: { mediaAppId: 'node-demo-v4v' as string | null, panelAppId: null as string | null, openSession: vi.fn() },
|
||||
}))
|
||||
vi.mock('../useAudioPlayer', () => ({ useAudioPlayer: () => fixture.player }))
|
||||
vi.mock('@/stores/appLauncher', () => ({ useAppLauncherStore: () => fixture.launcher }))
|
||||
vi.mock('@/views/discover/curatedApps', () => ({ appHasMediaBridge: () => fixture.allowed }))
|
||||
import { useAppMediaBridge } from '../useAppMediaBridge'
|
||||
|
||||
describe('app media session boundary', () => {
|
||||
beforeEach(() => { vi.clearAllMocks(); fixture.allowed = true; fixture.launcher.mediaAppId = 'node-demo-v4v'; fixture.launcher.panelAppId = null })
|
||||
it('checks frame, origin, nonce and finite state before attaching controls', async () => {
|
||||
const child = { postMessage: vi.fn() }
|
||||
const visible = ref(false)
|
||||
let bridge!: ReturnType<typeof useAppMediaBridge>
|
||||
const wrapper = mount(defineComponent({ setup() {
|
||||
bridge = useAppMediaBridge(ref('node-demo-v4v'), ref('https://node.test:7475/'), shallowRef({ contentWindow: child } as unknown as HTMLIFrameElement), visible)
|
||||
return () => null
|
||||
} }))
|
||||
bridge.connect()
|
||||
const hello = child.postMessage.mock.calls[0]![0]
|
||||
expect(child.postMessage.mock.calls[0]![1]).toBe('https://node.test:7475')
|
||||
const state = { type: 'archipelago:media-state', version: 1, session: hello.session, available: true, title: 'Song', artist: 'Artist', playing: true, position: 22, duration: 100 }
|
||||
const message = (data = state, origin = 'https://node.test:7475', source: unknown = child) => bridge.handle({ data, origin, source } as MessageEvent)
|
||||
message(state, 'http://node.test:7475'); message(state, 'https://evil.test'); message(state, 'https://node.test:7475', {})
|
||||
message({ ...state, session: 'wrong' }); message({ ...state, duration: NaN })
|
||||
expect(fixture.player.updateExternal).not.toHaveBeenCalled()
|
||||
message()
|
||||
expect(fixture.player.updateExternal).toHaveBeenCalledOnce()
|
||||
const [controller, snapshot, shown] = fixture.player.updateExternal.mock.calls[0]!
|
||||
expect(snapshot.position).toBe(22); expect(shown).toBe(false)
|
||||
controller.seek(30)
|
||||
expect(child.postMessage.mock.calls.at(-1)![0]).toMatchObject({ command: 'seek', position: 30, session: hello.session })
|
||||
controller.open(); expect(fixture.launcher.openSession).toHaveBeenCalledWith('node-demo-v4v')
|
||||
visible.value = true; await nextTick()
|
||||
expect(fixture.player.setExternalVisible).toHaveBeenCalledWith(controller.id, true)
|
||||
fixture.allowed = false; message()
|
||||
expect(fixture.player.updateExternal).toHaveBeenCalledOnce()
|
||||
fixture.allowed = true
|
||||
wrapper.unmount()
|
||||
expect(fixture.player.releaseExternal).toHaveBeenCalledWith(controller.id)
|
||||
expect(child.postMessage.mock.calls.at(-1)![0]).toMatchObject({ command: 'pause' })
|
||||
})
|
||||
})
|
||||
@@ -61,6 +61,29 @@ describe('useAudioPlayer', () => {
|
||||
if (lastMockAudio) lastMockAudio.shouldRejectPlay = false
|
||||
})
|
||||
|
||||
it('controls an app player without copying its stream or losing its position', () => {
|
||||
const player = useAudioPlayer()
|
||||
player.play('/local.mp3', 'Local')
|
||||
const controller = { id: 'demo-session', play: vi.fn(), pause: vi.fn(), seek: vi.fn(), open: vi.fn(), close: vi.fn() }
|
||||
player.updateExternal(controller, { title: 'Demo', artist: 'Artist', playing: true, position: 42, duration: 100 }, true)
|
||||
expect(lastMockAudio?.paused).toBe(true)
|
||||
expect(player.currentSrc.value).toBe('app:demo-session')
|
||||
lastMockAudio?.simulateEvent('pause')
|
||||
expect(player.playing.value).toBe(true)
|
||||
expect(player.currentTime.value).toBe(42)
|
||||
player.setExternalVisible('demo-session', false)
|
||||
expect(player.externalVisible.value).toBe(false)
|
||||
player.pause(); player.seek(55); player.openExternal()
|
||||
expect(controller.pause).toHaveBeenCalledOnce()
|
||||
expect(controller.seek).toHaveBeenCalledWith(55)
|
||||
expect(controller.open).toHaveBeenCalledOnce()
|
||||
player.play('app:demo-session', 'Demo')
|
||||
expect(controller.play).toHaveBeenCalledOnce()
|
||||
player.stop()
|
||||
expect(controller.close).toHaveBeenCalledOnce()
|
||||
expect(player.currentName.value).toBe('')
|
||||
})
|
||||
|
||||
it('returns all expected properties', () => {
|
||||
const player = useAudioPlayer()
|
||||
expect(player.play).toBeTypeOf('function')
|
||||
|
||||
Reference in New Issue
Block a user