diff --git a/packages/bruno-electron/src/app/collection-watcher.js b/packages/bruno-electron/src/app/collection-watcher.js index a1f5621c24..c4aece50ac 100644 --- a/packages/bruno-electron/src/app/collection-watcher.js +++ b/packages/bruno-electron/src/app/collection-watcher.js @@ -552,58 +552,84 @@ const change = async (win, pathname, collectionUid, collectionPath) => { }; const unlink = (win, pathname, collectionUid, collectionPath) => { - console.log(`watcher unlink: ${pathname}`); - - if (isEnvironmentsFolder(pathname, collectionPath)) { - return unlinkEnvironmentFile(win, pathname, collectionUid); - } + try { + if (!fs.existsSync(collectionPath)) { + return; + } + console.log(`watcher unlink: ${pathname}`); - const format = getCollectionFormat(collectionPath); - if (hasRequestExtension(pathname, format)) { - const basename = path.basename(pathname); - const dirname = path.dirname(pathname); + if (isEnvironmentsFolder(pathname, collectionPath)) { + return unlinkEnvironmentFile(win, pathname, collectionUid); + } - if (basename === 'opencollection.yml' && path.normalize(dirname) === path.normalize(collectionPath)) { + let format; + try { + format = getCollectionFormat(collectionPath); + } catch (error) { + console.error(`Error getting collection format for: ${collectionPath}`, error); return; } + if (hasRequestExtension(pathname, format)) { + const basename = path.basename(pathname); + const dirname = path.dirname(pathname); - const file = { - meta: { - collectionUid, - pathname, - name: basename + if (basename === 'opencollection.yml' && path.normalize(dirname) === path.normalize(collectionPath)) { + return; } - }; - win.webContents.send('main:collection-tree-updated', 'unlink', file); + + const file = { + meta: { + collectionUid, + pathname, + name: basename + } + }; + win.webContents.send('main:collection-tree-updated', 'unlink', file); + } + } catch (err) { + console.error(`Error processing unlink event for: ${pathname}`, err); } }; const unlinkDir = async (win, pathname, collectionUid, collectionPath) => { - const envDirectory = path.join(collectionPath, 'environments'); - - if (path.normalize(pathname) === path.normalize(envDirectory)) { - return; - } + try { + if (!fs.existsSync(collectionPath)) { + return; + } + const envDirectory = path.join(collectionPath, 'environments'); - const format = getCollectionFormat(collectionPath); - const folderFilePath = path.join(pathname, `folder.${format}`); + if (path.normalize(pathname) === path.normalize(envDirectory)) { + return; + } - let name = path.basename(pathname); + let format; + try { + format = getCollectionFormat(collectionPath); + } catch (error) { + console.error(`Error getting collection format for: ${collectionPath}`, error); + return; + } + const folderFilePath = path.join(pathname, `folder.${format}`); - if (fs.existsSync(folderFilePath)) { - let folderFileContent = fs.readFileSync(folderFilePath, 'utf8'); - let folderData = await parseFolder(folderFileContent, { format }); - name = folderData?.meta?.name || name; - } + let name = path.basename(pathname); - const directory = { - meta: { - collectionUid, - pathname, - name + if (fs.existsSync(folderFilePath)) { + let folderFileContent = fs.readFileSync(folderFilePath, 'utf8'); + let folderData = await parseFolder(folderFileContent, { format }); + name = folderData?.meta?.name || name; } - }; - win.webContents.send('main:collection-tree-updated', 'unlinkDir', directory); + + const directory = { + meta: { + collectionUid, + pathname, + name + } + }; + win.webContents.send('main:collection-tree-updated', 'unlinkDir', directory); + } catch (err) { + console.error(`Error processing unlinkDir event for: ${pathname}`, err); + } }; const onWatcherSetupComplete = (win, watchPath, collectionUid, watcher) => { diff --git a/tests/collection/delete/delete-collection.spec.ts b/tests/collection/delete/delete-collection.spec.ts new file mode 100644 index 0000000000..bb033672dc --- /dev/null +++ b/tests/collection/delete/delete-collection.spec.ts @@ -0,0 +1,42 @@ +import fs from 'fs'; +import path from 'path'; +import { test, expect } from '../../../playwright'; +import { createCollection, createRequest, deleteCollectionFromOverview } from '../../utils/page'; + +test.describe('Delete collection', () => { + test('Delete collection from workspace overview removes files from disk', async ({ page, createTmpDir }) => { + const collectionName = 'delete-test-collection'; + const tmpDir = await createTmpDir(collectionName); + const collectionPath = path.join(tmpDir, collectionName); + + // Create a collection with a request + await createCollection(page, collectionName, tmpDir); + await createRequest(page, 'ping', collectionName, { url: 'http://localhost:8081/ping' }); + + // Verify collection directory exists on disk + expect(fs.existsSync(collectionPath)).toBe(true); + + // Capture any uncaught errors during deletion + const pageErrors: Error[] = []; + page.on('pageerror', (error) => pageErrors.push(error)); + + // Navigate to Workspace and delete collection from overview + await deleteCollectionFromOverview(page, collectionName); + + // Verify collection is removed from overview + await expect( + page.locator('.collection-card').filter({ hasText: collectionName }) + ).not.toBeVisible(); + + // Verify collection is removed from sidebar + await expect( + page.locator('#sidebar-collection-name').filter({ hasText: collectionName }) + ).not.toBeVisible(); + + // Verify collection directory is deleted from disk + expect(fs.existsSync(collectionPath)).toBe(false); + + // Verify no uncaught JS errors occurred during deletion + expect(pageErrors).toHaveLength(0); + }); +}); diff --git a/tests/utils/page/actions.ts b/tests/utils/page/actions.ts index 30fd5ca4cc..c19d9b2e85 100644 --- a/tests/utils/page/actions.ts +++ b/tests/utils/page/actions.ts @@ -323,6 +323,42 @@ const deleteRequest = async (page, requestName: string, collectionName: string) }); }; +/** + * Delete a collection permanently from disk via the workspace overview page + * @param page - The page object + * @param collectionName - The name of the collection to delete + * @returns void + */ +const deleteCollectionFromOverview = async (page: Page, collectionName: string) => { + await test.step(`Delete collection "${collectionName}" from workspace overview`, async () => { + // Navigate to workspace overview + await page.locator('.home-button').click(); + const overviewTab = page.locator('.request-tab').filter({ hasText: 'Overview' }); + await overviewTab.click(); + + // Find the collection card and open its menu + const collectionCard = page.locator('.collection-card').filter({ hasText: collectionName }); + await collectionCard.waitFor({ state: 'visible', timeout: 5000 }); + await collectionCard.locator('.collection-menu').click(); + + // Click Delete from the dropdown + await page.locator('.dropdown-item').filter({ hasText: 'Delete' }).click(); + + // Wait for delete confirmation modal + const deleteModal = page.locator('.bruno-modal').filter({ hasText: 'Delete Collection' }); + await deleteModal.waitFor({ state: 'visible', timeout: 5000 }); + + // Type 'delete' to confirm + await deleteModal.locator('#delete-confirm-input').fill('delete'); + + // Click the Delete button + await deleteModal.getByRole('button', { name: 'Delete', exact: true }).click(); + + // Wait for modal to close + await deleteModal.waitFor({ state: 'hidden', timeout: 10000 }); + }); +}; + /** * Import a collection from a file * @param page - The page object @@ -1020,6 +1056,7 @@ export { createTransientRequest, fillRequestUrl, deleteRequest, + deleteCollectionFromOverview, importCollection, removeCollection, createFolder,