Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/apps/main/backups/add-backup.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as getPathFromDialogModule from '../device/service';
import * as getPathFromDialogModule from '../../../backend/features/backup/get-path-from-dialog';
import * as createBackupModule from './create-backup';
import * as DeviceModuleModule from './../../../backend/features/device/device.module';
import * as enableExistingBackupModule from './enable-existing-backup';
Expand Down
2 changes: 1 addition & 1 deletion src/apps/main/backups/add-backup.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { getPathFromDialog } from '../device/service';
import configStore from '../config';
import { createBackup } from './create-backup';
import { DeviceModule } from '../../../backend/features/device/device.module';
import { logger } from '@internxt/drive-desktop-core/build/backend';
import { enableExistingBackup } from './enable-existing-backup';
import { getPathFromDialog } from '../../../backend/features/backup/get-path-from-dialog';

export async function addBackup() {
const { error, data } = await DeviceModule.getOrCreateDevice();
Expand Down
2 changes: 1 addition & 1 deletion src/apps/main/device/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import {
disableBackup,
downloadBackup,
getDevices,
getPathFromDialog,
} from './service';
import { DeviceModule } from '../../../backend/features/device/device.module';
import { addBackup } from '../backups/add-backup';
import { getPathFromDialog } from '../../../backend/features/backup/get-path-from-dialog';

ipcMain.handle('devices.get-all', () => getDevices());

Expand Down
25 changes: 1 addition & 24 deletions src/apps/main/device/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { getBackupFolderUuid } from '../../../infra/drive-server/services/backup
import { updateBackupFolderName } from '../../../infra/drive-server/services/backup/services/update-backup-folder-metadata';
import { migrateBackupEntryIfNeeded } from './migrate-backup-entry-if-needed';
import { createBackup } from '../backups/create-backup';
import { getPathFromDialog } from '../../../backend/features/backup/get-path-from-dialog';

export type Device = {
id: number;
Expand Down Expand Up @@ -314,30 +315,6 @@ export type PathInfo = {
isDirectory?: boolean;
};

export async function getPathFromDialog(): Promise<{
path: string;
itemName: string;
} | null> {
const result = await dialog.showOpenDialog({
properties: ['openDirectory'],
});

if (result.canceled) {
return null;
}

const chosenPath = result.filePaths[0];

const itemPath = chosenPath + (chosenPath[chosenPath.length - 1] === path.sep ? '' : path.sep);

const itemName = path.basename(itemPath);

return {
path: itemPath,
itemName,
};
}

export async function getMultiplePathsFromDialog(allowFiles = false): Promise<PathInfo[] | null> {
const result = await dialog.showOpenDialog({
properties: ['multiSelections' as const, ...(allowFiles ? (['openFile'] as const) : ['openDirectory' as const])],
Expand Down
2 changes: 1 addition & 1 deletion src/apps/main/preload.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ declare interface Window {

changeBackupPath: typeof import('../main/device/service').changeBackupPath;

getFolderPath: typeof import('../main/device/service').getPathFromDialog;
getFolderPath: typeof import('../../backend/features/backup/get-path-from-dialog').getPathFromDialog;

onRemoteChanges(func: (value: import('../main/realtime').EventPayload) => void): () => void;

Expand Down
121 changes: 121 additions & 0 deletions src/backend/features/backup/get-path-from-dialog.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { BrowserWindow, dialog } from 'electron';
import { call } from 'tests/vitest/utils.helper';
import { getPathFromDialog } from './get-path-from-dialog';
import path from 'node:path';

const mockWindow = {
hide: vi.fn(),
show: vi.fn(),
isVisible: vi.fn().mockReturnValue(true),
isDestroyed: vi.fn().mockReturnValue(false),
};
Comment on lines +6 to +11

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid having to create this fake object here, you can create a mock using mockDeep.

This way, you save yourself from having to define functions in the object manually. The only thing you would have to do is manually define the return of the isVisible and isDestroyed functions in beforeEach.

Suggested change
const mockWindow = {
hide: vi.fn(),
show: vi.fn(),
isVisible: vi.fn().mockReturnValue(true),
isDestroyed: vi.fn().mockReturnValue(false),
};
const mockWindow = mockDeep<BrowserWindow>();


vi.mock('electron', () => ({
dialog: {
showOpenDialog: vi.fn(),
},
BrowserWindow: {
getFocusedWindow: vi.fn(),
getAllWindows: vi.fn(),
},
}));
Comment on lines +13 to +21

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These functions can be added to the same object in vitest.setup.main.ts to avoid duplication.


describe('getPathFromDialog', () => {
const mockedDialog = vi.mocked(dialog.showOpenDialog);
const mockedGetFocusedWindow = vi.mocked(BrowserWindow.getFocusedWindow);
const mockedGetAllWindows = vi.mocked(BrowserWindow.getAllWindows);
Comment on lines +24 to +26

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use partialSpyOn helper intead of vi.mocked


beforeEach(() => {
mockedGetFocusedWindow.mockReturnValue(mockWindow as unknown as BrowserWindow);
mockedGetAllWindows.mockReturnValue([mockWindow] as unknown as BrowserWindow[]);
});
Comment on lines +28 to +31

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
beforeEach(() => {
mockedGetFocusedWindow.mockReturnValue(mockWindow as unknown as BrowserWindow);
mockedGetAllWindows.mockReturnValue([mockWindow] as unknown as BrowserWindow[]);
});
beforeEach(() => {
mockedGetFocusedWindow.mockReturnValue(mockWindow as unknown as BrowserWindow);
mockedGetAllWindows.mockReturnValue([mockWindow] as unknown as BrowserWindow[]);
mockWindow.isVisible.mockReturnValue(true);
mockWindow.isDestroyed.mockReturnValue(false);
});


it('should hide the focused window before opening the dialog', async () => {
mockedDialog.mockResolvedValue({ canceled: true, filePaths: [] });

await getPathFromDialog();

expect(mockWindow.hide).toHaveBeenCalled();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use toBeCalled instead toHaveBeenCalled

});

it('should show the window after the dialog closes', async () => {
mockedDialog.mockResolvedValue({ canceled: true, filePaths: [] });

await getPathFromDialog();

expect(mockWindow.show).toHaveBeenCalled();
});

it('should not show the window if it was destroyed', async () => {
mockWindow.isDestroyed.mockReturnValue(true);
mockedDialog.mockResolvedValue({ canceled: true, filePaths: [] });

await getPathFromDialog();

expect(mockWindow.show).not.toHaveBeenCalled();
});

it('should use a visible window when no focused window exists', async () => {
mockedGetFocusedWindow.mockReturnValue(null);

mockedDialog.mockResolvedValue({ canceled: true, filePaths: [] });

await getPathFromDialog();

expect(mockWindow.hide).toHaveBeenCalled();
});

it('should return null when the dialog is canceled', async () => {
mockedDialog.mockResolvedValue({ canceled: true, filePaths: [] });

const result = await getPathFromDialog();

expect(result).toBe(null);
});

it('should return the path with a trailing separator and the item name', async () => {
mockedDialog.mockResolvedValue({ canceled: false, filePaths: ['/home/user/Documents'] });

const result = await getPathFromDialog();

expect(result).toStrictEqual({
path: `/home/user/Documents${path.sep}`,
itemName: 'Documents',
});
});

it('should not duplicate the separator if the path already ends with one', async () => {
mockedDialog.mockResolvedValue({ canceled: false, filePaths: [`/home/user/Documents${path.sep}`] });

const result = await getPathFromDialog();

expect(result).toStrictEqual({
path: `/home/user/Documents${path.sep}`,
itemName: 'Documents',
});
});

it('should open the dialog with openDirectory property', async () => {
mockedDialog.mockResolvedValue({ canceled: true, filePaths: [] });

await getPathFromDialog();

call(mockedDialog).toMatchObject({ properties: ['openDirectory'] });
});

it('should skip hide and show and still return the selected path when no BackupFolderSelector window exists', async () => {
mockedGetFocusedWindow.mockReturnValue(null);
mockedGetAllWindows.mockReturnValue([]);

mockedDialog.mockResolvedValue({ canceled: false, filePaths: ['/home/user/folder'] });

const result = await getPathFromDialog();

expect(mockWindow.hide).not.toHaveBeenCalled();
expect(mockWindow.show).not.toHaveBeenCalled();
expect(result).toStrictEqual({
path: `/home/user/folder${path.sep}`,
itemName: 'folder',
});
});
});
34 changes: 34 additions & 0 deletions src/backend/features/backup/get-path-from-dialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { BrowserWindow, dialog } from 'electron';
import { PathInfo } from '../../../apps/main/device/service';
import path from 'node:path';

export async function getPathFromDialog(): Promise<Omit<PathInfo, 'isDirectory'> | null> {
const parentWindow = BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows().find((w) => w.isVisible());

if (parentWindow) {
parentWindow.hide();
}

const result = await dialog.showOpenDialog({
properties: ['openDirectory'],
});

if (parentWindow && !parentWindow.isDestroyed()) {
parentWindow.show();
}

if (result.canceled) {
return null;
}

const chosenPath = result.filePaths[0];

const itemPath = `${chosenPath}${chosenPath.endsWith(path.sep) ? '' : path.sep}`;

const itemName = path.basename(itemPath);

return {
path: itemPath,
itemName,
};
}
Loading