refactor(runtime): require playlist storage bridge

This commit is contained in:
4gray committed 2026-05-22 13:37:36 +03:00
1 parent 4603bd83d5
commit f626e176d2
5 files changed
+77 -23

No files matched your search

+5 -3
View File
@@ -70,9 +70,11 @@ direct `window.electron` or `DataService.getAppEnvironment()` checks. Keep
feature decisions expressed as capabilities such as `supportsEpg`,
`supportsSqlite`, `supportsXtreamSqliteDataSource`, `supportsDownloads`, or
`supportsManagedExternalPlayers` so PWA and Electron behavior stays auditable
from one shared boundary. `supportsDownloads` requires the complete downloads
preload API surface used by `DownloadsService`, `supportsPlaylistRefresh`
requires the native playlist refresh/cancel/progress bridge, and
from one shared boundary. `supportsSqlite` requires the complete playlist
storage preload API surface used by `PlaylistsService`, `supportsDownloads`
requires the complete downloads preload API surface used by `DownloadsService`,
`supportsPlaylistRefresh` requires the native playlist refresh/cancel/progress
bridge, and
`supportsManagedExternalPlayers` requires the MPV and VLC preload launch methods
(`openInMpv` and `openInVlc`); a partial Electron bridge must not expose
desktop-only actions in the PWA/shared UI.
@@ -26,7 +26,8 @@ describe('PlaylistDeleteActionService', () => {
deletePlaylist: jest.Mock;
};
let runtime: {
isElectron: boolean;
supportsSqlite: boolean;
supportsXtreamSqliteDataSource: boolean;
};
let injector: EnvironmentInjector;
@@ -41,7 +42,8 @@ describe('PlaylistDeleteActionService', () => {
deletePlaylist: jest.fn(() => of({ success: true })),
};
runtime = {
isElectron: false,
supportsSqlite: false,
supportsXtreamSqliteDataSource: false,
};
injector = createEnvironmentInjector(
@@ -76,8 +78,8 @@ describe('PlaylistDeleteActionService', () => {
expect(databaseService.deletePlaylist).not.toHaveBeenCalled();
});
it('deletes Electron Xtream playlists through DatabaseService with progress options', async () => {
runtime.isElectron = true;
it('deletes SQLite-backed Xtream playlists through DatabaseService with progress options', async () => {
runtime.supportsXtreamSqliteDataSource = true;
const onEvent = jest.fn();
const service = createService();
@@ -98,8 +100,8 @@ describe('PlaylistDeleteActionService', () => {
expect(playlistsService.deletePlaylist).not.toHaveBeenCalled();
});
it('deletes Electron non-Xtream playlists without progress options', async () => {
runtime.isElectron = true;
it('deletes SQLite-backed non-Xtream playlists without progress options', async () => {
runtime.supportsSqlite = true;
const service = createService();
await expect(
@@ -115,4 +117,17 @@ describe('PlaylistDeleteActionService', () => {
undefined
);
});
it('uses browser playlist storage when an Xtream playlist lacks Xtream SQLite support', async () => {
runtime.supportsSqlite = true;
runtime.supportsXtreamSqliteDataSource = false;
const service = createService();
await expect(service.deletePlaylist(playlist)).resolves.toBe(true);
expect(playlistsService.deletePlaylist).toHaveBeenCalledWith(
'playlist-1'
);
expect(databaseService.deletePlaylist).not.toHaveBeenCalled();
});
});
@@ -27,7 +27,11 @@ export class PlaylistDeleteActionService {
playlist: PlaylistMeta,
options: PlaylistDeleteActionOptions = {}
): Promise<boolean> {
if (this.runtime.isElectron) {
const supportsElectronDelete = playlist.serverUrl
? this.runtime.supportsXtreamSqliteDataSource
: this.runtime.supportsSqlite;
if (supportsElectronDelete) {
return this.deletePlaylistInElectron(playlist, options);
}
@@ -36,8 +36,12 @@ describe('RuntimeCapabilitiesService', () => {
it('reports Electron capabilities from the available preload bridge methods', () => {
testWindow.electron = {
platform: 'darwin',
dbDeleteAllPlaylists: jest.fn(),
dbDeletePlaylist: jest.fn(),
dbGetAppPlaylist: jest.fn(),
dbGetAppPlaylists: jest.fn(),
dbUpsertAppPlaylist: jest.fn(),
dbUpsertAppPlaylists: jest.fn(),
dbGetAppState: jest.fn(),
dbSetAppState: jest.fn(),
dbGetRecentlyViewed: jest.fn(),
@@ -57,7 +61,6 @@ describe('RuntimeCapabilitiesService', () => {
dbGetPlaylist: jest.fn(),
dbCreatePlaylist: jest.fn(),
dbUpdatePlaylist: jest.fn(),
dbDeletePlaylist: jest.fn(),
dbHasCategories: jest.fn(),
dbGetCategories: jest.fn(),
dbSaveCategories: jest.fn(),
@@ -158,12 +161,7 @@ describe('RuntimeCapabilitiesService', () => {
});
it('keeps the Xtream SQLite data source disabled when only generic SQLite methods exist', () => {
testWindow.electron = {
dbGetAppPlaylists: jest.fn(),
dbUpsertAppPlaylist: jest.fn(),
dbGetAppState: jest.fn(),
dbSetAppState: jest.fn(),
};
testWindow.electron = createPlaylistStorageBridge();
const service = new RuntimeCapabilitiesService();
@@ -235,4 +233,35 @@ describe('RuntimeCapabilitiesService', () => {
expect(service.supportsPlaylistRefresh).toBe(true);
});
it('requires the complete playlist storage SQLite preload surface', () => {
testWindow.electron = {
dbGetAppPlaylists: jest.fn(),
dbUpsertAppPlaylist: jest.fn(),
dbGetAppState: jest.fn(),
dbSetAppState: jest.fn(),
};
const service = new RuntimeCapabilitiesService();
expect(service.isElectron).toBe(true);
expect(service.supportsSqlite).toBe(false);
testWindow.electron = createPlaylistStorageBridge();
expect(service.supportsSqlite).toBe(true);
});
});
function createPlaylistStorageBridge(): Record<string, jest.Mock> {
return {
dbDeleteAllPlaylists: jest.fn(),
dbDeletePlaylist: jest.fn(),
dbGetAppPlaylist: jest.fn(),
dbGetAppPlaylists: jest.fn(),
dbGetAppState: jest.fn(),
dbSetAppState: jest.fn(),
dbUpsertAppPlaylist: jest.fn(),
dbUpsertAppPlaylists: jest.fn(),
};
}
@@ -36,12 +36,16 @@ export class RuntimeCapabilitiesService {
}
get supportsSqlite(): boolean {
return (
this.hasElectronMethod('dbGetAppPlaylists') &&
this.hasElectronMethod('dbUpsertAppPlaylist') &&
this.hasElectronMethod('dbGetAppState') &&
this.hasElectronMethod('dbSetAppState')
);
return [
'dbDeleteAllPlaylists',
'dbDeletePlaylist',
'dbGetAppPlaylist',
'dbGetAppPlaylists',
'dbGetAppState',
'dbSetAppState',
'dbUpsertAppPlaylist',
'dbUpsertAppPlaylists',
].every((methodName) => this.hasElectronMethod(methodName));
}
get supportsXtreamSqliteDataSource(): boolean {