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 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5 authored and GitHub committed 2026-08-13 22:28:50 +02:00
1 parent e94cc029eb
commit 7952e6444d
2 files changed
+63 -3

No files matched your search

+6
View File
@@ -13,6 +13,12 @@ export default {
...angularEsmPreset,
rootDir: '.',
roots: ['<rootDir>/apps/web', '<rootDir>/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: ['<rootDir>/apps/web/src/test-setup.ts'],
resolver: nxPreset.resolver,
moduleFileExtensions: Array.from(
@@ -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: '<div data-test-id="stub-portal-inline-player"></div>',
})
class StubPortalInlinePlayerComponent {
readonly playbackSessionKey = input<string>('');
readonly playback = input<unknown>(null);
readonly timeUpdate = output<{ currentTime: number; duration: number }>();
readonly closed = output<void>();
readonly backClicked = output<void>();
readonly streamUrlCopied = output<void>();
readonly externalFallbackRequested = output<unknown>();
}
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', {