fix(epg): close a guide that never rendered rows without an EmptyError

whenRowsRendered used first(predicate), which errors when the source
completes without a match. The CDK completes renderedRangeStream on
destroy, so closing a guide opened on an empty scope raised an unhandled
EmptyError. Use filter + take(1) instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-01 14:27:45 +02:00
1 parent 810e39379d
commit f80a899212
2 files changed
+33 -3

No files matched your search

@@ -1,7 +1,7 @@
import { ListRange } from '@angular/cdk/collections';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { DestroyRef } from '@angular/core';
import { Subject } from 'rxjs';
import { config, Subject } from 'rxjs';
import { TimelineRenderBlock } from '../epg-timeline/epg-timeline-render.util';
import { EPG_GUIDE_ROW_BUFFER } from './epg-guide-layout.util';
import { EpgGuideChannel } from './epg-guide-source';
@@ -220,6 +220,32 @@ describe('EpgGuideViewportController', () => {
expect(callback).toHaveBeenCalledTimes(1);
});
it('closes cleanly when the viewport completes without ever rendering rows', async () => {
const test = harness();
const callback = jest.fn();
const onUnhandledError = jest.fn();
const previous = config.onUnhandledError;
config.onUnhandledError = onUnhandledError;
try {
test.controller.whenRowsRendered(
test.viewport,
test.destroyRef,
callback
);
// An empty scope: the CDK only ever reports an empty range, then
// completes the stream when the guide closes.
test.renderedRange$.next({ start: 0, end: 0 });
test.renderedRange$.complete();
// RxJS reports unhandled errors from a timeout.
await new Promise((resolve) => setTimeout(resolve, 0));
} finally {
config.onUnhandledError = previous;
}
expect(onUnhandledError).not.toHaveBeenCalled();
expect(callback).not.toHaveBeenCalled();
});
it('drops the initial jump when the host is destroyed first', () => {
const test = harness();
const callback = jest.fn();
@@ -2,7 +2,7 @@ import { ListRange } from '@angular/cdk/collections';
import { DestroyRef } from '@angular/core';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { first } from 'rxjs';
import { filter, take } from 'rxjs';
import { TimelineRenderBlock } from '../epg-timeline/epg-timeline-render.util';
import { EpgGuideFocus } from './epg-guide-keyboard.controller';
import { EPG_GUIDE_ROW_BUFFER } from './epg-guide-layout.util';
@@ -115,7 +115,11 @@ export class EpgGuideViewportController {
): void {
viewport.renderedRangeStream
.pipe(
first((range) => range.end > range.start),
// Not `first(predicate)`: the CDK completes the stream on
// destroy, and a guide closed without ever having rows would
// then raise an `EmptyError`.
filter((range) => range.end > range.start),
take(1),
takeUntilDestroyed(destroyRef)
)
.subscribe(() => callback());