mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 10:06:15 -08:00
Add typed EPG runtime bridge, split EPG runtime capabilities, migrate renderer EPG callers away from direct window.electron access, and address Greptile review feedback.
240 lines
8.0 KiB
TypeScript
240 lines
8.0 KiB
TypeScript
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
|
import { Router } from '@angular/router';
|
|
import { Actions } from '@ngrx/effects';
|
|
import { MockStore, provideMockStore } from '@ngrx/store/testing';
|
|
import { TranslateService } from '@ngx-translate/core';
|
|
import { EpgRuntimeBridgeService, EpgService } from '@iptvnator/epg/data-access';
|
|
import { WORKSPACE_SHELL_ACTIONS } from '@iptvnator/workspace/shell/util';
|
|
import { MockProvider } from 'ng-mocks';
|
|
import { EMPTY, of } from 'rxjs';
|
|
import { DataService, RuntimeCapabilitiesService } from '@iptvnator/services';
|
|
import {
|
|
Language,
|
|
Settings,
|
|
StartupBehavior,
|
|
STORE_KEY,
|
|
StreamFormat,
|
|
Theme,
|
|
VideoPlayer,
|
|
} from '@iptvnator/shared/interfaces';
|
|
import { PlaylistActions } from '@iptvnator/m3u-state';
|
|
import { AppComponent } from './app.component';
|
|
import { ElectronServiceStub } from './services/electron.service.stub';
|
|
import { SettingsService } from './services/settings.service';
|
|
|
|
jest.spyOn(global.console, 'error').mockImplementation(() => {
|
|
// suppress console.error output during tests
|
|
});
|
|
|
|
class MockSettingsService {
|
|
getValueFromLocalStorage = jest.fn().mockReturnValue(of(undefined));
|
|
changeTheme = jest.fn();
|
|
}
|
|
|
|
const DEFAULT_SETTINGS: Settings = {
|
|
player: VideoPlayer.VideoJs,
|
|
epgUrl: [],
|
|
streamFormat: StreamFormat.M3u8StreamFormat,
|
|
openStreamOnDoubleClick: false,
|
|
language: Language.ENGLISH,
|
|
showCaptions: false,
|
|
showDashboard: true,
|
|
startupBehavior: StartupBehavior.FirstView,
|
|
showExternalPlaybackBar: true,
|
|
theme: Theme.SystemTheme,
|
|
mpvPlayerPath: '',
|
|
mpvPlayerArguments: '',
|
|
mpvReuseInstance: false,
|
|
vlcPlayerPath: '',
|
|
vlcPlayerArguments: '',
|
|
vlcReuseInstance: false,
|
|
remoteControl: false,
|
|
remoteControlPort: 8765,
|
|
downloadFolder: '',
|
|
recordingFolder: '',
|
|
};
|
|
|
|
describe('AppComponent', () => {
|
|
let component: AppComponent;
|
|
let fixture: ComponentFixture<AppComponent>;
|
|
let epgService: EpgService;
|
|
let router: Router;
|
|
let settingsService: MockSettingsService;
|
|
let snackBar: MatSnackBar;
|
|
let store: MockStore;
|
|
let translateService: TranslateService;
|
|
let runtimeCapabilities: Partial<RuntimeCapabilitiesService>;
|
|
let epgBridge: Partial<EpgRuntimeBridgeService>;
|
|
|
|
beforeEach(waitForAsync(() => {
|
|
runtimeCapabilities = {
|
|
isElectron: true,
|
|
isMacOS: false,
|
|
};
|
|
epgBridge = {
|
|
checkFreshness: jest.fn().mockResolvedValue({
|
|
freshUrls: [],
|
|
staleUrls: [],
|
|
}),
|
|
supportsImport: true,
|
|
supportsSourceFreshness: true,
|
|
};
|
|
|
|
TestBed.configureTestingModule({
|
|
imports: [AppComponent],
|
|
providers: [
|
|
provideMockStore(),
|
|
{
|
|
provide: Actions,
|
|
useValue: new Actions(EMPTY),
|
|
},
|
|
{
|
|
provide: DataService,
|
|
useClass: ElectronServiceStub,
|
|
},
|
|
{
|
|
provide: SettingsService,
|
|
useClass: MockSettingsService,
|
|
},
|
|
{
|
|
provide: RuntimeCapabilitiesService,
|
|
useValue: runtimeCapabilities as RuntimeCapabilitiesService,
|
|
},
|
|
MockProvider(EpgService, {
|
|
fetchEpg: jest.fn(),
|
|
}),
|
|
{
|
|
provide: EpgRuntimeBridgeService,
|
|
useValue: epgBridge,
|
|
},
|
|
MockProvider(Router, {
|
|
navigateByUrl: jest.fn(),
|
|
}),
|
|
MockProvider(MatSnackBar, {
|
|
open: jest.fn(),
|
|
}),
|
|
MockProvider(TranslateService, {
|
|
instant: jest.fn((key: string) => key),
|
|
setDefaultLang: jest.fn(),
|
|
use: jest.fn(),
|
|
}),
|
|
{
|
|
provide: WORKSPACE_SHELL_ACTIONS,
|
|
useValue: {
|
|
openAddPlaylistDialog: jest.fn(),
|
|
openGlobalRecent: jest.fn(),
|
|
openGlobalSearch: jest.fn(),
|
|
openAccountInfo: jest.fn(),
|
|
},
|
|
},
|
|
],
|
|
})
|
|
.overrideComponent(AppComponent, {
|
|
set: {
|
|
template: '',
|
|
},
|
|
})
|
|
.compileComponents();
|
|
}));
|
|
|
|
beforeEach(() => {
|
|
fixture = TestBed.createComponent(AppComponent);
|
|
epgService = TestBed.inject(EpgService);
|
|
router = TestBed.inject(Router);
|
|
settingsService = TestBed.inject(
|
|
SettingsService
|
|
) as unknown as MockSettingsService;
|
|
snackBar = TestBed.inject(MatSnackBar);
|
|
store = TestBed.inject(MockStore);
|
|
translateService = TestBed.inject(TranslateService);
|
|
component = fixture.componentInstance;
|
|
});
|
|
|
|
it('should create the component', () => {
|
|
expect(component).toBeTruthy();
|
|
});
|
|
|
|
it('should init component', () => {
|
|
const storeDispatchSpy = jest.spyOn(store, 'dispatch');
|
|
jest.spyOn(translateService, 'setDefaultLang');
|
|
jest.spyOn(component, 'initSettings');
|
|
|
|
component.ngOnInit();
|
|
expect(storeDispatchSpy).toHaveBeenCalledWith(
|
|
PlaylistActions.loadPlaylists()
|
|
);
|
|
expect(translateService.setDefaultLang).toHaveBeenCalledWith(
|
|
Language.ENGLISH
|
|
);
|
|
expect(component.initSettings).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('should navigate to the provided route', () => {
|
|
const route = '/add-playlists';
|
|
jest.spyOn(router, 'navigateByUrl');
|
|
|
|
component.navigateToRoute(route);
|
|
|
|
expect(router.navigateByUrl).toHaveBeenCalledWith(route);
|
|
});
|
|
|
|
it('should apply system theme when no settings are stored', () => {
|
|
jest.spyOn(settingsService, 'changeTheme');
|
|
|
|
component.initSettings();
|
|
|
|
expect(settingsService.getValueFromLocalStorage).toHaveBeenCalledWith(
|
|
STORE_KEY.Settings
|
|
);
|
|
expect(settingsService.changeTheme).toHaveBeenCalledWith(
|
|
Theme.SystemTheme
|
|
);
|
|
});
|
|
|
|
it('should apply saved settings and fetch stale epg data only', async () => {
|
|
const settings: Settings = {
|
|
...DEFAULT_SETTINGS,
|
|
epgUrl: ['https://example.com/epg.xml'],
|
|
language: Language.SPANISH,
|
|
theme: Theme.DarkTheme,
|
|
};
|
|
epgBridge.checkFreshness = jest.fn().mockResolvedValue({
|
|
freshUrls: [],
|
|
staleUrls: settings.epgUrl,
|
|
});
|
|
settingsService.getValueFromLocalStorage.mockReturnValue(of(settings));
|
|
jest.spyOn(settingsService, 'changeTheme');
|
|
jest.spyOn(translateService, 'use');
|
|
|
|
component.initSettings();
|
|
await fixture.whenStable();
|
|
|
|
expect(translateService.use).toHaveBeenCalledWith(Language.SPANISH);
|
|
expect(settingsService.changeTheme).toHaveBeenCalledWith(
|
|
Theme.DarkTheme
|
|
);
|
|
expect(epgBridge.checkFreshness).toHaveBeenCalledWith(
|
|
settings.epgUrl,
|
|
12
|
|
);
|
|
expect(epgService.fetchEpg).toHaveBeenCalledWith(settings.epgUrl);
|
|
expect(snackBar.open).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('does not fetch EPG settings when the EPG bridge cannot import EPG', async () => {
|
|
const settings: Settings = {
|
|
...DEFAULT_SETTINGS,
|
|
epgUrl: ['https://example.com/epg.xml'],
|
|
};
|
|
epgBridge.supportsImport = false;
|
|
settingsService.getValueFromLocalStorage.mockReturnValue(of(settings));
|
|
|
|
component.initSettings();
|
|
await fixture.whenStable();
|
|
|
|
expect(epgBridge.checkFreshness).not.toHaveBeenCalled();
|
|
expect(epgService.fetchEpg).not.toHaveBeenCalled();
|
|
});
|
|
});
|