From 4a21579afff2eb5a109ef2f2d2f4ba64dbdc8e9a Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 4 May 2026 23:26:56 +0200 Subject: [PATCH] fix: clear external player paths on save Entire-Checkpoint: f957cd9849e0 --- .../src/app/events/player.events.spec.ts | 46 +++++++++++++++++++ .../src/app/events/player.events.ts | 32 +++++++++---- .../app/settings/settings.component.spec.ts | 24 ++++++++++ .../src/app/settings/settings.component.ts | 31 +++++++------ 4 files changed, 111 insertions(+), 22 deletions(-) diff --git a/apps/electron-backend/src/app/events/player.events.spec.ts b/apps/electron-backend/src/app/events/player.events.spec.ts index 016b0c59b..9040817ff 100644 --- a/apps/electron-backend/src/app/events/player.events.spec.ts +++ b/apps/electron-backend/src/app/events/player.events.spec.ts @@ -25,6 +25,12 @@ jest.mock('../services/stalker-playback-context.service', () => ({ getStalkerPlaybackContextHeaders: jest.fn(() => undefined), })); +import { ipcMain } from 'electron'; +import { + MPV_PLAYER_PATH, + store, + VLC_PLAYER_PATH, +} from '../services/store.service'; import { buildExternalPlayerSpawnSpec, isRunningInFlatpak, @@ -39,6 +45,22 @@ function createPathExists(existingPaths: string[]) { return (candidatePath: string) => existingPaths.includes(candidatePath); } +function getIpcMainHandler(channel: string): (...args: unknown[]) => unknown { + const handleMock = ipcMain.handle as unknown as jest.Mock; + const calls = handleMock.mock.calls as Array< + [string, (...args: unknown[]) => unknown] + >; + const match = calls.find( + ([registeredChannel]) => registeredChannel === channel + ); + + if (!match) { + throw new Error(`Missing ipcMain handler for ${channel}`); + } + + return match[1]; +} + describe('player.events Flatpak launch helpers', () => { it('detects Flatpak only on Linux when /.flatpak-info exists', () => { expect(isRunningInFlatpak(createPathExists(['/.flatpak-info']), 'linux')).toBe( @@ -170,3 +192,27 @@ Command Line Interface initialized. Type \`help' for help. ).toBe('stopped'); }); }); + +describe('player.events external player path settings', () => { + beforeEach(() => { + (store.set as unknown as jest.Mock).mockClear(); + }); + + it('stores cleared VLC player paths as an empty string', () => { + getIpcMainHandler('SET_VLC_PLAYER_PATH')({}, ' '); + + expect(store.set).toHaveBeenCalledWith(VLC_PLAYER_PATH, ''); + }); + + it('trims custom MPV player paths before storing them', () => { + getIpcMainHandler('SET_MPV_PLAYER_PATH')( + {}, + ' /Applications/mpv.app/Contents/MacOS/mpv ' + ); + + expect(store.set).toHaveBeenCalledWith( + MPV_PLAYER_PATH, + '/Applications/mpv.app/Contents/MacOS/mpv' + ); + }); +}); diff --git a/apps/electron-backend/src/app/events/player.events.ts b/apps/electron-backend/src/app/events/player.events.ts index f8de5c802..759ae70db 100644 --- a/apps/electron-backend/src/app/events/player.events.ts +++ b/apps/electron-backend/src/app/events/player.events.ts @@ -77,11 +77,17 @@ export function isRunningInFlatpak( return platform === 'linux' && pathExists('/.flatpak-info'); } -function normalizeCustomPlayerPath(value: string | undefined): string | null { +function normalizeCustomPlayerPath( + value: string | null | undefined +): string | null { const trimmedValue = value?.trim(); return trimmedValue ? trimmedValue : null; } +function normalizePlayerPathForStore(value: string | null | undefined): string { + return normalizeCustomPlayerPath(value) ?? ''; +} + function getDefaultPlayerPath( player: ExternalPlayerName, options: PlayerPathOptions = {} @@ -921,10 +927,14 @@ ipcMain.handle( } ); -ipcMain.handle('SET_MPV_PLAYER_PATH', (_event, mpvPlayerPath) => { - console.log('... setting mpv player path', mpvPlayerPath); - store.set(MPV_PLAYER_PATH, mpvPlayerPath); -}); +ipcMain.handle( + 'SET_MPV_PLAYER_PATH', + (_event, mpvPlayerPath: string | null | undefined) => { + const normalizedPlayerPath = normalizePlayerPathForStore(mpvPlayerPath); + console.log('... setting mpv player path', normalizedPlayerPath); + store.set(MPV_PLAYER_PATH, normalizedPlayerPath); + } +); ipcMain.handle('SET_MPV_REUSE_INSTANCE', (_event, reuseInstance: boolean) => { console.log('... setting mpv reuse instance', reuseInstance); @@ -1247,10 +1257,14 @@ ipcMain.handle( } ); -ipcMain.handle('SET_VLC_PLAYER_PATH', (_event, vlcPlayerPath) => { - console.log('... setting vlc player path', vlcPlayerPath); - store.set(VLC_PLAYER_PATH, vlcPlayerPath); -}); +ipcMain.handle( + 'SET_VLC_PLAYER_PATH', + (_event, vlcPlayerPath: string | null | undefined) => { + const normalizedPlayerPath = normalizePlayerPathForStore(vlcPlayerPath); + console.log('... setting vlc player path', normalizedPlayerPath); + store.set(VLC_PLAYER_PATH, normalizedPlayerPath); + } +); ipcMain.handle( CLOSE_EXTERNAL_PLAYER_SESSION, diff --git a/apps/web/src/app/settings/settings.component.spec.ts b/apps/web/src/app/settings/settings.component.spec.ts index 3af53a2f9..35c243c46 100644 --- a/apps/web/src/app/settings/settings.component.spec.ts +++ b/apps/web/src/app/settings/settings.component.spec.ts @@ -925,4 +925,28 @@ describe('SettingsComponent', () => { component.settingsForm.value ); }); + + it('clears external player paths in Electron when saved as empty', async () => { + const mockStore = settingsStore as unknown as MockSettingsStore; + mockStore.updateSettings.mockResolvedValue(undefined); + const setMpvPlayerPath = jest.spyOn( + window.electron, + 'setMpvPlayerPath' + ); + const setVlcPlayerPath = jest.spyOn( + window.electron, + 'setVlcPlayerPath' + ); + + component.settingsForm.patchValue({ + mpvPlayerPath: '', + vlcPlayerPath: '', + }); + + component.onSubmit(); + await fixture.whenStable(); + + expect(setMpvPlayerPath).toHaveBeenCalledWith(''); + expect(setVlcPlayerPath).toHaveBeenCalledWith(''); + }); }); diff --git a/apps/web/src/app/settings/settings.component.ts b/apps/web/src/app/settings/settings.component.ts index 0f517e42d..4b9ece471 100644 --- a/apps/web/src/app/settings/settings.component.ts +++ b/apps/web/src/app/settings/settings.component.ts @@ -443,23 +443,22 @@ export class SettingsComponent implements OnInit, OnDestroy { * the indexed db store */ onSubmit(): void { - this.settingsStore.updateSettings(this.settingsForm.value).then(() => { + const settings = this.settingsForm.value; + const mpvPlayerPath = this.normalizeExternalPlayerPath( + settings.mpvPlayerPath + ); + const vlcPlayerPath = this.normalizeExternalPlayerPath( + settings.vlcPlayerPath + ); + + this.settingsStore.updateSettings(settings).then(() => { this.applyChangedSettings(); if (window.electron) { - window.electron.updateSettings(this.settingsForm.value); + window.electron.updateSettings(settings); - // Set player paths if using external players - if (this.settingsForm.value.mpvPlayerPath) { - window.electron.setMpvPlayerPath( - this.settingsForm.value.mpvPlayerPath - ); - } - if (this.settingsForm.value.vlcPlayerPath) { - window.electron.setVlcPlayerPath( - this.settingsForm.value.vlcPlayerPath - ); - } + window.electron.setMpvPlayerPath(mpvPlayerPath); + window.electron.setVlcPlayerPath(vlcPlayerPath); } }); if (this.isDialog) { @@ -467,6 +466,12 @@ export class SettingsComponent implements OnInit, OnDestroy { } } + private normalizeExternalPlayerPath( + playerPath: string | null | undefined + ): string { + return playerPath?.trim() ?? ''; + } + /** * Applies the changed settings to the app */