From 0bb15f749bc3c5a79525ba8621fc7b21bd308517 Mon Sep 17 00:00:00 2001 From: DEV-bot Date: Mon, 2 Mar 2026 02:30:11 +0000 Subject: [PATCH] test(player): lock primary-device audio policy for issue 260 --- ...60-PHONE-CLIENT-NO-AUDIO-GUARD-ARTIFACT.md | 18 +++++++++++++ .../player/player-shell.component.spec.ts | 27 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 docs/ISSUE-260-PHONE-CLIENT-NO-AUDIO-GUARD-ARTIFACT.md diff --git a/docs/ISSUE-260-PHONE-CLIENT-NO-AUDIO-GUARD-ARTIFACT.md b/docs/ISSUE-260-PHONE-CLIENT-NO-AUDIO-GUARD-ARTIFACT.md new file mode 100644 index 0000000..8401951 --- /dev/null +++ b/docs/ISSUE-260-PHONE-CLIENT-NO-AUDIO-GUARD-ARTIFACT.md @@ -0,0 +1,18 @@ +# Issue #260 Artifact — Phone/client no-audio guard (primary-device only playback) + +## Scope +Added regression coverage for MVP audio policy to ensure phone/client flows never claim playback ownership, while primary-device playback stays unaffected. + +## Acceptance mapping +1. **client/phone triggers no playback** + - Existing test coverage retained in `player-shell.component.spec.ts`: + - `does not trigger original media play during player-shell init path` + - `installs secondary-device audio guard while player shell is mounted` +2. **primary device playback unaffected** + - New negative test in `player-shell.component.spec.ts`: + - `keeps primary-device playback untouched when no-audio capability is disabled` +3. **one negative test for phone audio** + - Existing negative path preserved: + - `does not trigger original media play during player-shell init path` +4. **no backend contract changes** + - Frontend test/docs-only scope; no backend contract files changed. diff --git a/frontend/angular/src/app/features/player/player-shell.component.spec.ts b/frontend/angular/src/app/features/player/player-shell.component.spec.ts index ed8b19a..5bd1d1d 100644 --- a/frontend/angular/src/app/features/player/player-shell.component.spec.ts +++ b/frontend/angular/src/app/features/player/player-shell.component.spec.ts @@ -468,6 +468,33 @@ describe('PlayerShellComponent gameplay wiring', () => { component.ngOnDestroy(); }); + it('keeps primary-device playback untouched when no-audio capability is disabled', async () => { + const originalPlay = vi.fn().mockResolvedValue(undefined); + const mediaPrototype = { play: originalPlay }; + + vi.stubGlobal('window', { + location: { hash: '', search: '' }, + history: { state: null, replaceState: vi.fn() }, + localStorage: { getItem: vi.fn().mockReturnValue('en'), setItem: vi.fn(), removeItem: vi.fn() }, + sessionStorage: { getItem: vi.fn().mockReturnValue(null), setItem: vi.fn(), removeItem: vi.fn() }, + HTMLMediaElement: { prototype: mediaPrototype }, + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + }); + vi.stubGlobal('navigator', { language: 'en-US', onLine: true }); + + const component = new PlayerShellComponent(); + (component as any).clientHasNoAudioOutput = false; + + component.ngOnInit(); + + await expect(mediaPrototype.play()).resolves.toBeUndefined(); + expect(mediaPrototype.play).toBe(originalPlay); + expect((mediaPrototype as any).__wppSecondaryDeviceAudioGuard__).toBeUndefined(); + + component.ngOnDestroy(); + }); + it('resolves i18n warning copy from shared catalog without key fallback', () => { const component = new PlayerShellComponent();