mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
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:
@@ -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'],
|
||||
},
|
||||
|
||||
@@ -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 = () => (
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
108
tests/collection/open/open-multiple-collections.spec.ts
Normal file
108
tests/collection/open/open-multiple-collections.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
11
tests/utils/page/actions.ts
Normal file
11
tests/utils/page/actions.ts
Normal 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 };
|
||||
1
tests/utils/page/index.ts
Normal file
1
tests/utils/page/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './actions';
|
||||
Reference in New Issue
Block a user