import { Component, input, output, signal } from '@angular/core';
import {
ComponentFixture,
DeferBlockBehavior,
TestBed,
} from '@angular/core/testing';
import { StorageMap } from '@ngx-pwa/local-storage';
import { TranslateModule } from '@ngx-translate/core';
import { RuntimeCapabilitiesService, SettingsStore } from '@iptvnator/services';
import { VideoPlayer } from '@iptvnator/shared/interfaces';
import { EMPTY } from 'rxjs';
import type { WebPlayerViewComponent as WebPlayerViewComponentInstance } from './web-player-view.component';
jest.unstable_mockModule('video.js', () => ({ default: jest.fn() }));
jest.unstable_mockModule('@yangkghjh/videojs-aspect-ratio-panel', () => ({}));
jest.unstable_mockModule('videojs-contrib-quality-levels', () => ({}));
jest.unstable_mockModule('videojs-quality-selector-hls', () => ({}));
@Component({
selector: 'app-vjs-player',
template: '
',
})
class StubVjsPlayerComponent {
readonly options = input.required();
readonly volume = input(1);
readonly timeUpdate = output();
}
@Component({
selector: 'app-html-video-player',
template: '',
})
class StubHtmlVideoPlayerComponent {
readonly channel = input.required();
readonly volume = input(1);
readonly timeUpdate = output();
}
/**
* The engine is part of the application token, so choosing the wrong one
* before the settings arrive mounts a second application and swaps the player
* under the user mid-playback. The persisted settings read is asynchronous;
* `SettingsStore` holds the same value synchronously.
*/
describe('WebPlayerViewComponent player fallback', () => {
let WebPlayerViewComponent: typeof WebPlayerViewComponentInstance;
let fixture: ComponentFixture;
const storedPlayer = signal(VideoPlayer.Html5Player);
beforeAll(async () => {
({ WebPlayerViewComponent } =
await import('./web-player-view.component'));
});
const create = async (playerOverride: VideoPlayer | null = null) => {
await TestBed.configureTestingModule({
deferBlockBehavior: DeferBlockBehavior.Playthrough,
imports: [WebPlayerViewComponent, TranslateModule.forRoot()],
providers: [
// Never emits: stands in for the IndexedDB read still pending.
{ provide: StorageMap, useValue: { get: () => EMPTY } },
{
provide: SettingsStore,
useValue: {
player: storedPlayer,
showCaptions: signal(false),
},
},
{
provide: RuntimeCapabilitiesService,
useValue: { supportsManagedExternalPlayers: false },
},
],
})
.overrideComponent(WebPlayerViewComponent, {
set: {
imports: [
StubVjsPlayerComponent,
StubHtmlVideoPlayerComponent,
],
},
})
.compileComponents();
fixture = TestBed.createComponent(WebPlayerViewComponent);
fixture.componentRef.setInput('playbackSessionKey', 'live:p1:c1');
fixture.componentRef.setInput('streamUrl', 'http://host/movie.mkv');
if (playerOverride !== null) {
fixture.componentRef.setInput('playerOverride', playerOverride);
}
fixture.detectChanges();
};
afterEach(() => {
fixture?.destroy();
TestBed.resetTestingModule();
});
it('uses the store while the persisted settings read is pending', async () => {
storedPlayer.set(VideoPlayer.Html5Player);
await create();
expect(fixture.componentInstance.selectedPlayer()).toBe(
VideoPlayer.Html5Player
);
});
it('still lets a host override win', async () => {
storedPlayer.set(VideoPlayer.Html5Player);
await create(VideoPlayer.ArtPlayer);
expect(fixture.componentInstance.selectedPlayer()).toBe(
VideoPlayer.ArtPlayer
);
});
it('falls back to Video.js when the store has no player either', async () => {
storedPlayer.set(undefined as unknown as VideoPlayer);
await create();
expect(fixture.componentInstance.selectedPlayer()).toBe(
VideoPlayer.VideoJs
);
});
});