|
| 1 | +import { |
| 2 | + createComponent, |
| 3 | + createScope, |
| 4 | + provideContext, |
| 5 | + root, |
| 6 | + signal, |
| 7 | + type Scope, |
| 8 | +} from 'maverick.js'; |
| 9 | +import { vi } from 'vitest'; |
| 10 | + |
| 11 | +import { mediaContext, type MediaContext } from '../../core/api/media-context'; |
| 12 | +import type { Src } from '../../core/api/src-types'; |
| 13 | +import type { MediaProviderAdapter, MediaProviderLoader } from '../../providers/types'; |
| 14 | +import { MediaProvider } from './provider'; |
| 15 | + |
| 16 | +beforeEach(() => { |
| 17 | + const raf = vi.fn((callback: FrameRequestCallback) => { |
| 18 | + callback(0); |
| 19 | + return 1; |
| 20 | + }), |
| 21 | + caf = vi.fn(); |
| 22 | + |
| 23 | + vi.stubGlobal('requestAnimationFrame', raf); |
| 24 | + vi.stubGlobal('cancelAnimationFrame', caf); |
| 25 | + vi.stubGlobal( |
| 26 | + 'ResizeObserver', |
| 27 | + class ResizeObserver { |
| 28 | + observe = vi.fn(); |
| 29 | + disconnect = vi.fn(); |
| 30 | + }, |
| 31 | + ); |
| 32 | + |
| 33 | + window.requestAnimationFrame = raf; |
| 34 | + window.cancelAnimationFrame = caf; |
| 35 | +}); |
| 36 | + |
| 37 | +afterEach(() => { |
| 38 | + vi.unstubAllGlobals(); |
| 39 | +}); |
| 40 | + |
| 41 | +it('defers loading the provider target until connected', async () => { |
| 42 | + const src: Src = { src: 'https://example.com/audio.custom', type: 'application/custom' }, |
| 43 | + events: string[] = [], |
| 44 | + adapter = createAdapter(), |
| 45 | + loader = createLoader(adapter), |
| 46 | + media = createMediaContext(src, events); |
| 47 | + |
| 48 | + adapter.setup.mockImplementation(() => { |
| 49 | + media.notify('provider-setup', adapter); |
| 50 | + }); |
| 51 | + |
| 52 | + let dispose!: () => void; |
| 53 | + const host = document.createElement('div'), |
| 54 | + target = document.createElement('audio'), |
| 55 | + provider = root((disposer) => { |
| 56 | + dispose = disposer; |
| 57 | + provideContext(mediaContext, media); |
| 58 | + return createComponent(MediaProvider, { props: { loaders: [loader] } }); |
| 59 | + }); |
| 60 | + |
| 61 | + provider.$$.setup(); |
| 62 | + provider.$$.attach(host); |
| 63 | + provider.load(target); |
| 64 | + |
| 65 | + expect(target.getAttribute('aria-hidden')).to.equal('true'); |
| 66 | + expect(loader.load).not.toHaveBeenCalled(); |
| 67 | + expect(events).not.toContain('provider-change'); |
| 68 | + |
| 69 | + provider.$$.connect(); |
| 70 | + |
| 71 | + await vi.waitFor(() => { |
| 72 | + expect(loader.load).toHaveBeenCalledOnce(); |
| 73 | + expect(adapter.setup).toHaveBeenCalledOnce(); |
| 74 | + expect(adapter.loadSource).toHaveBeenCalledWith(src, 'metadata'); |
| 75 | + }); |
| 76 | + |
| 77 | + expect(events).toContain('provider-setup'); |
| 78 | + |
| 79 | + dispose(); |
| 80 | + adapter.scope.dispose(); |
| 81 | +}); |
| 82 | + |
| 83 | +function createAdapter(): MediaProviderAdapter & { |
| 84 | + scope: Scope; |
| 85 | + setup: ReturnType<typeof vi.fn>; |
| 86 | + loadSource: ReturnType<typeof vi.fn>; |
| 87 | +} { |
| 88 | + let currentSrc: Src | null = null; |
| 89 | + |
| 90 | + const adapter = { |
| 91 | + scope: createScope(), |
| 92 | + type: 'audio', |
| 93 | + get currentSrc() { |
| 94 | + return currentSrc; |
| 95 | + }, |
| 96 | + setup: vi.fn(), |
| 97 | + destroy: vi.fn(), |
| 98 | + play: vi.fn(() => Promise.resolve()), |
| 99 | + pause: vi.fn(), |
| 100 | + setMuted: vi.fn(), |
| 101 | + setCurrentTime: vi.fn(), |
| 102 | + setVolume: vi.fn(), |
| 103 | + loadSource: vi.fn((src: Src) => { |
| 104 | + currentSrc = src; |
| 105 | + return Promise.resolve(); |
| 106 | + }), |
| 107 | + } as unknown as MediaProviderAdapter & { |
| 108 | + scope: Scope; |
| 109 | + setup: ReturnType<typeof vi.fn>; |
| 110 | + loadSource: ReturnType<typeof vi.fn>; |
| 111 | + }; |
| 112 | + |
| 113 | + return adapter; |
| 114 | +} |
| 115 | + |
| 116 | +function createLoader(adapter: MediaProviderAdapter): MediaProviderLoader { |
| 117 | + return { |
| 118 | + name: 'custom-audio', |
| 119 | + target: null, |
| 120 | + canPlay: vi.fn((src: Src) => src.type === 'application/custom'), |
| 121 | + mediaType: vi.fn(() => 'audio'), |
| 122 | + preconnect: vi.fn(), |
| 123 | + load: vi.fn(async () => adapter), |
| 124 | + }; |
| 125 | +} |
| 126 | + |
| 127 | +function createMediaContext(src: Src, events: string[]): MediaContext { |
| 128 | + const state = { |
| 129 | + canLoad: signal(true), |
| 130 | + canLoadPoster: signal(false), |
| 131 | + crossOrigin: signal(null), |
| 132 | + currentTime: signal(0), |
| 133 | + inferredViewType: signal('unknown'), |
| 134 | + mediaType: signal('unknown'), |
| 135 | + paused: signal(true), |
| 136 | + poster: signal(''), |
| 137 | + preload: signal('metadata'), |
| 138 | + providedPoster: signal(false), |
| 139 | + quality: signal(null), |
| 140 | + remotePlaybackLoader: signal(null), |
| 141 | + savedState: signal(null), |
| 142 | + source: signal({ src: '', type: '' } as Src), |
| 143 | + sources: signal([] as Src[]), |
| 144 | + started: signal(false), |
| 145 | + }; |
| 146 | + |
| 147 | + const media = { |
| 148 | + $provider: signal(null), |
| 149 | + $providerSetup: signal(false), |
| 150 | + $props: { |
| 151 | + preferNativeHLS: signal(false), |
| 152 | + src: signal(src), |
| 153 | + }, |
| 154 | + $state: state, |
| 155 | + audioTracks: [], |
| 156 | + notify(type: string, detail: any) { |
| 157 | + events.push(type); |
| 158 | + |
| 159 | + switch (type) { |
| 160 | + case 'provider-change': |
| 161 | + media.$provider.set(detail); |
| 162 | + break; |
| 163 | + case 'sources-change': |
| 164 | + state.sources.set(detail); |
| 165 | + break; |
| 166 | + case 'source-change': |
| 167 | + state.source.set(detail); |
| 168 | + break; |
| 169 | + case 'media-type-change': |
| 170 | + state.mediaType.set(detail); |
| 171 | + break; |
| 172 | + } |
| 173 | + }, |
| 174 | + player: null, |
| 175 | + qualities: [], |
| 176 | + storage: null, |
| 177 | + textTracks: { |
| 178 | + add: vi.fn(), |
| 179 | + getById: vi.fn(() => null), |
| 180 | + remove: vi.fn(), |
| 181 | + }, |
| 182 | + } as unknown as MediaContext; |
| 183 | + |
| 184 | + return media; |
| 185 | +} |
0 commit comments