From 7952e6444dfbe3bd2c8f873d552a3a57743791cc Mon Sep 17 00:00:00 2001 From: 4gray <4gray@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:28:50 +0200 Subject: [PATCH] test(playback): stub the inline player in the VOD details spec (#1440) `VodDetailsComponent` specs imported the real component tree, so every `createComponent` pulled in `PortalInlinePlayerComponent` -> `WebPlayerViewComponent` -> ArtPlayer, video.js and the embedded-MPV bridge, and instantiated all of it. The suite's slowest test measured 2521ms idle on a 10-core machine -- a 2.0x margin against Jest's 5000ms default, thin enough that a loaded worker failed tests that had no defect behind them. Swap the inline player for a stand-in that mirrors the selector and the template's bindings; the specs only assert what the host hands the player. Worst test drops 2521ms -> 306ms and the summed test time 3427ms -> 632ms. Assertions are unchanged. Also raise testTimeout to 15s for the shared web-ESM config. The 5s default is thin for Angular component specs generally: TestBed compiles and instantiates a real tree per test, and CI runners are much slower per-core than a dev machine. This is headroom, not a mute -- a spec that genuinely hangs still fails, just later. Attribution, full suite at --maxWorkers=48 on 10 cores: - forced back to 5000ms: VodDetails no longer fails (it did before this change); HtmlVideoPlayer still does, which is what the timeout covers - shipped 15000ms: 2/2 runs, 975/975 passing All six projects sharing the config pass: 1656 tests. Co-authored-by: Claude Opus 5 --- jest.web-esm.workspace.ts | 6 ++ .../vod-details/vod-details.component.spec.ts | 60 ++++++++++++++++++- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/jest.web-esm.workspace.ts b/jest.web-esm.workspace.ts index d22c06e22..0bb8da563 100644 --- a/jest.web-esm.workspace.ts +++ b/jest.web-esm.workspace.ts @@ -13,6 +13,12 @@ export default { ...angularEsmPreset, rootDir: '.', roots: ['/apps/web', '/libs'], + // Jest's 5s default is thin for Angular component specs: TestBed compiles + // and instantiates a real component tree per test, and CI runners are far + // slower per-core than a dev machine. A starved worker then fails a test + // that is merely slow, with no defect behind it. Raised as headroom only — + // a spec that genuinely hangs still fails, just later. + testTimeout: 15_000, setupFilesAfterEnv: ['/apps/web/src/test-setup.ts'], resolver: nxPreset.resolver, moduleFileExtensions: Array.from( diff --git a/libs/ui/playback/src/lib/vod-details/vod-details.component.spec.ts b/libs/ui/playback/src/lib/vod-details/vod-details.component.spec.ts index a43109eb2..157a94e73 100644 --- a/libs/ui/playback/src/lib/vod-details/vod-details.component.spec.ts +++ b/libs/ui/playback/src/lib/vod-details/vod-details.component.spec.ts @@ -1,8 +1,21 @@ -import { signal } from '@angular/core'; +import { Component, input, output, signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Router } from '@angular/router'; -import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { + TranslateModule, + TranslatePipe, + TranslateService, +} from '@ngx-translate/core'; +import { MatIcon } from '@angular/material/icon'; +import { SafePipe } from '@iptvnator/pipes'; +import { + DetailActionsTemplateDirective, + DetailMetaTemplateDirective, + DetailTagsTemplateDirective, + PortalDetailShellComponent, + ViewInPortalActionComponent, +} from '@iptvnator/ui/components'; import { PORTAL_EXTERNAL_PLAYBACK } from '@iptvnator/portal/shared/util'; import { CrossPortalSimilarService, @@ -23,6 +36,29 @@ jest.unstable_mockModule('@yangkghjh/videojs-aspect-ratio-panel', () => ({})); jest.unstable_mockModule('videojs-contrib-quality-levels', () => ({})); jest.unstable_mockModule('videojs-quality-selector-hls', () => ({})); +/** + * Stand-in for the inline player. The real one imports + * `WebPlayerViewComponent`, which drags ArtPlayer, video.js and the + * embedded-MPV bridge into this suite and instantiates that whole tree on + * every `createComponent`. These specs only assert what the host *hands* the + * player, so mirroring the selector and the template's bindings is enough — + * and it keeps the per-test budget clear of Jest's timeout on slower CI + * hardware. + */ +@Component({ + selector: 'app-portal-inline-player', + template: '
', +}) +class StubPortalInlinePlayerComponent { + readonly playbackSessionKey = input(''); + readonly playback = input(null); + readonly timeUpdate = output<{ currentTime: number; duration: number }>(); + readonly closed = output(); + readonly backClicked = output(); + readonly streamUrlCopied = output(); + readonly externalFallbackRequested = output(); +} + const STALKER_VOD: VodDetailsItem = createStalkerVodItem( { id: '42', @@ -250,7 +286,25 @@ describe('VodDetailsComponent offline playback', () => { }, }, ], - }).compileComponents(); + }) + .overrideComponent(VodDetailsComponent, { + // Same import list the component declares, with the inline + // player swapped for the stub above. + set: { + imports: [ + DetailActionsTemplateDirective, + DetailMetaTemplateDirective, + DetailTagsTemplateDirective, + MatIcon, + PortalDetailShellComponent, + ViewInPortalActionComponent, + StubPortalInlinePlayerComponent, + SafePipe, + TranslatePipe, + ], + }, + }) + .compileComponents(); const translate = TestBed.inject(TranslateService); translate.setTranslation('en', {