refactor(portal-ui): use runtime capabilities

This commit is contained in:
4gray committed 2026-05-22 11:47:33 +03:00
1 parent ebd3b2dcfb
commit 45ee1ac3e4
5 files changed
+137 -12

No files matched your search

@@ -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<typeof signal<Partial<Playlist> | undefined>>;
let runtime: {
supportsDownloads: boolean;
};
beforeEach(async () => {
playlist = signal<Partial<Playlist> | 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',
]);
});
});
@@ -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,
});
});
@@ -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,
});
@@ -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)',
@@ -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,