diff --git a/electron/main.ts b/electron/main.ts index 28287cbcd..c028f3df5 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -6,7 +6,7 @@ import { Api } from '../api'; import { AppMenu } from '../menu'; const contextMenu = require('electron-context-menu'); -let win: BrowserWindow = null; +let win: BrowserWindow | null = null; const args = process.argv.slice(1), serve = args.some((val) => val === '--serve'); @@ -79,11 +79,9 @@ function createEpgWorkerWindow() { }, }); + window.loadFile('./electron/epg-worker.html'); if (serve) { - window.loadFile(path.join(__dirname, '../electron/index.html')); window.webContents.openDevTools(); - } else { - window.loadFile('./epg-worker.html'); } window.once('ready-to-show', () => { diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 9034a3836..3ba345a52 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -19,7 +19,6 @@ import { WhatsNewService } from './services/whats-new.service'; import { WhatsNewServiceStub } from './services/whats-new.service.stub'; import { Theme } from './settings/theme.enum'; import { STORE_KEY } from './shared/enums/store-keys.enum'; -import { ChannelStore } from './state'; class MatSnackBarStub { open(): void {} @@ -117,28 +116,6 @@ describe('AppComponent', () => { } )); - it('should show a notification on epg error', inject( - [MatSnackBar], - (snackbar: MatSnackBar) => { - jest.spyOn(snackbar, 'open'); - component.onEpgError(); - expect(snackbar.open).toHaveBeenCalledTimes(1); - } - )); - - it('should handle epg download success', inject( - [MatSnackBar, ChannelStore], - (snackbar: MatSnackBar, channelStore: ChannelStore) => { - jest.spyOn(snackbar, 'open'); - jest.spyOn(channelStore, 'setEpgAvailableFlag'); - component.onEpgFetchDone(); - expect(snackbar.open).toHaveBeenCalledTimes(1); - expect(channelStore.setEpgAvailableFlag).toHaveBeenCalledWith( - true - ); - } - )); - it('show show whats new dialog', () => { jest.spyOn(whatsNewService, 'getModalsByVersion'); jest.spyOn(component, 'setDialogVisibility'); diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d20e334ec..6361eab00 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,5 +1,4 @@ import { Component, NgZone } from '@angular/core'; -import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { ModalWindow } from 'ngx-whats-new/lib/modal-window.interface'; @@ -7,7 +6,6 @@ import * as semver from 'semver'; import { IpcCommand } from '../../shared/ipc-command.class'; import { EPG_ERROR, - EPG_FETCH, EPG_FETCH_DONE, OPEN_FILE, SHOW_WHATS_NEW, @@ -15,13 +13,13 @@ import { VIEW_SETTINGS, } from '../../shared/ipc-commands'; import { DataService } from './services/data.service'; +import { EpgService } from './services/epg.service'; import { SettingsService } from './services/settings.service'; import { WhatsNewService } from './services/whats-new.service'; import { Language } from './settings/language.enum'; import { Settings } from './settings/settings.interface'; import { Theme } from './settings/theme.enum'; import { STORE_KEY } from './shared/enums/store-keys.enum'; -import { ChannelStore } from './state'; /** * AppComponent @@ -40,18 +38,12 @@ export class AppComponent { /** Modals to show for the updated version of the application */ modals: ModalWindow[] = []; - /** Default options for epg snackbar notifications */ - epgSnackBarOptions: MatSnackBarConfig = { - verticalPosition: 'bottom', - horizontalPosition: 'right', - }; - /** List of ipc commands with function mapping */ commandsList = [ new IpcCommand(VIEW_ADD_PLAYLIST, () => this.navigateToRoute('/')), new IpcCommand(VIEW_SETTINGS, () => this.navigateToRoute('/settings')), - new IpcCommand(EPG_FETCH_DONE, () => this.onEpgFetchDone()), - new IpcCommand(EPG_ERROR, () => this.onEpgError()), + new IpcCommand(EPG_FETCH_DONE, () => this.epgService.onEpgFetchDone()), + new IpcCommand(EPG_ERROR, () => this.epgService.onEpgError()), new IpcCommand(SHOW_WHATS_NEW, () => this.showWhatsNewDialog()), ]; @@ -62,13 +54,12 @@ export class AppComponent { * Creates an instance of AppComponent */ constructor( - private channelStore: ChannelStore, private electronService: DataService, + private epgService: EpgService, private ngZone: NgZone, private router: Router, private translate: TranslateService, private settingsService: SettingsService, - private snackBar: MatSnackBar, private whatsNewService: WhatsNewService ) { if ( @@ -127,14 +118,7 @@ export class AppComponent { if (settings && Object.keys(settings).length > 0) { this.translate.use(settings.language ?? this.DEFAULT_LANG); if (settings.epgUrl) { - this.electronService.sendIpcEvent(EPG_FETCH, { - url: settings.epgUrl, - }); - this.snackBar.open( - this.translate.instant('EPG.FETCH_EPG'), - this.translate.instant('CLOSE'), - this.epgSnackBarOptions - ); + this.epgService.fetchEpg(settings.epgUrl); } if (settings.theme) { @@ -205,31 +189,6 @@ export class AppComponent { this.router.navigateByUrl(route); } - /** - * Handles the event when the EPG fetching is done - */ - onEpgFetchDone(): void { - this.channelStore.setEpgAvailableFlag(true); - this.snackBar.open( - this.translate.instant('EPG.DOWNLOAD_SUCCESS'), - null, - { - ...this.epgSnackBarOptions, - duration: 2000, - } - ); - } - - /** - * Handles epg error - */ - onEpgError(): void { - this.snackBar.open(this.translate.instant('EPG.ERROR'), null, { - ...this.epgSnackBarOptions, - duration: 2000, - }); - } - /** * Shows the "what is new" dialog */ diff --git a/src/app/player/components/epg-list/epg-list.component.ts b/src/app/player/components/epg-list/epg-list.component.ts index 23c9aca01..bfa5ceb6c 100644 --- a/src/app/player/components/epg-list/epg-list.component.ts +++ b/src/app/player/components/epg-list/epg-list.component.ts @@ -1,12 +1,12 @@ import { Component, NgZone } from '@angular/core'; -import { EpgChannel } from '../../models/epg-channel.model'; -import { EpgProgram } from '../../models/epg-program.model'; import * as moment from 'moment'; -import { EPG_GET_PROGRAM_DONE } from '../../../../../shared/ipc-commands'; -import { ChannelQuery, ChannelStore } from '../../../state'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; +import { EPG_GET_PROGRAM_DONE } from '../../../../../shared/ipc-commands'; import { DataService } from '../../../services/data.service'; +import { ChannelQuery, ChannelStore } from '../../../state'; +import { EpgChannel } from '../../models/epg-channel.model'; +import { EpgProgram } from '../../models/epg-program.model'; export interface EpgData { channel: EpgChannel; @@ -76,7 +76,7 @@ export class EpgListComponent { this.timeshiftUntil$ = this.channelQuery .select( (store) => - store.active.tvg.rec || + store.active.tvg?.rec || store.active.timeshift || store.active.catchup?.days ) diff --git a/src/app/services/epg.service.spec.ts b/src/app/services/epg.service.spec.ts new file mode 100644 index 000000000..00c18beb3 --- /dev/null +++ b/src/app/services/epg.service.spec.ts @@ -0,0 +1,50 @@ +import { inject, TestBed } from '@angular/core/testing'; +import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { TranslateService } from '@ngx-translate/core'; +import { MockModule, MockProviders } from 'ng-mocks'; +import { ChannelStore } from '../state'; +import { DataService } from './data.service'; +import { EpgService } from './epg.service'; + +describe('EpgService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ + EpgService, + MockProviders(DataService, TranslateService, MatSnackBar), + ], + imports: [MockModule(MatSnackBarModule)], + }); + }); + + it('should create a service instance', inject( + [EpgService], + (service: EpgService) => { + expect(service).toBeTruthy(); + } + )); + + it('should show a notification on epg error', inject( + [MatSnackBar, EpgService], + (snackbar: MatSnackBar, service: EpgService) => { + jest.spyOn(snackbar, 'open'); + service.onEpgError(); + expect(snackbar.open).toHaveBeenCalledTimes(1); + } + )); + + it('should handle epg download success', inject( + [MatSnackBar, ChannelStore, EpgService], + ( + snackbar: MatSnackBar, + channelStore: ChannelStore, + service: EpgService + ) => { + jest.spyOn(snackbar, 'open'); + jest.spyOn(channelStore, 'setEpgAvailableFlag'); + service.onEpgFetchDone(); + expect(snackbar.open).toHaveBeenCalledTimes(1); + expect(channelStore.setEpgAvailableFlag).toHaveBeenCalledWith(true); + } + )); +}); diff --git a/src/app/services/epg.service.ts b/src/app/services/epg.service.ts new file mode 100644 index 000000000..9c89a5996 --- /dev/null +++ b/src/app/services/epg.service.ts @@ -0,0 +1,69 @@ +import { Injectable } from '@angular/core'; +import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar'; +import { TranslateService } from '@ngx-translate/core'; +import { EPG_FETCH } from '../../../shared/ipc-commands'; +import { ChannelStore } from '../state'; +import { DataService } from './data.service'; + +@Injectable({ + providedIn: 'root', +}) +export class EpgService { + /** Default options for epg snackbar notifications */ + epgSnackBarOptions: MatSnackBarConfig = { + verticalPosition: 'bottom', + horizontalPosition: 'right', + }; + + constructor( + private channelStore: ChannelStore, + private electronService: DataService, + private snackBar: MatSnackBar, + private translate: TranslateService + ) {} + + /** + * Fetches and updates EPG from the given URL + * @param urls epg source urls + */ + fetchEpg(urls: string | string[]): void { + if (!Array.isArray(urls)) { + urls = [urls]; + } + urls.forEach((url) => + this.electronService.sendIpcEvent(EPG_FETCH, { + url, + }) + ); + this.snackBar.open( + this.translate.instant('EPG.FETCH_EPG'), + this.translate.instant('CLOSE'), + this.epgSnackBarOptions + ); + } + + /** + * Handles the event when the EPG fetching is done + */ + onEpgFetchDone(): void { + this.channelStore.setEpgAvailableFlag(true); + this.snackBar.open( + this.translate.instant('EPG.DOWNLOAD_SUCCESS'), + null, + { + ...this.epgSnackBarOptions, + duration: 2000, + } + ); + } + + /** + * Handles epg error + */ + onEpgError(): void { + this.snackBar.open(this.translate.instant('EPG.ERROR'), null, { + ...this.epgSnackBarOptions, + duration: 2000, + }); + } +} diff --git a/src/app/settings/settings.component.html b/src/app/settings/settings.component.html index 267541b89..da98f7eea 100644 --- a/src/app/settings/settings.component.html +++ b/src/app/settings/settings.component.html @@ -1,187 +1,217 @@ -