mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
refactor(runtime): gate settings backup file save by capability
This commit is contained in:
1 parent
c9a0d5210e
commit
582422c512
4 files changed
+81
-2
No files matched your search
@@ -911,6 +911,65 @@ describe('SettingsComponent', () => {
|
||||
expect(component.isExportingData()).toBe(false);
|
||||
});
|
||||
|
||||
it('falls back to browser backup download when desktop file-save preload is incomplete', async () => {
|
||||
fixture.destroy();
|
||||
const saveFileDialog = jest.fn().mockResolvedValue('/tmp/backup.json');
|
||||
window.electron = {
|
||||
platform: 'linux',
|
||||
saveFileDialog,
|
||||
} as unknown as typeof window.electron;
|
||||
|
||||
const createObjectURL = jest.fn().mockReturnValue('blob:backup');
|
||||
const revokeObjectURL = jest.fn();
|
||||
const originalCreateObjectURL = window.URL.createObjectURL;
|
||||
const originalRevokeObjectURL = window.URL.revokeObjectURL;
|
||||
Object.defineProperty(window.URL, 'createObjectURL', {
|
||||
configurable: true,
|
||||
value: createObjectURL,
|
||||
});
|
||||
Object.defineProperty(window.URL, 'revokeObjectURL', {
|
||||
configurable: true,
|
||||
value: revokeObjectURL,
|
||||
});
|
||||
const clickSpy = jest
|
||||
.spyOn(HTMLAnchorElement.prototype, 'click')
|
||||
.mockImplementation();
|
||||
|
||||
try {
|
||||
const partialFileSaveFixture =
|
||||
TestBed.createComponent(SettingsComponent);
|
||||
const partialFileSaveComponent =
|
||||
partialFileSaveFixture.componentInstance;
|
||||
partialFileSaveComponent.checkAppVersion = jest.fn();
|
||||
partialFileSaveComponent.fetchLocalIpAddresses = jest
|
||||
.fn()
|
||||
.mockResolvedValue(undefined);
|
||||
partialFileSaveFixture.detectChanges();
|
||||
|
||||
expect(partialFileSaveComponent.isDesktop).toBe(true);
|
||||
expect(partialFileSaveComponent.supportsDesktopFileSave).toBe(
|
||||
false
|
||||
);
|
||||
|
||||
await partialFileSaveComponent.exportData();
|
||||
|
||||
expect(saveFileDialog).not.toHaveBeenCalled();
|
||||
expect(createObjectURL).toHaveBeenCalledWith(expect.any(Blob));
|
||||
expect(clickSpy).toHaveBeenCalled();
|
||||
expect(revokeObjectURL).toHaveBeenCalledWith('blob:backup');
|
||||
} finally {
|
||||
clickSpy.mockRestore();
|
||||
Object.defineProperty(window.URL, 'createObjectURL', {
|
||||
configurable: true,
|
||||
value: originalCreateObjectURL,
|
||||
});
|
||||
Object.defineProperty(window.URL, 'revokeObjectURL', {
|
||||
configurable: true,
|
||||
value: originalRevokeObjectURL,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('shows a failure snackbar and skips refresh when clearing EPG data rejects', async () => {
|
||||
(window.electron.clearEpgData as jest.Mock).mockRejectedValueOnce(
|
||||
new Error('boom')
|
||||
|
||||
@@ -137,6 +137,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
/** Flag that indicates whether the app runs in electron environment */
|
||||
readonly isDesktop = this.runtime.isElectron;
|
||||
readonly supportsDesktopFileSave = this.runtime.supportsDesktopFileSave;
|
||||
readonly supportsEpg = this.runtime.supportsEpg;
|
||||
readonly supportsExternalPlayerPathSettings =
|
||||
this.runtime.supportsExternalPlayerPathSettings;
|
||||
@@ -699,7 +700,7 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
try {
|
||||
const backup = await this.playlistBackupService.exportBackup();
|
||||
|
||||
if (this.isDesktop && window.electron?.saveFileDialog) {
|
||||
if (this.supportsDesktopFileSave && window.electron) {
|
||||
const savePath = await window.electron.saveFileDialog(
|
||||
backup.defaultFileName,
|
||||
[
|
||||
|
||||
@@ -79,7 +79,8 @@ panels (`fetchEpg`, `getChannelPrograms`, `checkEpgFreshness`,
|
||||
`searchEpgPrograms`), `supportsPlaylistRefresh` requires the native playlist
|
||||
refresh/cancel/progress bridge, `supportsXtreamSectionNavigation` is available
|
||||
in PWA and in Electron when either the SQLite Xtream data source or the Xtream
|
||||
API transport is available, and
|
||||
API transport is available, `supportsDesktopFileSave` requires both
|
||||
`saveFileDialog` and `writeFile`, and
|
||||
`supportsManagedExternalPlayers` requires the MPV and VLC preload launch and
|
||||
path-setting methods (`openInMpv`, `openInVlc`, `setMpvPlayerPath`, and
|
||||
`setVlcPlayerPath`); a partial Electron bridge must not expose desktop-only
|
||||
|
||||
@@ -258,6 +258,24 @@ describe('RuntimeCapabilitiesService', () => {
|
||||
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(),
|
||||
|
||||
Reference in new issue
Block a user