From 1fc8e1cf73a87def3149da0987936833bcc9b85f Mon Sep 17 00:00:00 2001 From: 4gray Date: Sun, 6 Sep 2026 13:40:47 +0200 Subject: [PATCH] fix(m3u): keep the sidebar mounted while the guide is open Guide mode wrapped the sidebar in `@if (!guideOpen())`, so opening the guide destroyed `app-channel-list-container`, whose `ngOnDestroy` dispatches `resetActiveChannel()`. That cleared the active channel, which unmounted the block hosting `app-epg-guide` and tripped the `!canOpenGuide()` effect into closing the guide again: the guide never appeared and the page dropped to "Please select a channel". The sidebar now stays mounted and is hidden with `.sidebar--guide-hidden` plus `inert`, so it is neither focusable nor read by assistive technology while the guide owns the layout. Hiding also preserves the channel list's scroll position across guide toggles. Co-Authored-By: Claude Fable 5.1 --- .../video-player/video-player.component.html | 111 ++++++++++-------- .../video-player/video-player.component.scss | 11 ++ .../video-player.component.spec.ts | 26 +++- 3 files changed, 90 insertions(+), 58 deletions(-) diff --git a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html index 729c4b9ea..3e426a59c 100644 --- a/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html +++ b/libs/playlist/m3u/feature-player/src/lib/video-player/video-player.component.html @@ -1,55 +1,62 @@ -@if (!guideOpen()) { - -} + +
{ expect(headerContext.action()).toBeNull(); }); - it('opens the guide in place of sidebar and timeline without remounting the player', () => { + it('opens the guide, hides the sidebar without unmounting it, and keeps the player', () => { syncStoreState(sampleChannel); player.set(VideoPlayer.VideoJs); fixture.detectChanges(); @@ -284,7 +284,9 @@ describe('VideoPlayerComponent', () => { expect( fixture.nativeElement.querySelector('app-epg-timeline') ).not.toBeNull(); - expect(fixture.nativeElement.querySelector('.sidebar')).not.toBeNull(); + const sidebarBefore = fixture.nativeElement.querySelector('.sidebar'); + expect(sidebarBefore).not.toBeNull(); + storeMock.dispatch.mockClear(); component.openGuide(); fixture.detectChanges(); @@ -295,7 +297,18 @@ describe('VideoPlayerComponent', () => { expect( fixture.nativeElement.querySelector('app-epg-guide-now-playing') ).not.toBeNull(); - expect(fixture.nativeElement.querySelector('.sidebar')).toBeNull(); + // Regression: the sidebar used to sit behind `@if (!guideOpen())`, so + // opening the guide destroyed the channel-list container, whose + // `ngOnDestroy` dispatches `resetActiveChannel()`. That cleared the + // active channel and closed the guide again the moment it opened. + expect(storeMock.dispatch).not.toHaveBeenCalledWith( + ChannelActions.resetActiveChannel() + ); + const sidebarWhileOpen = + fixture.nativeElement.querySelector('.sidebar'); + expect(sidebarWhileOpen).toBe(sidebarBefore); + expect(sidebarWhileOpen.classList).toContain('sidebar--guide-hidden'); + expect(sidebarWhileOpen.hasAttribute('inert')).toBe(true); expect( fixture.nativeElement.querySelector('app-epg-timeline') ).toBeNull(); @@ -310,9 +323,10 @@ describe('VideoPlayerComponent', () => { component.closeGuide(); fixture.detectChanges(); expect(fixture.nativeElement.querySelector('app-epg-guide')).toBeNull(); - // The sidebar list itself sits behind an `@defer`, so the assertion - // targets the container the guide mode actually removes. - expect(fixture.nativeElement.querySelector('.sidebar')).not.toBeNull(); + const sidebarAfter = fixture.nativeElement.querySelector('.sidebar'); + expect(sidebarAfter).toBe(sidebarBefore); + expect(sidebarAfter.classList).not.toContain('sidebar--guide-hidden'); + expect(sidebarAfter.hasAttribute('inert')).toBe(false); expect(fixture.nativeElement.querySelector('app-web-player-view')).toBe( playerBefore );