mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 12:36:15 -08:00
Adds a paused state to the Electron download manager with a full partial-file lifecycle: - Pause keeps the .part and byte progress; cancel discards them; every lifecycle stage (queued, active, mid-transfer) is pausable. - Resume continues via HTTP Range with If-Range entity validation (strong ETag / Last-Modified persisted in the new resume_validator column, idempotent migration incl. legacy-table rebuild). Non-206 answers restart from zero over the same .part; the 206 Content-Range offset is verified; responses that end before the advertised size are retained for a Range retry instead of being committed as completed. - Crash recovery converts interrupted transfers to paused, keeps queued-with-partial rows resumable, and commits finalizations that crashed before the DB update. - Destination collisions are non-destructive (retained partials finalize to the next numbered name); locked .part files never lose their DB owner across cancel/remove/restart; resume claims rows atomically and the queue dedupes ids. - Stored request headers are re-filtered through the User-Agent/Origin/Referer allowlist on read, URL-derived extensions are sanitized, resume appends never follow symlinks, and transfer errors are logged by message only. - UI: pause/resume/cancel/retry/remove surface failures in a snackbar; paused items show an active Resume button in VOD/episode detail views; translations for all 18 locales. - Runtime split into download-runtime/transfer/finalize/broadcast modules; +30 unit tests and an Electron E2E covering pause -> retained .part -> Range/If-Range resume -> byte-exact assembly. Co-authored-by: genrichh93-ui <genrichh93@users.noreply.github.com> 🤖 Generated with [Claude Code](https://claude.com/claude-code)
549 lines
21 KiB
TypeScript
549 lines
21 KiB
TypeScript
import { RuntimeCapabilitiesService } from './runtime-capabilities.service';
|
|
|
|
describe('RuntimeCapabilitiesService', () => {
|
|
const testWindow = window as unknown as {
|
|
electron?: Record<string, unknown>;
|
|
};
|
|
const originalElectron = testWindow.electron;
|
|
|
|
afterEach(() => {
|
|
testWindow.electron = originalElectron;
|
|
});
|
|
|
|
it('reports browser PWA capabilities when the Electron bridge is absent', () => {
|
|
testWindow.electron = undefined;
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.environment).toBe('pwa');
|
|
expect(service.isPwa).toBe(true);
|
|
expect(service.isElectron).toBe(false);
|
|
expect(service.platform).toBeUndefined();
|
|
expect(service.isMacOS).toBe(false);
|
|
expect(service.supportsEpg).toBe(false);
|
|
expect(service.supportsSqlite).toBe(false);
|
|
expect(service.supportsXtreamSqliteDataSource).toBe(false);
|
|
expect(service.supportsDownloads).toBe(false);
|
|
expect(service.supportsPortalActivityStorage).toBe(false);
|
|
expect(service.supportsPlaybackPositionStorage).toBe(false);
|
|
expect(service.supportsPlaybackPositionUpdates).toBe(false);
|
|
expect(service.supportsAppStateStorage).toBe(false);
|
|
expect(service.supportsStalkerPlaylistSqliteSync).toBe(false);
|
|
expect(service.supportsPlaylistRefresh).toBe(false);
|
|
expect(service.supportsManagedExternalPlayers).toBe(false);
|
|
expect(service.supportsExternalPlayerPathSettings).toBe(false);
|
|
expect(service.supportsEmbeddedMpv).toBe(false);
|
|
expect(service.supportsDesktopFileSave).toBe(false);
|
|
expect(service.supportsRemoteControl).toBe(false);
|
|
expect(service.supportsXtreamSectionNavigation).toBe(true);
|
|
expect(service.supportsEpgImport).toBe(false);
|
|
expect(service.supportsEpgProgress).toBe(false);
|
|
expect(service.supportsEpgProgramLookup).toBe(false);
|
|
expect(service.supportsEpgCurrentProgramBatch).toBe(false);
|
|
expect(service.supportsEpgChannelMetadata).toBe(false);
|
|
expect(service.supportsEpgSourceFreshness).toBe(false);
|
|
expect(service.supportsEpgDataManagement).toBe(false);
|
|
expect(service.supportsEpgChannelBrowser).toBe(false);
|
|
expect(service.supportsEpgProgramSearch).toBe(false);
|
|
});
|
|
|
|
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(),
|
|
dbClearRecentlyViewed: jest.fn(),
|
|
dbGetAllGlobalFavorites: jest.fn(),
|
|
dbGetGlobalRecentlyAdded: jest.fn(),
|
|
dbAddFavorite: jest.fn(),
|
|
dbRemoveRecentItem: jest.fn(),
|
|
dbRemoveFavorite: jest.fn(),
|
|
dbGetFavorites: jest.fn(),
|
|
dbReorderGlobalFavorites: jest.fn(),
|
|
dbGetRecentItems: jest.fn(),
|
|
dbAddRecentItem: jest.fn(),
|
|
dbClearPlaylistRecentItems: jest.fn(),
|
|
dbRemoveRecentItemsBatch: jest.fn(),
|
|
dbGetContentByXtreamId: jest.fn(),
|
|
dbGetPlaylist: jest.fn(),
|
|
dbCreatePlaylist: jest.fn(),
|
|
dbUpdatePlaylist: jest.fn(),
|
|
dbHasCategories: jest.fn(),
|
|
dbGetCategories: jest.fn(),
|
|
dbSaveCategories: jest.fn(),
|
|
dbGetAllCategories: jest.fn(),
|
|
dbUpdateCategoryVisibility: jest.fn(),
|
|
dbHasContent: jest.fn(),
|
|
dbGetContent: jest.fn(),
|
|
dbSaveContent: jest.fn(),
|
|
dbSearchContent: jest.fn(),
|
|
dbIsFavorite: jest.fn(),
|
|
dbSavePlaybackPosition: jest.fn(),
|
|
dbGetPlaybackPosition: jest.fn(),
|
|
dbGetSeriesPlaybackPositions: jest.fn(),
|
|
dbGetRecentPlaybackPositions: jest.fn(),
|
|
dbGetAllPlaybackPositions: jest.fn(),
|
|
dbClearAllPlaybackPositions: jest.fn(),
|
|
dbClearPlaybackPosition: jest.fn(),
|
|
onPlaybackPositionUpdate: jest.fn(),
|
|
dbDeleteXtreamContent: jest.fn(),
|
|
dbRestoreXtreamUserData: jest.fn(),
|
|
downloadsStart: jest.fn(),
|
|
downloadsCancel: jest.fn(),
|
|
downloadsPause: jest.fn(),
|
|
downloadsResume: jest.fn(),
|
|
downloadsRetry: jest.fn(),
|
|
downloadsRemove: jest.fn(),
|
|
downloadsGetList: jest.fn(),
|
|
downloadsGet: jest.fn(),
|
|
downloadsGetDefaultFolder: jest.fn(),
|
|
downloadsSelectFolder: jest.fn(),
|
|
downloadsRevealFile: jest.fn(),
|
|
downloadsPlayFile: jest.fn(),
|
|
downloadsClearCompleted: jest.fn(),
|
|
onDownloadsUpdate: jest.fn(),
|
|
refreshPlaylist: jest.fn(),
|
|
cancelPlaylistRefresh: jest.fn(),
|
|
onPlaylistRefreshEvent: jest.fn(),
|
|
openInMpv: jest.fn(),
|
|
openInVlc: jest.fn(),
|
|
setMpvPlayerPath: jest.fn(),
|
|
setVlcPlayerPath: jest.fn(),
|
|
prepareEmbeddedMpv: jest.fn(),
|
|
saveFileDialog: jest.fn(),
|
|
writeFile: jest.fn(),
|
|
updateRemoteControlStatus: jest.fn(),
|
|
onChannelChange: jest.fn(),
|
|
onRemoteControlCommand: jest.fn(),
|
|
xtreamRequest: jest.fn(),
|
|
fetchEpg: jest.fn(),
|
|
getChannelPrograms: jest.fn(),
|
|
getCurrentProgramsBatch: jest.fn(),
|
|
getEpgChannelMetadata: jest.fn(),
|
|
checkEpgFreshness: jest.fn(),
|
|
onEpgProgress: jest.fn(),
|
|
forceFetchEpg: jest.fn(),
|
|
clearEpgData: jest.fn(),
|
|
clearEpgDataForSource: jest.fn(),
|
|
getEpgChannelsByRange: jest.fn(),
|
|
searchEpgPrograms: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.environment).toBe('electron');
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.isPwa).toBe(false);
|
|
expect(service.platform).toBe('darwin');
|
|
expect(service.isMacOS).toBe(true);
|
|
expect(service.supportsEpg).toBe(true);
|
|
expect(service.supportsSqlite).toBe(true);
|
|
expect(service.supportsXtreamSqliteDataSource).toBe(true);
|
|
expect(service.supportsDownloads).toBe(true);
|
|
expect(service.supportsPortalActivityStorage).toBe(true);
|
|
expect(service.supportsPlaybackPositionStorage).toBe(true);
|
|
expect(service.supportsPlaybackPositionUpdates).toBe(true);
|
|
expect(service.supportsAppStateStorage).toBe(true);
|
|
expect(service.supportsStalkerPlaylistSqliteSync).toBe(true);
|
|
expect(service.supportsPlaylistRefresh).toBe(true);
|
|
expect(service.supportsManagedExternalPlayers).toBe(true);
|
|
expect(service.supportsExternalPlayerPathSettings).toBe(true);
|
|
expect(service.supportsEmbeddedMpv).toBe(true);
|
|
expect(service.supportsDesktopFileSave).toBe(true);
|
|
expect(service.supportsRemoteControl).toBe(true);
|
|
expect(service.supportsXtreamSectionNavigation).toBe(true);
|
|
expect(service.supportsEpgImport).toBe(true);
|
|
expect(service.supportsEpgProgress).toBe(true);
|
|
expect(service.supportsEpgProgramLookup).toBe(true);
|
|
expect(service.supportsEpgCurrentProgramBatch).toBe(true);
|
|
expect(service.supportsEpgChannelMetadata).toBe(true);
|
|
expect(service.supportsEpgSourceFreshness).toBe(true);
|
|
expect(service.supportsEpgDataManagement).toBe(true);
|
|
expect(service.supportsEpgChannelBrowser).toBe(true);
|
|
expect(service.supportsEpgProgramSearch).toBe(true);
|
|
});
|
|
|
|
it('keeps feature-specific capabilities false when an Electron bridge is partial', () => {
|
|
testWindow.electron = {
|
|
updateRemoteControlStatus: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.environment).toBe('electron');
|
|
expect(service.platform).toBeUndefined();
|
|
expect(service.isMacOS).toBe(false);
|
|
expect(service.supportsEpg).toBe(false);
|
|
expect(service.supportsSqlite).toBe(false);
|
|
expect(service.supportsXtreamSqliteDataSource).toBe(false);
|
|
expect(service.supportsDownloads).toBe(false);
|
|
expect(service.supportsPortalActivityStorage).toBe(false);
|
|
expect(service.supportsPlaybackPositionStorage).toBe(false);
|
|
expect(service.supportsPlaybackPositionUpdates).toBe(false);
|
|
expect(service.supportsAppStateStorage).toBe(false);
|
|
expect(service.supportsStalkerPlaylistSqliteSync).toBe(false);
|
|
expect(service.supportsPlaylistRefresh).toBe(false);
|
|
expect(service.supportsManagedExternalPlayers).toBe(false);
|
|
expect(service.supportsExternalPlayerPathSettings).toBe(false);
|
|
expect(service.supportsEmbeddedMpv).toBe(false);
|
|
expect(service.supportsDesktopFileSave).toBe(false);
|
|
expect(service.supportsRemoteControl).toBe(false);
|
|
expect(service.supportsXtreamSectionNavigation).toBe(false);
|
|
expect(service.supportsEpgImport).toBe(false);
|
|
expect(service.supportsEpgProgress).toBe(false);
|
|
expect(service.supportsEpgProgramLookup).toBe(false);
|
|
expect(service.supportsEpgCurrentProgramBatch).toBe(false);
|
|
expect(service.supportsEpgChannelMetadata).toBe(false);
|
|
expect(service.supportsEpgSourceFreshness).toBe(false);
|
|
expect(service.supportsEpgDataManagement).toBe(false);
|
|
expect(service.supportsEpgChannelBrowser).toBe(false);
|
|
expect(service.supportsEpgProgramSearch).toBe(false);
|
|
});
|
|
|
|
it('reads the bridge dynamically so tests and late preload setup stay accurate', () => {
|
|
testWindow.electron = undefined;
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.isPwa).toBe(true);
|
|
|
|
testWindow.electron = {};
|
|
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.environment).toBe('electron');
|
|
});
|
|
|
|
it('keeps the Xtream SQLite data source disabled when only generic SQLite methods exist', () => {
|
|
testWindow.electron = createPlaylistStorageBridge();
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.supportsSqlite).toBe(true);
|
|
expect(service.supportsAppStateStorage).toBe(true);
|
|
expect(service.supportsXtreamSqliteDataSource).toBe(false);
|
|
});
|
|
|
|
it('checks app state storage without requiring full playlist SQLite support', () => {
|
|
testWindow.electron = {
|
|
dbGetAppState: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.supportsAppStateStorage).toBe(false);
|
|
|
|
testWindow.electron = {
|
|
dbGetAppState: jest.fn(),
|
|
dbSetAppState: jest.fn(),
|
|
};
|
|
|
|
expect(service.supportsAppStateStorage).toBe(true);
|
|
expect(service.supportsSqlite).toBe(false);
|
|
});
|
|
|
|
it('checks Stalker playlist SQLite sync without requiring the full Xtream data source', () => {
|
|
testWindow.electron = {
|
|
dbGetPlaylist: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.supportsStalkerPlaylistSqliteSync).toBe(false);
|
|
|
|
testWindow.electron = {
|
|
dbGetPlaylist: jest.fn(),
|
|
dbCreatePlaylist: jest.fn(),
|
|
};
|
|
|
|
expect(service.supportsStalkerPlaylistSqliteSync).toBe(true);
|
|
expect(service.supportsXtreamSqliteDataSource).toBe(false);
|
|
});
|
|
|
|
it('decouples external player launch support from path-setting support', () => {
|
|
testWindow.electron = {
|
|
openInMpv: jest.fn(),
|
|
openInVlc: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.supportsManagedExternalPlayers).toBe(true);
|
|
expect(service.supportsExternalPlayerPathSettings).toBe(false);
|
|
|
|
testWindow.electron = {
|
|
openInMpv: jest.fn(),
|
|
openInVlc: jest.fn(),
|
|
setMpvPlayerPath: jest.fn(),
|
|
setVlcPlayerPath: jest.fn(),
|
|
};
|
|
|
|
expect(service.supportsManagedExternalPlayers).toBe(true);
|
|
expect(service.supportsExternalPlayerPathSettings).toBe(true);
|
|
});
|
|
|
|
it('requires the complete EPG preload surface', () => {
|
|
testWindow.electron = {
|
|
fetchEpg: jest.fn(),
|
|
getChannelPrograms: jest.fn(),
|
|
checkEpgFreshness: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.supportsEpg).toBe(false);
|
|
|
|
testWindow.electron = {
|
|
fetchEpg: jest.fn(),
|
|
getChannelPrograms: jest.fn(),
|
|
checkEpgFreshness: jest.fn(),
|
|
forceFetchEpg: jest.fn(),
|
|
clearEpgData: jest.fn(),
|
|
clearEpgDataForSource: jest.fn(),
|
|
getEpgChannelsByRange: jest.fn(),
|
|
searchEpgPrograms: jest.fn(),
|
|
};
|
|
|
|
expect(service.supportsEpg).toBe(true);
|
|
});
|
|
|
|
it('exposes EPG capabilities by the specific preload surface they need', () => {
|
|
testWindow.electron = {
|
|
fetchEpg: jest.fn(),
|
|
onEpgProgress: jest.fn(),
|
|
getChannelPrograms: jest.fn(),
|
|
checkEpgFreshness: jest.fn(),
|
|
forceFetchEpg: jest.fn(),
|
|
clearEpgData: jest.fn(),
|
|
clearEpgDataForSource: jest.fn(),
|
|
getEpgChannelsByRange: jest.fn(),
|
|
searchEpgPrograms: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.supportsEpg).toBe(true);
|
|
expect(service.supportsEpgImport).toBe(true);
|
|
expect(service.supportsEpgProgress).toBe(true);
|
|
expect(service.supportsEpgProgramLookup).toBe(true);
|
|
expect(service.supportsEpgCurrentProgramBatch).toBe(false);
|
|
expect(service.supportsEpgChannelMetadata).toBe(false);
|
|
expect(service.supportsEpgSourceFreshness).toBe(true);
|
|
expect(service.supportsEpgDataManagement).toBe(true);
|
|
expect(service.supportsEpgChannelBrowser).toBe(true);
|
|
expect(service.supportsEpgProgramSearch).toBe(true);
|
|
|
|
testWindow.electron = {
|
|
checkEpgFreshness: jest.fn(),
|
|
};
|
|
|
|
expect(service.supportsEpg).toBe(false);
|
|
expect(service.supportsEpgSourceFreshness).toBe(true);
|
|
expect(service.supportsEpgImport).toBe(false);
|
|
expect(service.supportsEpgProgramLookup).toBe(false);
|
|
expect(service.supportsEpgDataManagement).toBe(false);
|
|
expect(service.supportsEpgChannelBrowser).toBe(false);
|
|
expect(service.supportsEpgProgramSearch).toBe(false);
|
|
});
|
|
|
|
it('requires the complete downloads preload surface', () => {
|
|
testWindow.electron = {
|
|
downloadsGetList: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.supportsDownloads).toBe(false);
|
|
|
|
testWindow.electron = {
|
|
downloadsStart: jest.fn(),
|
|
downloadsCancel: jest.fn(),
|
|
downloadsPause: jest.fn(),
|
|
downloadsResume: jest.fn(),
|
|
downloadsRetry: jest.fn(),
|
|
downloadsRemove: jest.fn(),
|
|
downloadsGetList: jest.fn(),
|
|
downloadsGet: jest.fn(),
|
|
downloadsGetDefaultFolder: jest.fn(),
|
|
downloadsSelectFolder: jest.fn(),
|
|
downloadsRevealFile: jest.fn(),
|
|
downloadsPlayFile: jest.fn(),
|
|
downloadsClearCompleted: jest.fn(),
|
|
onDownloadsUpdate: jest.fn(),
|
|
};
|
|
|
|
expect(service.supportsDownloads).toBe(true);
|
|
});
|
|
|
|
it('requires both desktop file-save preload methods', () => {
|
|
testWindow.electron = {
|
|
saveFileDialog: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.supportsDesktopFileSave).toBe(false);
|
|
|
|
testWindow.electron = {
|
|
saveFileDialog: jest.fn(),
|
|
writeFile: jest.fn(),
|
|
};
|
|
|
|
expect(service.supportsDesktopFileSave).toBe(true);
|
|
});
|
|
|
|
it('requires the complete playlist refresh preload surface', () => {
|
|
testWindow.electron = {
|
|
refreshPlaylist: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.supportsPlaylistRefresh).toBe(false);
|
|
|
|
testWindow.electron = {
|
|
refreshPlaylist: jest.fn(),
|
|
cancelPlaylistRefresh: jest.fn(),
|
|
onPlaylistRefreshEvent: jest.fn(),
|
|
};
|
|
|
|
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);
|
|
});
|
|
|
|
it('checks playback-position storage and update bridge capabilities independently', () => {
|
|
testWindow.electron = {
|
|
dbSavePlaybackPosition: jest.fn(),
|
|
dbGetPlaybackPosition: jest.fn(),
|
|
dbGetSeriesPlaybackPositions: jest.fn(),
|
|
dbGetRecentPlaybackPositions: jest.fn(),
|
|
dbGetAllPlaybackPositions: jest.fn(),
|
|
dbClearAllPlaybackPositions: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.supportsPlaybackPositionStorage).toBe(false);
|
|
expect(service.supportsPlaybackPositionUpdates).toBe(false);
|
|
|
|
testWindow.electron = {
|
|
...testWindow.electron,
|
|
dbClearPlaybackPosition: jest.fn(),
|
|
};
|
|
|
|
expect(service.supportsPlaybackPositionStorage).toBe(true);
|
|
expect(service.supportsPlaybackPositionUpdates).toBe(false);
|
|
|
|
testWindow.electron = {
|
|
...testWindow.electron,
|
|
onPlaybackPositionUpdate: jest.fn(),
|
|
};
|
|
|
|
expect(service.supportsPlaybackPositionStorage).toBe(true);
|
|
expect(service.supportsPlaybackPositionUpdates).toBe(true);
|
|
expect(service.supportsXtreamSqliteDataSource).toBe(false);
|
|
});
|
|
|
|
it('supports Xtream section navigation in Electron when Xtream API transport is available', () => {
|
|
testWindow.electron = {
|
|
xtreamRequest: jest.fn(),
|
|
};
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.supportsXtreamSqliteDataSource).toBe(false);
|
|
expect(service.supportsXtreamSectionNavigation).toBe(true);
|
|
});
|
|
|
|
it('supports Xtream section navigation in Electron when only the SQLite data source is available', () => {
|
|
testWindow.electron = createXtreamSqliteBridge();
|
|
|
|
const service = new RuntimeCapabilitiesService();
|
|
|
|
expect(service.isElectron).toBe(true);
|
|
expect(service.supportsXtreamSqliteDataSource).toBe(true);
|
|
expect(service.supportsXtreamSectionNavigation).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(),
|
|
};
|
|
}
|
|
|
|
function createXtreamSqliteBridge(): Record<string, jest.Mock> {
|
|
return {
|
|
dbAddFavorite: jest.fn(),
|
|
dbAddRecentItem: jest.fn(),
|
|
dbClearAllPlaybackPositions: jest.fn(),
|
|
dbClearPlaybackPosition: jest.fn(),
|
|
dbClearPlaylistRecentItems: jest.fn(),
|
|
dbCreatePlaylist: jest.fn(),
|
|
dbDeletePlaylist: jest.fn(),
|
|
dbDeleteXtreamContent: jest.fn(),
|
|
dbGetAllCategories: jest.fn(),
|
|
dbGetAllPlaybackPositions: jest.fn(),
|
|
dbGetAppState: jest.fn(),
|
|
dbGetCategories: jest.fn(),
|
|
dbGetContent: jest.fn(),
|
|
dbGetContentByXtreamId: jest.fn(),
|
|
dbGetFavorites: jest.fn(),
|
|
dbGetPlaylist: jest.fn(),
|
|
dbGetPlaybackPosition: jest.fn(),
|
|
dbGetRecentItems: jest.fn(),
|
|
dbGetRecentPlaybackPositions: jest.fn(),
|
|
dbGetSeriesPlaybackPositions: jest.fn(),
|
|
dbHasCategories: jest.fn(),
|
|
dbHasContent: jest.fn(),
|
|
dbIsFavorite: jest.fn(),
|
|
dbRemoveFavorite: jest.fn(),
|
|
dbRemoveRecentItem: jest.fn(),
|
|
dbRestoreXtreamUserData: jest.fn(),
|
|
dbSaveCategories: jest.fn(),
|
|
dbSaveContent: jest.fn(),
|
|
dbSavePlaybackPosition: jest.fn(),
|
|
dbSearchContent: jest.fn(),
|
|
dbSetAppState: jest.fn(),
|
|
dbUpdateCategoryVisibility: jest.fn(),
|
|
dbUpdatePlaylist: jest.fn(),
|
|
};
|
|
}
|