From 45ee1ac3e40bac85f0d768105c66caab668bbe7a Mon Sep 17 00:00:00 2001 From: 4gray Date: Fri, 22 May 2026 02:58:30 +0300 Subject: [PATCH] refactor(portal-ui): use runtime capabilities --- .../navigation/navigation.component.spec.ts | 121 ++++++++++++++++++ .../lib/navigation/navigation.component.ts | 8 +- .../lib/navigation/portal-rail-links.spec.ts | 8 +- .../src/lib/navigation/portal-rail-links.ts | 8 +- .../services/workspace-shell.facade.ts | 4 +- 5 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 libs/portal/shared/ui/src/lib/navigation/navigation.component.spec.ts diff --git a/libs/portal/shared/ui/src/lib/navigation/navigation.component.spec.ts b/libs/portal/shared/ui/src/lib/navigation/navigation.component.spec.ts new file mode 100644 index 000000000..320625478 --- /dev/null +++ b/libs/portal/shared/ui/src/lib/navigation/navigation.component.spec.ts @@ -0,0 +1,121 @@ +import { signal } from '@angular/core'; +import { TestBed } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { TranslateModule } from '@ngx-translate/core'; +import { PORTAL_NAVIGATION_ACTIONS } from '@iptvnator/portal/shared/util'; +import { RuntimeCapabilitiesService } from '@iptvnator/services'; +import { Playlist } from '@iptvnator/shared/interfaces'; +import { NavigationComponent } from './navigation.component'; + +describe('NavigationComponent', () => { + let playlist: ReturnType | undefined>>; + let runtime: { + supportsDownloads: boolean; + }; + + beforeEach(async () => { + playlist = signal | undefined>({ + _id: 'xtream-1', + }); + runtime = { + supportsDownloads: true, + }; + + await TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), NavigationComponent], + providers: [ + { + provide: ActivatedRoute, + useValue: { + snapshot: { + params: { + id: 'xtream-1', + }, + }, + }, + }, + { + provide: Store, + useValue: { + selectSignal: jest.fn(() => playlist), + }, + }, + { + provide: PORTAL_NAVIGATION_ACTIONS, + useValue: { + openAccountInfo: jest.fn(), + openPlaylistInfo: jest.fn(), + openSettings: jest.fn(), + }, + }, + { + provide: RuntimeCapabilitiesService, + useValue: runtime, + }, + ], + }).compileComponents(); + }); + + function createComponent(): NavigationComponent { + return TestBed.createComponent(NavigationComponent).componentInstance; + } + + it('includes downloads in portal rail links when downloads are supported', () => { + runtime.supportsDownloads = true; + + const component = createComponent(); + + expect(component.secondaryLinks().map((link) => link.section)).toEqual([ + 'recently-added', + 'search', + 'downloads', + ]); + }); + + it('hides downloads in portal rail links when downloads are unsupported', () => { + runtime.supportsDownloads = false; + + const component = createComponent(); + + expect(component.secondaryLinks().map((link) => link.section)).toEqual([ + 'recently-added', + 'search', + ]); + }); + + it('uses the Stalker rail shape for Stalker playlists', () => { + runtime.supportsDownloads = true; + playlist.set({ + _id: 'stalker-1', + macAddress: '00:1A:79:00:00:01', + }); + + const component = createComponent(); + + expect(component.primaryLinks().map((link) => link.section)).toEqual([ + 'vod', + 'itv', + 'radio', + 'series', + ]); + expect(component.secondaryLinks().map((link) => link.section)).toEqual([ + 'search', + 'downloads', + ]); + }); + + it('hides Stalker downloads when downloads are unsupported', () => { + runtime.supportsDownloads = false; + playlist.set({ + _id: 'stalker-1', + macAddress: '00:1A:79:00:00:01', + }); + + const component = createComponent(); + + expect(component.secondaryLinks().map((link) => link.section)).toEqual([ + 'search', + ]); + }); +}); diff --git a/libs/portal/shared/ui/src/lib/navigation/navigation.component.ts b/libs/portal/shared/ui/src/lib/navigation/navigation.component.ts index 64c899bd4..fae267ff1 100644 --- a/libs/portal/shared/ui/src/lib/navigation/navigation.component.ts +++ b/libs/portal/shared/ui/src/lib/navigation/navigation.component.ts @@ -12,6 +12,7 @@ import { PortalRailSection, } from '@iptvnator/portal/shared/util'; import { selectPlaylistById } from '@iptvnator/m3u-state'; +import { RuntimeCapabilitiesService } from '@iptvnator/services'; import { Playlist } from '@iptvnator/shared/interfaces'; import { PortalRailLinksComponent } from './portal-rail-links.component'; @@ -33,6 +34,7 @@ import { PortalRailLinksComponent } from './portal-rail-links.component'; export class NavigationComponent { private readonly activatedRoute = inject(ActivatedRoute); private readonly navigationActions = inject(PORTAL_NAVIGATION_ACTIONS); + private readonly runtime = inject(RuntimeCapabilitiesService); private readonly store = inject(Store); readonly portalStatus = input< @@ -48,7 +50,9 @@ export class NavigationComponent { () => !!(this.currentPlaylist() as Playlist | undefined)?.macAddress ); - readonly isElectron = !!window.electron; + get supportsDownloads(): boolean { + return this.runtime.supportsDownloads; + } readonly railLinks = computed(() => { const playlistId = @@ -61,7 +65,7 @@ export class NavigationComponent { return buildPortalRailLinks({ provider: this.isStalkerPlaylist() ? 'stalker' : 'xtreams', playlistId, - isElectron: this.isElectron, + supportsDownloads: this.supportsDownloads, workspace: false, }); }); diff --git a/libs/portal/shared/util/src/lib/navigation/portal-rail-links.spec.ts b/libs/portal/shared/util/src/lib/navigation/portal-rail-links.spec.ts index 9bdd78e5c..b04195703 100644 --- a/libs/portal/shared/util/src/lib/navigation/portal-rail-links.spec.ts +++ b/libs/portal/shared/util/src/lib/navigation/portal-rail-links.spec.ts @@ -5,7 +5,7 @@ describe('buildPortalRailLinks', () => { const links = buildPortalRailLinks({ provider: 'xtreams', playlistId: 'xtream-1', - isElectron: true, + supportsDownloads: true, workspace: false, }); @@ -28,7 +28,7 @@ describe('buildPortalRailLinks', () => { const links = buildPortalRailLinks({ provider: 'xtreams', playlistId: 'xtream-web', - isElectron: false, + supportsDownloads: false, workspace: true, }); @@ -50,7 +50,7 @@ describe('buildPortalRailLinks', () => { const links = buildPortalRailLinks({ provider: 'stalker', playlistId: 'portal-1', - isElectron: false, + supportsDownloads: false, workspace: true, }); @@ -74,7 +74,7 @@ describe('buildPortalRailLinks', () => { const links = buildPortalRailLinks({ provider: 'playlists', playlistId: 'm3u-1', - isElectron: true, + supportsDownloads: true, workspace: true, }); diff --git a/libs/portal/shared/util/src/lib/navigation/portal-rail-links.ts b/libs/portal/shared/util/src/lib/navigation/portal-rail-links.ts index 895476704..dbabde281 100644 --- a/libs/portal/shared/util/src/lib/navigation/portal-rail-links.ts +++ b/libs/portal/shared/util/src/lib/navigation/portal-rail-links.ts @@ -25,7 +25,7 @@ export interface PortalRailLink { interface BuildPortalRailLinksOptions { provider: PortalProvider; playlistId: string; - isElectron: boolean; + supportsDownloads: boolean; workspace: boolean; } @@ -37,7 +37,7 @@ interface PortalRailLinkGroups { export function buildPortalRailLinks( options: BuildPortalRailLinksOptions ): PortalRailLinkGroups { - const { provider, playlistId, isElectron, workspace } = options; + const { provider, playlistId, supportsDownloads, workspace } = options; const root = workspace ? ['/workspace', provider, playlistId] : [`/${provider}`, playlistId]; @@ -82,7 +82,7 @@ export function buildPortalRailLinks( } ); - if (isElectron) { + if (supportsDownloads) { secondary.push({ icon: 'download', tooltip: 'Downloads (this playlist)', @@ -131,7 +131,7 @@ export function buildPortalRailLinks( }, ]; - if (isElectron) { + if (supportsDownloads) { secondary.push({ icon: 'download', tooltip: 'Downloads (this playlist)', diff --git a/libs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell.facade.ts b/libs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell.facade.ts index ea4ac909c..2fa3c7418 100644 --- a/libs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell.facade.ts +++ b/libs/workspace/shell/feature/src/lib/workspace-shell/services/workspace-shell.facade.ts @@ -430,7 +430,7 @@ export class WorkspaceShellFacade { buildPortalRailLinks({ provider: context.provider, playlistId: context.playlistId, - isElectron: this.isElectron, + supportsDownloads: this.isElectron, workspace: true, }).primary, context.provider, @@ -449,7 +449,7 @@ export class WorkspaceShellFacade { buildPortalRailLinks({ provider: context.provider, playlistId: context.playlistId, - isElectron: this.isElectron, + supportsDownloads: this.isElectron, workspace: true, }).secondary.filter((link) => link.section !== 'downloads'), context.provider,