diff --git a/libs/portal/xtream/feature/src/lib/account-info/account-info.component.spec.ts b/libs/portal/xtream/feature/src/lib/account-info/account-info.component.spec.ts new file mode 100644 index 000000000..04d35f37a --- /dev/null +++ b/libs/portal/xtream/feature/src/lib/account-info/account-info.component.spec.ts @@ -0,0 +1,95 @@ +import { signal } from '@angular/core'; +import type { WritableSignal } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateModule } from '@ngx-translate/core'; +import { + XtreamApiService, + XtreamStore, +} from '@iptvnator/portal/xtream/data-access'; +import { AccountInfoComponent } from './account-info.component'; + +describe('AccountInfoComponent', () => { + let fixture: ComponentFixture; + let component: AccountInfoComponent; + let xtreamApiService: { + getAccountInfo: jest.Mock; + }; + let currentPlaylist: WritableSignal; + + beforeEach(async () => { + xtreamApiService = { + getAccountInfo: jest.fn().mockResolvedValue({ + user_info: { + active_cons: '0', + allowed_output_formats: [], + max_connections: '0', + status: 'Active', + username: 'dialog-user', + }, + server_info: { + server_protocol: 'http', + url: 'dialog.example.test', + }, + }), + }; + currentPlaylist = signal(null); + + await TestBed.configureTestingModule({ + imports: [ + AccountInfoComponent, + NoopAnimationsModule, + TranslateModule.forRoot(), + ], + providers: [ + { + provide: MAT_DIALOG_DATA, + useValue: { + playlist: { + id: 'dialog-playlist', + title: 'Dialog Xtream', + serverUrl: 'https://dialog.example.test', + username: 'dialog-user', + password: 'dialog-secret', + }, + }, + }, + { + provide: XtreamApiService, + useValue: xtreamApiService, + }, + { + provide: XtreamStore, + useValue: { + currentPlaylist, + }, + }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(AccountInfoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it('loads account info from dialog-supplied playlist credentials', () => { + expect(xtreamApiService.getAccountInfo).toHaveBeenCalledWith({ + serverUrl: 'https://dialog.example.test', + username: 'dialog-user', + password: 'dialog-secret', + }); + expect(component.loadState()).toBe('ready'); + expect(component.playlistLabel()).toBe('Dialog Xtream'); + }); + + it('shows unknown content counts when dashboard does not supply them', () => { + expect(component.heroStats().map((stat) => stat.value)).toEqual([ + '0/0', + '-', + '-', + '-', + ]); + }); +}); diff --git a/libs/portal/xtream/feature/src/lib/account-info/account-info.component.ts b/libs/portal/xtream/feature/src/lib/account-info/account-info.component.ts index ee42f608a..80a666238 100644 --- a/libs/portal/xtream/feature/src/lib/account-info/account-info.component.ts +++ b/libs/portal/xtream/feature/src/lib/account-info/account-info.component.ts @@ -15,6 +15,7 @@ import { XtreamStore, } from '@iptvnator/portal/xtream/data-access'; import { createLogger } from '@iptvnator/portal/shared/util'; +import type { XtreamAccountInfoDialogData } from 'shared-interfaces'; type AccountLoadState = 'loading' | 'ready' | 'error'; @@ -46,23 +47,21 @@ interface AccountPort { changeDetection: ChangeDetectionStrategy.OnPush, }) export class AccountInfoComponent { - readonly data = inject<{ - vodStreamsCount: number; - liveStreamsCount: number; - seriesCount: number; - }>(MAT_DIALOG_DATA); + readonly data = + inject(MAT_DIALOG_DATA, { + optional: true, + }) ?? {}; private readonly xtreamApiService = inject(XtreamApiService); private readonly xtreamStore = inject(XtreamStore); private readonly logger = createLogger('XtreamAccountInfo'); - readonly currentPlaylist = this.xtreamStore.currentPlaylist; + readonly currentPlaylist = computed( + () => this.data.playlist ?? this.xtreamStore.currentPlaylist() + ); readonly loadState = signal('loading'); readonly accountInfo = signal(null); readonly skeletonStats = [1, 2, 3, 4]; readonly skeletonPanels = [1, 2]; - readonly vodStreamsCount = this.data.vodStreamsCount; - readonly liveStreamsCount = this.data.liveStreamsCount; - readonly seriesCount = this.data.seriesCount; readonly isActive = computed( () => this.accountInfo()?.user_info?.status === 'Active' @@ -76,6 +75,7 @@ export class AccountInfoComponent { return ( playlist?.title || + playlist?.name || info?.server_info?.url || info?.user_info?.username || '-' @@ -103,7 +103,8 @@ export class AccountInfoComponent { ); }); readonly activeConnectionsLabel = computed( - () => `${this.activeConnections()}/${Math.max(this.maxConnections(), 0)}` + () => + `${this.activeConnections()}/${Math.max(this.maxConnections(), 0)}` ); readonly formattedExpDate = computed(() => this.formatUnixDate(this.accountInfo()?.user_info?.exp_date) @@ -142,19 +143,19 @@ export class AccountInfoComponent { { icon: 'live_tv', labelKey: 'XTREAM.ACCOUNT_INFO.LIVE_TV', - value: String(this.liveStreamsCount), + value: this.formatOptionalCount(this.data.liveStreamsCount), meter: null, }, { icon: 'movie', labelKey: 'XTREAM.ACCOUNT_INFO.MOVIES', - value: String(this.vodStreamsCount), + value: this.formatOptionalCount(this.data.vodStreamsCount), meter: null, }, { icon: 'tv', labelKey: 'XTREAM.ACCOUNT_INFO.TV_SERIES', - value: String(this.seriesCount), + value: this.formatOptionalCount(this.data.seriesCount), meter: null, }, ]); @@ -219,7 +220,7 @@ export class AccountInfoComponent { async reload(): Promise { const playlist = this.currentPlaylist(); - if (!playlist) { + if (!playlist?.serverUrl || !playlist.username || !playlist.password) { this.loadState.set('error'); this.accountInfo.set(null); return; @@ -257,4 +258,8 @@ export class AccountInfoComponent { const parsed = Number.parseInt(value ?? '', 10); return Number.isFinite(parsed) ? parsed : 0; } + + private formatOptionalCount(value?: number): string { + return Number.isFinite(value) ? String(value) : '-'; + } } diff --git a/libs/shared/interfaces/src/index.ts b/libs/shared/interfaces/src/index.ts index bd587bbe7..3bbeab1e7 100644 --- a/libs/shared/interfaces/src/index.ts +++ b/libs/shared/interfaces/src/index.ts @@ -1,7 +1,7 @@ export * from './lib/channel.interface'; export * from './lib/channel.model'; -export * from './lib/epg-channel-with-programs.interface'; export * from './lib/epg-channel-metadata.model'; +export * from './lib/epg-channel-with-programs.interface'; export * from './lib/epg-channel.model'; export * from './lib/epg-item.interface'; export * from './lib/epg-program.model'; @@ -14,9 +14,9 @@ export * from './lib/parsed-playlist.interface'; export * from './lib/playback-position.interface'; export * from './lib/playlist-backup.interface'; export * from './lib/playlist-meta.type'; -export * from './lib/playlist-refresh.interface'; export * from './lib/playlist-recently-viewed.interface'; export * from './lib/playlist-recently-viewed.utils'; +export * from './lib/playlist-refresh.interface'; export * from './lib/playlist.interface'; export * from './lib/portal-activity-item.interface'; export * from './lib/portal-debug.interface'; @@ -26,12 +26,13 @@ export * from './lib/stalker-portal-actions.enum'; export * from './lib/store-keys.enum'; export * from './lib/stream-format.enum'; export * from './lib/theme.enum'; -export * from './lib/xtream-restore-state.util'; +export * from './lib/xtream-account-info-dialog-data.interface'; export * from './lib/xtream-category.interface'; export * from './lib/xtream-code-actions'; export * from './lib/xtream-item.interface'; export * from './lib/xtream-live-stream.interface'; export * from './lib/xtream-response.interface'; +export * from './lib/xtream-restore-state.util'; export * from './lib/xtream-serie-details.interface'; export * from './lib/xtream-serie-item.interface'; export * from './lib/xtream-vod-details.interface'; diff --git a/libs/shared/interfaces/src/lib/xtream-account-info-dialog-data.interface.ts b/libs/shared/interfaces/src/lib/xtream-account-info-dialog-data.interface.ts new file mode 100644 index 000000000..7a4b78e4d --- /dev/null +++ b/libs/shared/interfaces/src/lib/xtream-account-info-dialog-data.interface.ts @@ -0,0 +1,15 @@ +export interface XtreamAccountInfoDialogPlaylist { + id: string; + name?: string; + title?: string; + serverUrl: string; + username: string; + password: string; +} + +export interface XtreamAccountInfoDialogData { + vodStreamsCount?: number; + liveStreamsCount?: number; + seriesCount?: number; + playlist?: XtreamAccountInfoDialogPlaylist; +} diff --git a/libs/workspace/dashboard/feature/jest.config.ts b/libs/workspace/dashboard/feature/jest.config.ts new file mode 100644 index 000000000..91c85f3f3 --- /dev/null +++ b/libs/workspace/dashboard/feature/jest.config.ts @@ -0,0 +1,22 @@ +export default { + displayName: 'workspace-dashboard-feature', + preset: '../../../../jest.preset.js', + setupFilesAfterEnv: ['/src/test-setup.ts'], + coverageDirectory: '../../../../coverage/libs/workspace/dashboard/feature', + transform: { + '^.+\\.(ts|mjs|js|html)$': [ + 'jest-preset-angular', + { + tsconfig: '/tsconfig.spec.json', + stringifyContentPathRegex: '\\.(html|svg)$', + }, + ], + }, + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + snapshotSerializers: [ + 'jest-preset-angular/build/serializers/no-ng-attributes', + 'jest-preset-angular/build/serializers/ng-snapshot', + 'jest-preset-angular/build/serializers/html-comment', + ], + moduleFileExtensions: ['ts', 'js', 'html'], +}; diff --git a/libs/workspace/dashboard/feature/project.json b/libs/workspace/dashboard/feature/project.json index 7021b7f55..e22fb3c1d 100644 --- a/libs/workspace/dashboard/feature/project.json +++ b/libs/workspace/dashboard/feature/project.json @@ -6,6 +6,14 @@ "projectType": "library", "tags": ["scope:workspace", "domain:workspace", "type:feature"], "targets": { + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/workspace/dashboard/feature/jest.config.ts", + "tsConfig": "libs/workspace/dashboard/feature/tsconfig.spec.json" + } + }, "lint": { "executor": "@nx/eslint:lint" } diff --git a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html index de8e50342..60dd77c2f 100644 --- a/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html +++ b/libs/workspace/dashboard/feature/src/lib/rails/dashboard-rail.component.html @@ -12,9 +12,7 @@ {{ 'WORKSPACE.DASHBOARD.MANAGE_ALL' | translate @@ -36,52 +34,94 @@ chevron_left - + + + @for (action of card.actions ?? []; track action.id) { + + } + + +