feat: import multiple collections from a parent folder (#5431)

* feat: import multiple collections from a parent folder
* feat: open collections in parallel, revert plural labels, and update playwright tests

---------

Co-authored-by: Bijin Bruno <bijin@usebruno.com>
This commit is contained in:
Jayakrishnan C N
2025-09-30 13:27:20 +05:30
committed by GitHub
parent 56f0741121
commit 14966f6e6c
11 changed files with 165 additions and 11 deletions

View File

@@ -60,6 +60,7 @@ module.exports = runESMImports().then(() => defineConfig([
'@stylistic/function-call-spacing': ['error', 'never'],
'@stylistic/multiline-ternary': ['off'],
'@stylistic/padding-line-between-statements': ['off'],
'@stylistic/jsx-one-expression-per-line': ['off'],
'@stylistic/semi-style': ['error', 'last'],
'@stylistic/max-len': ['off'],
},

View File

@@ -19,7 +19,10 @@ const CreateOrOpenCollection = () => {
const handleOpenCollection = () => {
dispatch(openCollection()).catch(
(err) => console.log(err) && toast.error('An error occurred while opening the collection')
(err) => {
console.log(err);
toast.error('An error occurred while opening the collection');
}
);
};
const CreateLink = () => (

View File

@@ -55,7 +55,10 @@ const TitleBar = () => {
const handleOpenCollection = () => {
dispatch(openCollection()).catch(
(err) => console.log(err) && toast.error('An error occurred while opening the collection')
(err) => {
console.log(err);
toast.error('An error occurred while opening the collection');
}
);
};

View File

@@ -42,15 +42,36 @@ const getCollectionConfigFile = async (pathname) => {
};
const openCollectionDialog = async (win, watcher) => {
const { filePaths } = await dialog.showOpenDialog(win, {
properties: ['openDirectory', 'createDirectory']
const { canceled, filePaths } = await dialog.showOpenDialog(win, {
properties: ['openDirectory', 'createDirectory', 'multiSelections']
});
if (filePaths && filePaths[0]) {
const resolvedPath = path.resolve(filePaths[0]);
if (isDirectory(resolvedPath)) {
openCollection(win, watcher, resolvedPath);
} else {
console.error(`[ERROR] Cannot open unknown folder: "${resolvedPath}"`);
if (!canceled && filePaths?.length > 0) {
// Using Set to remove duplicates
const { openCollectionPromises, invalidPaths } = [...new Set(filePaths)].reduce((acc, filePath) => {
const resolvedPath = path.resolve(filePath);
if (isDirectory(resolvedPath)) {
// Open each valid collection in parallel
acc.openCollectionPromises.push(openCollection(win, watcher, resolvedPath).catch((err) => {
console.error(`[ERROR] Failed to open collection at "${resolvedPath}":`, err.message);
return { error: err, path: resolvedPath };
}));
} else {
acc.invalidPaths.push(resolvedPath);
console.error(`[ERROR] Cannot open unknown folder: "${resolvedPath}"`);
}
return acc;
},
{ openCollectionPromises: [], invalidPaths: [] });
// Wait for all valid collections to be opened
await Promise.all(openCollectionPromises);
// Notify about any invalid paths
if (invalidPaths.length > 0) {
win.webContents.send('main:display-error', `Some selected folders could not be opened: ${invalidPaths.join(', ')}`);
}
}
};
@@ -78,7 +99,7 @@ const openCollection = async (win, watcher, collectionPath, options = {}) => {
} catch (err) {
if (!options.dontSendDisplayErrors) {
win.webContents.send('main:display-error', {
error: err.message || 'An error occurred while opening the local collection'
message: err.message || 'An error occurred while opening the local collection'
});
}
}

View File

@@ -1,6 +1,12 @@
import { test, expect } from '../../../playwright';
import { closeAllCollections } from '../../utils/page';
test.describe('Tag persistence', () => {
test.afterAll(async ({ pageWithUserData: page }) => {
// cleanup: close all collections
await closeAllCollections(page);
});
test('Verify tag persistence while moving requests within a collection', async ({ pageWithUserData: page, createTmpDir }) => {
// Create first collection - click dropdown menu first
await page.getByLabel('Create Collection').click();

View File

@@ -0,0 +1,108 @@
import { test, expect } from '../../../playwright';
import * as path from 'path';
import * as fs from 'fs';
import { closeAllCollections } from '../../utils/page';
test.describe('Open Multiple Collections', () => {
let originalShowOpenDialog;
test.beforeAll(async ({ electronApp }) => {
// save the original showOpenDialog function
await electronApp.evaluate(({ dialog }) => {
originalShowOpenDialog = dialog.showOpenDialog;
});
});
test.afterAll(async ({ electronApp }) => {
// restore the original showOpenDialog function
await electronApp.evaluate(({ dialog }) => {
dialog.showOpenDialog = originalShowOpenDialog;
});
});
test('Should open multiple collections using Open Collection feature', async ({
page,
electronApp,
createTmpDir
}) => {
// Create two test collections with proper bruno.json files
const collection1Dir = await createTmpDir('collection-1');
const collection2Dir = await createTmpDir('collection-2');
// Create bruno.json for first collection
const collection1Config = {
version: '1',
name: 'Test Collection 1',
type: 'collection'
};
// Create bruno.json for second collection
const collection2Config = {
version: '1',
name: 'Test Collection 2',
type: 'collection'
};
fs.writeFileSync(path.join(collection1Dir, 'bruno.json'), JSON.stringify(collection1Config, null, 2));
fs.writeFileSync(path.join(collection2Dir, 'bruno.json'), JSON.stringify(collection2Config, null, 2));
// Mock the electron dialog to return multiple folder selections
await electronApp.evaluate(({ dialog }, { collection1Dir, collection2Dir }) => {
dialog.showOpenDialog = async () => ({
canceled: false,
filePaths: [collection1Dir, collection2Dir]
});
},
{ collection1Dir, collection2Dir });
await expect(page.locator('#sidebar-collection-name').getByText('Test Collection 1')).not.toBeVisible();
// Click on Open Collection(s) button
await page.getByRole('button', { name: 'Open Collection' }).click();
// Wait for both collections to appear in the sidebar
const collection1Element = page.locator('#sidebar-collection-name').getByText('Test Collection 1');
const collection2Element = page.locator('#sidebar-collection-name').getByText('Test Collection 2');
await expect(collection1Element).toBeVisible();
await expect(collection2Element).toBeVisible();
// cleanup: close all collections
await closeAllCollections(page);
});
test('Should handle invalid collection path and display error', async ({
page,
electronApp,
createTmpDir
}) => {
// Directory without bruno.json file
const collection1Dir = await createTmpDir('collection-1');
const collection2Dir = 'invalid-collection-path';
// Mock the electron dialog to return multiple folder selections
await electronApp.evaluate(({ dialog }, { collection1Dir, collection2Dir }) => {
dialog.showOpenDialog = async () => ({
canceled: false,
filePaths: [collection1Dir, collection2Dir]
});
},
{ collection1Dir, collection2Dir });
await expect(page.locator('#sidebar-collection-name').getByText('Test Collection 1')).not.toBeVisible();
// Click on Open Collection(s) button
await page.getByRole('button', { name: 'Open Collection' }).click();
// Verify no collections were opened
await expect(page.locator('#sidebar-collection-name')).toHaveCount(0);
// Verify invalid collection error
const invalidCollectionError = page.getByText('The collection is not valid (bruno.json not found)').first();
await expect(invalidCollectionError).toBeVisible();
// Verify invalid path error
const invalidPathError = page.getByText('Some selected folders could not be opened').getByText('invalid-collection-path').first();
await expect(invalidPathError).toBeVisible();
});
});

View File

@@ -0,0 +1,11 @@
const closeAllCollections = async (page) => {
const numberOfCollections = await page.locator('.collection-name').count();
for (let i = 0; i < numberOfCollections; i++) {
await page.locator('.collection-name').first().locator('.collection-actions').click();
await page.locator('.dropdown-item').getByText('Close').click();
await page.getByRole('button', { name: 'Close' }).click();
}
};
export { closeAllCollections };

View File

@@ -0,0 +1 @@
export * from './actions';