mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
test(workspace-shell): repair stale component-spec mocks and cover new overlay child
The two component specs in this lib had been carrying 7 long-standing failures rooted in three stale-mock issues, all unrelated to public behaviour but enough to make the suite red on every run. Header spec (5 failures, NG0303 on app-playlist-switcher): - MockPlaylistSwitcherComponent was missing canRefreshActivePlaylist / isRefreshingActivePlaylist inputs and refreshPlaylistRequested output. The real PlaylistSwitcherComponent grew them when refresh was added to the header; the mock never caught up. Re-aligned. Workspace-shell component spec (2 failures): - NG0301 on #dropZone="playlistDropZone" because overrideComponent's set.imports list dropped PlaylistDropZoneDirective and the drop overlay component. Added MockPlaylistDropZoneDirective and MockPlaylistDropOverlayComponent. - After the recent option-D refactor that lifted the import overlay into its own child component, the spec also needed MockWorkspaceShellImportOverlayComponent so the parent template could resolve <app-workspace-shell-import-overlay />. - MockWorkspaceShellHeaderComponent gained hasNoPlaylists input and MockWorkspaceShellContextSidebarComponent gained hasPlaylists input to match the real components. - The "renders type-aware xtream import progress copy" test asserted on inline overlay markup that no longer lives in the parent template. Replaced with a structural test that the overlay child renders/unmounts based on facade.showXtreamImportOverlay(). New focused spec for WorkspaceShellImportOverlayComponent (6 tests) covers the moved progress-copy assertion at the right level by mocking the WorkspaceShellXtreamImportService directly: title, progress copy, determinate vs indeterminate progress bar, badge tone, and cancel forwarding. Final state: full feature lib suite is 66/66 (was 53/60 with 7 chronic failures). pnpm nx test web, pnpm nx lint workspace-shell-feature, pnpm nx build web --configuration=production, and the basic + settings web-e2e suites all pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Entire-Checkpoint: c2cea9c530e6
This commit is contained in:
1 parent
63f5173139
commit
d24c67c6e8
3 files changed
+197
-12
No files matched your search
+3
@@ -24,9 +24,12 @@ class MockPlaylistSwitcherComponent {
|
||||
readonly showPlaylistInfo = input(false);
|
||||
readonly showAccountInfo = input(false);
|
||||
readonly showAddPlaylist = input(false);
|
||||
readonly canRefreshActivePlaylist = input(false);
|
||||
readonly isRefreshingActivePlaylist = input(false);
|
||||
readonly playlistInfoRequested = output<void>();
|
||||
readonly accountInfoRequested = output<void>();
|
||||
readonly addPlaylistRequested = output<void>();
|
||||
readonly refreshPlaylistRequested = output<void>();
|
||||
}
|
||||
|
||||
describe('WorkspaceShellHeaderComponent', () => {
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import { signal } from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { of } from 'rxjs';
|
||||
import { WorkspaceShellXtreamImportService } from '../../services/workspace-shell-xtream-import.service';
|
||||
import { WorkspaceShellImportOverlayComponent } from './workspace-shell-import-overlay.component';
|
||||
|
||||
class MockWorkspaceShellXtreamImportService {
|
||||
readonly xtreamImportSourceLabel = signal('');
|
||||
readonly xtreamImportPhaseTone = signal<'remote' | 'local' | null>(null);
|
||||
readonly xtreamImportTitleLabel = signal('Import title');
|
||||
readonly xtreamImportPhaseLabel = signal('');
|
||||
readonly xtreamImportDetailLabel = signal('');
|
||||
readonly xtreamImportProgressLabel = signal('');
|
||||
readonly xtreamActiveImportCount = signal(0);
|
||||
readonly xtreamActiveItemsToImport = signal(0);
|
||||
readonly canCancelXtreamImport = signal(false);
|
||||
readonly isCancellingXtreamImport = signal(false);
|
||||
|
||||
cancelXtreamImport = jest.fn();
|
||||
}
|
||||
|
||||
describe('WorkspaceShellImportOverlayComponent', () => {
|
||||
let importService: MockWorkspaceShellXtreamImportService;
|
||||
|
||||
beforeEach(async () => {
|
||||
importService = new MockWorkspaceShellXtreamImportService();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
WorkspaceShellImportOverlayComponent,
|
||||
NoopAnimationsModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: WorkspaceShellXtreamImportService,
|
||||
useValue: importService,
|
||||
},
|
||||
{
|
||||
provide: TranslateService,
|
||||
useValue: {
|
||||
instant: (key: string) => key,
|
||||
get: (key: string) => of(key),
|
||||
stream: (key: string) => of(key),
|
||||
onLangChange: of(null),
|
||||
onTranslationChange: of(null),
|
||||
onDefaultLangChange: of(null),
|
||||
currentLang: 'en',
|
||||
defaultLang: 'en',
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('renders the title label', () => {
|
||||
const fixture = TestBed.createComponent(
|
||||
WorkspaceShellImportOverlayComponent
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'.workspace-loading-overlay__card h3'
|
||||
).textContent
|
||||
).toContain('Import title');
|
||||
});
|
||||
|
||||
it('renders the progress copy when the service exposes one', () => {
|
||||
importService.xtreamImportProgressLabel.set('Movies imported: 20 / 12,323');
|
||||
|
||||
const fixture = TestBed.createComponent(
|
||||
WorkspaceShellImportOverlayComponent
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'.workspace-loading-overlay__progress-copy'
|
||||
)?.textContent
|
||||
).toContain('Movies imported: 20 / 12,323');
|
||||
});
|
||||
|
||||
it('renders a determinate progress bar when current and total are non-zero', () => {
|
||||
importService.xtreamActiveImportCount.set(20);
|
||||
importService.xtreamActiveItemsToImport.set(100);
|
||||
|
||||
const fixture = TestBed.createComponent(
|
||||
WorkspaceShellImportOverlayComponent
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
const progress = fixture.nativeElement.querySelector(
|
||||
'mat-progress-bar'
|
||||
);
|
||||
expect(progress).not.toBeNull();
|
||||
expect(progress.getAttribute('mode')).toBe('determinate');
|
||||
});
|
||||
|
||||
it('renders an indeterminate progress bar when totals are unknown', () => {
|
||||
const fixture = TestBed.createComponent(
|
||||
WorkspaceShellImportOverlayComponent
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
const progress = fixture.nativeElement.querySelector(
|
||||
'mat-progress-bar'
|
||||
);
|
||||
expect(progress.getAttribute('mode')).toBe('indeterminate');
|
||||
});
|
||||
|
||||
it('toggles the remote badge modifier when phase tone is remote', () => {
|
||||
importService.xtreamImportSourceLabel.set('REMOTE');
|
||||
importService.xtreamImportPhaseTone.set('remote');
|
||||
|
||||
const fixture = TestBed.createComponent(
|
||||
WorkspaceShellImportOverlayComponent
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
const badge = fixture.nativeElement.querySelector(
|
||||
'.workspace-loading-overlay__badge'
|
||||
);
|
||||
expect(badge).not.toBeNull();
|
||||
expect(
|
||||
badge.classList.contains('workspace-loading-overlay__badge--remote')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('forwards cancel clicks to the import service', () => {
|
||||
importService.canCancelXtreamImport.set(true);
|
||||
|
||||
const fixture = TestBed.createComponent(
|
||||
WorkspaceShellImportOverlayComponent
|
||||
);
|
||||
fixture.detectChanges();
|
||||
|
||||
const button = fixture.nativeElement.querySelector(
|
||||
'.workspace-loading-overlay__action'
|
||||
) as HTMLButtonElement;
|
||||
button.click();
|
||||
|
||||
expect(importService.cancelXtreamImport).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
+48
-12
@@ -1,11 +1,11 @@
|
||||
import {
|
||||
Component,
|
||||
Directive,
|
||||
input,
|
||||
output,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||
import { RouterOutlet, provideRouter } from '@angular/router';
|
||||
import {
|
||||
WorkspacePortalContext,
|
||||
@@ -54,6 +54,7 @@ class MockWorkspaceShellHeaderComponent {
|
||||
readonly canRefreshPlaylist = input(false);
|
||||
readonly isRefreshingPlaylist = input(false);
|
||||
readonly isElectron = input(false);
|
||||
readonly hasNoPlaylists = input(false);
|
||||
readonly isDownloadsView = input(false);
|
||||
readonly hasActiveDownloads = input(false);
|
||||
readonly headerBulkAction = input<WorkspaceHeaderBulkAction | null>(null);
|
||||
@@ -78,6 +79,7 @@ class MockWorkspaceShellContextSidebarComponent {
|
||||
readonly variant = input<WorkspaceShellContextPanel>('none');
|
||||
readonly context = input<WorkspacePortalContext | null>(null);
|
||||
readonly section = input<string | null>(null);
|
||||
readonly hasPlaylists = input(false);
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -90,6 +92,31 @@ class MockExternalPlaybackDockComponent {
|
||||
readonly closeClicked = output<void>();
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-playlist-drop-overlay',
|
||||
template: '',
|
||||
standalone: true,
|
||||
})
|
||||
class MockPlaylistDropOverlayComponent {
|
||||
readonly state = input<unknown>({ kind: 'idle' });
|
||||
}
|
||||
|
||||
@Directive({
|
||||
selector: '[appPlaylistDropZone]',
|
||||
exportAs: 'playlistDropZone',
|
||||
standalone: true,
|
||||
})
|
||||
class MockPlaylistDropZoneDirective {
|
||||
readonly overlayState = signal({ kind: 'idle' });
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-workspace-shell-import-overlay',
|
||||
template: '',
|
||||
standalone: true,
|
||||
})
|
||||
class MockWorkspaceShellImportOverlayComponent {}
|
||||
|
||||
class MockWorkspaceShellFacade {
|
||||
readonly brandLink = signal('/workspace/dashboard');
|
||||
readonly brandTooltipKey = signal('WORKSPACE.SHELL.RAIL_DASHBOARD');
|
||||
@@ -114,6 +141,7 @@ class MockWorkspaceShellFacade {
|
||||
readonly headerShortcut = signal(null);
|
||||
readonly canRefreshPlaylist = signal(false);
|
||||
readonly isRefreshingPlaylist = signal(false);
|
||||
readonly hasNoPlaylists = signal(false);
|
||||
readonly isDownloadsView = signal(false);
|
||||
readonly hasActiveDownloads = signal(false);
|
||||
readonly headerBulkAction = signal<WorkspaceHeaderBulkAction | null>(null);
|
||||
@@ -173,11 +201,13 @@ describe('WorkspaceShellComponent', () => {
|
||||
.overrideComponent(WorkspaceShellComponent, {
|
||||
set: {
|
||||
imports: [
|
||||
MatProgressBarModule,
|
||||
RouterOutlet,
|
||||
MockExternalPlaybackDockComponent,
|
||||
MockPlaylistDropOverlayComponent,
|
||||
MockPlaylistDropZoneDirective,
|
||||
MockWorkspaceShellContextSidebarComponent,
|
||||
MockWorkspaceShellHeaderComponent,
|
||||
MockWorkspaceShellImportOverlayComponent,
|
||||
MockWorkspaceShellRailComponent,
|
||||
],
|
||||
providers: [
|
||||
@@ -210,7 +240,7 @@ describe('WorkspaceShellComponent', () => {
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
it('renders type-aware xtream import progress copy in the overlay', async () => {
|
||||
it('renders the xtream import overlay child only when the facade flag is true', async () => {
|
||||
const facade = new MockWorkspaceShellFacade();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
@@ -220,11 +250,13 @@ describe('WorkspaceShellComponent', () => {
|
||||
.overrideComponent(WorkspaceShellComponent, {
|
||||
set: {
|
||||
imports: [
|
||||
MatProgressBarModule,
|
||||
RouterOutlet,
|
||||
MockExternalPlaybackDockComponent,
|
||||
MockPlaylistDropOverlayComponent,
|
||||
MockPlaylistDropZoneDirective,
|
||||
MockWorkspaceShellContextSidebarComponent,
|
||||
MockWorkspaceShellHeaderComponent,
|
||||
MockWorkspaceShellImportOverlayComponent,
|
||||
MockWorkspaceShellRailComponent,
|
||||
],
|
||||
providers: [
|
||||
@@ -237,18 +269,22 @@ describe('WorkspaceShellComponent', () => {
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
facade.showXtreamImportOverlay.set(true);
|
||||
facade.xtreamActiveImportCount.set(20);
|
||||
facade.xtreamActiveItemsToImport.set(12323);
|
||||
facade.xtreamImportProgressLabel.set('Movies imported: 20 / 12,323');
|
||||
|
||||
const fixture = TestBed.createComponent(WorkspaceShellComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'.workspace-loading-overlay__progress-copy'
|
||||
)?.textContent
|
||||
).toContain('Movies imported: 20 / 12,323');
|
||||
'app-workspace-shell-import-overlay'
|
||||
)
|
||||
).toBeNull();
|
||||
|
||||
facade.showXtreamImportOverlay.set(true);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(
|
||||
fixture.nativeElement.querySelector(
|
||||
'app-workspace-shell-import-overlay'
|
||||
)
|
||||
).not.toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user