diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
index e7a3a21ea..1b09cb7e3 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/index.js
@@ -60,8 +60,8 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
const [dropType, setDropType] = useState(null); // 'adjacent' or 'inside'
const [{ isDragging }, drag, dragPreview] = useDrag({
- type: `collection-item-${collectionUid}`,
- item,
+ type: 'collection-item',
+ item: { ...item, sourceCollectionUid: collectionUid },
collect: (monitor) => ({
isDragging: monitor.isDragging()
}),
@@ -92,10 +92,15 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
const canItemBeDropped = ({ draggedItem, targetItem, dropType }) => {
const { uid: targetItemUid, pathname: targetItemPathname } = targetItem;
- const { uid: draggedItemUid, pathname: draggedItemPathname } = draggedItem;
+ const { uid: draggedItemUid, pathname: draggedItemPathname, sourceCollectionUid } = draggedItem;
if (draggedItemUid === targetItemUid) return false;
+ // For cross-collection moves, we allow the drop
+ if (sourceCollectionUid !== collectionUid) {
+ return true;
+ }
+
const newPathname = calculateDraggedItemNewPathname({ draggedItem, targetItem, dropType, collectionPathname });
if (!newPathname) return false;
@@ -105,7 +110,7 @@ const CollectionItem = ({ item, collectionUid, collectionPathname, searchText })
};
const [{ isOver, canDrop }, drop] = useDrop({
- accept: `collection-item-${collectionUid}`,
+ accept: 'collection-item',
hover: (draggedItem, monitor) => {
const { uid: targetItemUid } = item;
const { uid: draggedItemUid } = draggedItem;
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js
index 8c1111c29..dc2e05251 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/StyledWrapper.js
@@ -7,6 +7,7 @@ const Wrapper = styled.div`
user-select: none;
padding-left: 8px;
font-weight: 600;
+ border: ${(props) => props.theme.dragAndDrop.borderStyle} transparent;
.rotate-90 {
transform: rotateZ(90deg);
@@ -66,6 +67,7 @@ const Wrapper = styled.div`
}
&.drop-target {
+ border: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border};
background-color: ${(props) => props.theme.dragAndDrop.hoverBg};
transition: ${(props) => props.theme.dragAndDrop.transition};
}
@@ -95,15 +97,6 @@ const Wrapper = styled.div`
}
}
- .collection-name.drop-target {
- border: ${(props) => props.theme.dragAndDrop.borderStyle} ${(props) => props.theme.dragAndDrop.border};
- border-radius: 4px;
- background-color: ${(props) => props.theme.dragAndDrop.hoverBg};
- margin: -2px;
- transition: ${(props) => props.theme.dragAndDrop.transition};
- box-shadow: 0 0 0 2px ${(props) => props.theme.dragAndDrop.hoverBg};
- }
-
#sidebar-collection-name {
white-space: nowrap;
text-overflow: ellipsis;
diff --git a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
index 67ffcb68b..3152bef8c 100644
--- a/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
+++ b/packages/bruno-app/src/components/Sidebar/Collections/Collection/index.js
@@ -34,6 +34,7 @@ const Collection = ({ collection, searchText }) => {
const [showCloneCollectionModalOpen, setShowCloneCollectionModalOpen] = useState(false);
const [showShareCollectionModal, setShowShareCollectionModal] = useState(false);
const [showRemoveCollectionModal, setShowRemoveCollectionModal] = useState(false);
+ const [dropType, setDropType] = useState(null);
const dispatch = useDispatch();
const isLoading = areItemsLoading(collection);
const collectionRef = useRef(null);
@@ -42,7 +43,7 @@ const Collection = ({ collection, searchText }) => {
const menuDropdownTippyRef = useRef();
const onMenuDropdownCreate = (ref) => (menuDropdownTippyRef.current = ref);
- const MenuIcon = forwardRef((props, ref) => {
+ const MenuIcon = forwardRef((_props, ref) => {
return (
@@ -101,7 +102,7 @@ const Collection = ({ collection, searchText }) => {
}
};
- const handleDoubleClick = (event) => {
+ const handleDoubleClick = (_event) => {
dispatch(makeTabPermanent({ uid: collection.uid }))
};
@@ -118,7 +119,7 @@ const Collection = ({ collection, searchText }) => {
e.preventDefault();
};
- const handleRightClick = (event) => {
+ const handleRightClick = (_event) => {
const _menuDropdown = menuDropdownTippyRef.current;
if (_menuDropdown) {
let menuDropdownBehavior = 'show';
@@ -140,7 +141,7 @@ const Collection = ({ collection, searchText }) => {
};
const isCollectionItem = (itemType) => {
- return itemType.startsWith('collection-item');
+ return itemType === 'collection-item';
};
const [{ isDragging }, drag, dragPreview] = useDrag({
@@ -155,7 +156,17 @@ const Collection = ({ collection, searchText }) => {
});
const [{ isOver }, drop] = useDrop({
- accept: ["collection", `collection-item-${collection.uid}`],
+ accept: ["collection", "collection-item"],
+ hover: (_draggedItem, monitor) => {
+ const itemType = monitor.getItemType();
+ if (isCollectionItem(itemType)) {
+ // For collection items, always show full highlight (inside drop)
+ setDropType('inside');
+ } else {
+ // For collections, show line indicator (adjacent drop)
+ setDropType('adjacent');
+ }
+ },
drop: (draggedItem, monitor) => {
const itemType = monitor.getItemType();
if (isCollectionItem(itemType)) {
@@ -163,6 +174,7 @@ const Collection = ({ collection, searchText }) => {
} else {
dispatch(moveCollectionAndPersist({draggedItem, targetItem: collection}));
}
+ setDropType(null);
},
canDrop: (draggedItem) => {
return draggedItem.uid !== collection.uid;
@@ -183,7 +195,8 @@ const Collection = ({ collection, searchText }) => {
}
const collectionRowClassName = classnames('flex py-1 collection-name items-center', {
- 'item-hovered': isOver,
+ 'item-hovered': isOver && dropType === 'adjacent', // For collection-to-collection moves (show line)
+ 'drop-target': isOver && dropType === 'inside', // For collection-item drops (highlight full area)
'collection-focused-in-tab': isCollectionFocused
});
@@ -241,7 +254,7 @@ const Collection = ({ collection, searchText }) => {
} placement="bottom-start">
{
+ onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowNewRequestModal(true);
}}
@@ -250,7 +263,7 @@ const Collection = ({ collection, searchText }) => {
{
+ onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowNewFolderModal(true);
}}
@@ -259,7 +272,7 @@ const Collection = ({ collection, searchText }) => {
{
+ onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowCloneCollectionModalOpen(true);
}}
@@ -268,7 +281,7 @@ const Collection = ({ collection, searchText }) => {
{
+ onClick={(_e) => {
menuDropdownTippyRef.current.hide();
ensureCollectionIsMounted();
handleRun();
@@ -278,7 +291,7 @@ const Collection = ({ collection, searchText }) => {
{
+ onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowRenameCollectionModal(true);
}}
@@ -287,7 +300,7 @@ const Collection = ({ collection, searchText }) => {
{
+ onClick={(_e) => {
menuDropdownTippyRef.current.hide();
ensureCollectionIsMounted();
setShowShareCollectionModal(true);
@@ -297,7 +310,7 @@ const Collection = ({ collection, searchText }) => {
{
+ onClick={(_e) => {
menuDropdownTippyRef.current.hide();
setShowRemoveCollectionModal(true);
}}
@@ -306,7 +319,7 @@ const Collection = ({ collection, searchText }) => {
{
+ onClick={(_e) => {
menuDropdownTippyRef.current.hide();
viewCollectionSettings();
}}
diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
index 2787016c4..c341cf4f5 100644
--- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
+++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/actions.js
@@ -726,11 +726,16 @@ export const handleCollectionItemDrop =
(dispatch, getState) => {
const state = getState();
const collection = findCollectionByUid(state.collections.collections, collectionUid);
+ // if its withincollection set the source to current collection,
+ // if its cross collection set the source to the source collection
+ const sourceCollectionUid = draggedItem.sourceCollectionUid
+ const isCrossCollectionMove = sourceCollectionUid && collectionUid !== sourceCollectionUid;
+ const sourceCollection = isCrossCollectionMove ? findCollectionByUid(state.collections.collections, sourceCollectionUid) : collection;
const { uid: draggedItemUid, pathname: draggedItemPathname } = draggedItem;
const { uid: targetItemUid, pathname: targetItemPathname } = targetItem;
const targetItemDirectory = findParentItemInCollection(collection, targetItemUid) || collection;
const targetItemDirectoryItems = cloneDeep(targetItemDirectory.items);
- const draggedItemDirectory = findParentItemInCollection(collection, draggedItemUid) || collection;
+ const draggedItemDirectory = findParentItemInCollection(sourceCollection, draggedItemUid) || sourceCollection;
const draggedItemDirectoryItems = cloneDeep(draggedItemDirectory.items);
const handleMoveToNewLocation = async ({
diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js
index 30cf11111..f26ec6a2c 100644
--- a/packages/bruno-electron/src/ipc/collection.js
+++ b/packages/bruno-electron/src/ipc/collection.js
@@ -699,7 +699,7 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
// Recursive function to parse the folder and create files/folders
const parseCollectionItems = (items = [], currentPath) => {
items.forEach(async (item) => {
- if (['http-request', 'graphql-request'].includes(item.type)) {
+ if (['http-request', 'graphql-request', 'grpc-request'].includes(item.type)) {
const content = await stringifyRequestViaWorker(item);
const filePath = path.join(currentPath, item.filename);
safeWriteFileSync(filePath, content);
diff --git a/tests/collection/moving-requests/cross-collection-drag-drop-folder.spec.ts b/tests/collection/moving-requests/cross-collection-drag-drop-folder.spec.ts
new file mode 100644
index 000000000..9fce391ff
--- /dev/null
+++ b/tests/collection/moving-requests/cross-collection-drag-drop-folder.spec.ts
@@ -0,0 +1,235 @@
+import { test, expect } from '../../../playwright';
+
+test.describe('Cross-Collection Drag and Drop for folder', () => {
+ test('Verify cross-collection folder drag and drop', async ({ pageWithUserData: page, createTmpDir }) => {
+ // Create first collection - click dropdown menu first
+ await page.locator('.dropdown-icon').click();
+ await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
+ await page.getByLabel('Name').fill('source-collection');
+ await page.getByLabel('Location').fill(await createTmpDir('source-collection'));
+ await page.getByRole('button', { name: 'Create', exact: true }).click();
+
+ // Wait for collection to appear and click on it
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
+ await page.getByLabel('Safe Mode').check();
+ await page.getByRole('button', { name: 'Save' }).click();
+
+ // Create a folder in the first collection
+ // Look for the collection menu button (usually three dots or similar)
+ await page.locator('.collection-actions').hover();
+ await page.locator('.collection-actions .icon').click();
+ await page.locator('.dropdown-item').filter({ hasText: 'New Folder' }).click();
+
+ // Fill folder name in the modal
+ await expect(page.locator('#collection-name')).toBeVisible();
+ await page.locator('#collection-name').fill('test-folder');
+ await page.getByRole('button', { name: 'Create' }).click();
+
+ // Wait for the folder to be created and appear in the sidebar
+ await page.waitForTimeout(2000);
+ await expect(page.locator('.collection-item-name').filter({ hasText: 'test-folder' })).toBeVisible();
+
+ // Add a request to the folder to make it more realistic
+ await page.locator('.collection-item-name').filter({ hasText: 'test-folder' }).click({ button: 'right' });
+ await page.locator('.dropdown-item').filter({ hasText: 'New Request' }).click();
+ await page.getByPlaceholder('Request Name').fill('test-request-in-folder');
+ await page.locator('#new-request-url .CodeMirror').click();
+ await page.locator('textarea').fill('https://httpbin.org/get');
+ await page.getByRole('button', { name: 'Create' }).click();
+
+ // Wait for the request to be created
+ await page.waitForTimeout(1000);
+
+ // Expand the folder to see the request inside
+ await page.locator('.collection-item-name').filter({ hasText: 'test-folder' }).click();
+ await page.waitForTimeout(500);
+ await expect(page.locator('.collection-item-name').filter({ hasText: 'test-request-in-folder' })).toBeVisible();
+
+ // Create second collection - click dropdown menu first
+ await page.locator('.dropdown-icon').click();
+ await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
+ await page.getByLabel('Name').fill('target-collection');
+ await page.getByLabel('Location').fill(await createTmpDir('target-collection'));
+ await page.getByRole('button', { name: 'Create', exact: true }).click();
+
+ // Wait for second collection to appear and click on it
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
+ await page.getByLabel('Safe Mode').check();
+ await page.getByRole('button', { name: 'Save' }).click();
+
+ // Wait for both collections to be visible in sidebar
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
+
+ // Locate the folder in source collection
+ const sourceFolder = page.locator('.collection-item-name').filter({ hasText: 'test-folder' });
+ await expect(sourceFolder).toBeVisible();
+
+ // Locate the target collection area (the collection name element)
+ const targetCollection = page.locator('.collection-name').filter({ hasText: 'target-collection' });
+ await expect(targetCollection).toBeVisible();
+
+ // Perform drag and drop operation
+ await sourceFolder.dragTo(targetCollection);
+
+ // Wait for the operation to complete
+ await page.waitForTimeout(3000);
+
+ // Verify the folder has been moved to the target collection
+ // Click on target collection to expand it if needed
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
+ await page.waitForTimeout(1000);
+
+ // Check that the folder now appears under target collection
+ const targetCollectionContainer = page
+ .locator('.collection-name')
+ .filter({ hasText: 'target-collection' })
+ .locator('..');
+ await expect(
+ targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-folder' })
+ ).toBeVisible();
+
+ // Expand the moved folder to verify the request inside is also moved
+ await targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-folder' }).click();
+ await page.waitForTimeout(500);
+ await expect(
+ targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-request-in-folder' })
+ ).toBeVisible();
+
+ // Verify the folder is no longer in the source collection
+ const sourceCollectionContainer = page
+ .locator('.collection-name')
+ .filter({ hasText: 'source-collection' })
+ .locator('..');
+ await expect(
+ sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-folder' })
+ ).not.toBeVisible();
+
+ // Verify the request is also no longer in the source collection
+ await expect(
+ sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-request-in-folder' })
+ ).not.toBeVisible();
+ });
+
+ test('Verify cross-collection folder drag and drop, a duplicate folder exist. expected to throw error toast', async ({
+ pageWithUserData: page,
+ createTmpDir
+ }) => {
+ // Create first collection (source) - use unique names for this test
+ await page.locator('.dropdown-icon').click();
+ await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
+ await page.getByLabel('Name').fill('source-collection');
+ await page.getByLabel('Location').fill(await createTmpDir('source-collection'));
+ await page.getByRole('button', { name: 'Create', exact: true }).click();
+
+ // Wait for collection to appear and click on it
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
+ await page.getByLabel('Safe Mode').check();
+ await page.getByRole('button', { name: 'Save' }).click();
+
+ // Create a folder in the first collection
+ await page
+ .locator('.collection-name')
+ .filter({ hasText: 'source-collection' })
+ .locator('..')
+ .locator('.collection-actions')
+ .hover();
+ await page
+ .locator('.collection-name')
+ .filter({ hasText: 'source-collection' })
+ .locator('..')
+ .locator('.collection-actions .icon')
+ .click();
+ await page.locator('.dropdown-item').filter({ hasText: 'New Folder' }).click();
+ await expect(page.locator('#collection-name')).toBeVisible();
+ await page.locator('#collection-name').fill('folder-1');
+ await page.getByRole('button', { name: 'Create' }).click();
+
+ await expect(page.locator('.collection-item-name').filter({ hasText: 'folder-1' })).toBeVisible();
+
+ // Add a request to the folder to make it more realistic
+ await page.locator('.collection-item-name').filter({ hasText: 'folder-1' }).click({ button: 'right' });
+ await page.locator('.dropdown-item').filter({ hasText: 'New Request' }).click();
+ await page.getByPlaceholder('Request Name').fill('http-request');
+ await page.locator('#new-request-url .CodeMirror').click();
+ await page.locator('textarea').fill('https://httpbin.org/get');
+ await page.getByRole('button', { name: 'Create' }).click();
+ // Expand the folder to see the request inside
+ await page.locator('.collection-item-name').filter({ hasText: 'folder-1' }).click();
+ await expect(page.locator('.collection-item-name').filter({ hasText: 'http-request' })).toBeVisible();
+
+ // Create second collection (target)
+ await page.locator('.dropdown-icon').click();
+ await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
+ await page.getByLabel('Name').fill('target-collection');
+ await page.getByLabel('Location').fill(await createTmpDir('target-collection'));
+ await page.getByRole('button', { name: 'Create', exact: true }).click();
+
+ // Wait for second collection to appear and click on it
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
+ await page.getByLabel('Safe Mode').check();
+ await page.getByRole('button', { name: 'Save' }).click();
+
+ // Create a folder with the same name in the target collection
+ await page
+ .locator('.collection-name')
+ .filter({ hasText: 'target-collection' })
+ .locator('..')
+ .locator('.collection-actions')
+ .hover();
+ await page
+ .locator('.collection-name')
+ .filter({ hasText: 'target-collection' })
+ .locator('..')
+ .locator('.collection-actions .icon')
+ .click();
+ await page.locator('.dropdown-item').filter({ hasText: 'New Folder' }).click();
+ await expect(page.locator('#collection-name')).toBeVisible();
+ await page.locator('#collection-name').fill('folder-1');
+ await page.getByRole('button', { name: 'Create' }).click();
+
+ // Go back to source collection to drag the folder
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
+
+ // Verify we have the folder to drag in the source collection
+ const sourceFolder = page.locator('.collection-item-name').filter({ hasText: 'folder-1' }).first();
+ await expect(sourceFolder).toBeVisible();
+
+ // Locate the target collection area
+ const targetCollection = page.locator('.collection-name').filter({ hasText: 'target-collection' });
+ await expect(targetCollection).toBeVisible();
+
+ // Perform drag and drop operation
+ await sourceFolder.dragTo(targetCollection);
+
+ // check for error toast notification
+ await expect(page.getByText(/Error: Cannot copy.*already exists/i)).toBeVisible();
+
+ // source and target collection request should remain unchanged
+ const sourceCollectionContainer = page
+ .locator('.collection-name')
+ .filter({ hasText: 'source-collection' })
+ .locator('..');
+ await expect(
+ sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'folder-1' })
+ ).toBeVisible();
+ await expect(
+ sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'http-request' })
+ ).toBeVisible();
+
+ const targetCollectionContainer = page
+ .locator('.collection-name')
+ .filter({ hasText: 'target-collection' })
+ .locator('..');
+ await expect(
+ targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'folder-1' })
+ ).toBeVisible();
+ await expect(
+ targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'http-request' })
+ ).not.toBeVisible();
+ });
+});
diff --git a/tests/collection/moving-requests/cross-collection-drag-drop-request.spec.ts b/tests/collection/moving-requests/cross-collection-drag-drop-request.spec.ts
new file mode 100644
index 000000000..598ac858a
--- /dev/null
+++ b/tests/collection/moving-requests/cross-collection-drag-drop-request.spec.ts
@@ -0,0 +1,154 @@
+import { test, expect } from '../../../playwright';
+
+test.describe('Cross-Collection Drag and Drop', () => {
+ test('Verify request drag and drop', async ({ pageWithUserData: page, createTmpDir }) => {
+ // Create first collection - click dropdown menu first
+ await page.locator('.dropdown-icon').click();
+ await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
+ await page.getByLabel('Name').fill('source-collection');
+ await page.getByLabel('Location').fill(await createTmpDir('source-collection'));
+ await page.getByRole('button', { name: 'Create', exact: true }).click();
+
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
+ await page.getByLabel('Safe Mode').check();
+ await page.getByRole('button', { name: 'Save' }).click();
+
+ // Create a request in the first collection
+ await page.locator('#create-new-tab').getByRole('img').click();
+ await page.getByPlaceholder('Request Name').fill('test-request');
+ await page.locator('#new-request-url .CodeMirror').click();
+ await page.locator('textarea').fill('https://httpbin.org/get');
+ await page.getByRole('button', { name: 'Create' }).click();
+
+ await expect(page.locator('.collection-item-name').filter({ hasText: 'test-request' })).toBeVisible();
+
+ // Create second collection - click dropdown menu first
+ await page.locator('.dropdown-icon').click();
+ await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
+ await page.getByLabel('Name').fill('target-collection');
+ await page.getByLabel('Location').fill(await createTmpDir('target-collection'));
+ await page.getByRole('button', { name: 'Create', exact: true }).click();
+
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
+ await page.getByLabel('Safe Mode').check();
+ await page.getByRole('button', { name: 'Save' }).click();
+
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
+
+ // Locate the request in source collection
+ const sourceRequest = page.locator('.collection-item-name').filter({ hasText: 'test-request' });
+ await expect(sourceRequest).toBeVisible();
+
+ // Locate the target collection area (the collection name element)
+ const targetCollection = page.locator('.collection-name').filter({ hasText: 'target-collection' });
+ await expect(targetCollection).toBeVisible();
+
+ // Perform drag and drop operation
+ await sourceRequest.dragTo(targetCollection);
+
+ // Verify the request has been moved to the target collection
+ // Click on target collection to expand it if needed
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
+
+ // Check that the request now appears under target collection
+ const targetCollectionContainer = page
+ .locator('.collection-name')
+ .filter({ hasText: 'target-collection' })
+ .locator('..');
+ await expect(
+ targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-request' })
+ ).toBeVisible();
+
+ // Verify the request is no longer in the source collection
+ const sourceCollectionContainer = page
+ .locator('.collection-name')
+ .filter({ hasText: 'source-collection' })
+ .locator('..');
+ await expect(
+ sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'test-request' })
+ ).not.toBeVisible();
+ });
+
+ test('Expected to show error toast message, when duplicate request found in drop location', async ({
+ pageWithUserData: page,
+ createTmpDir
+ }) => {
+ // Create first collection (source-collection)
+ await page.locator('.dropdown-icon').click();
+ await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
+ await page.getByLabel('Name').fill('source-collection');
+ await page.getByLabel('Location').fill(await createTmpDir('source-collection'));
+ await page.getByRole('button', { name: 'Create', exact: true }).click();
+
+ // Open collection
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' })).toBeVisible();
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
+ await page.getByLabel('Safe Mode').check();
+ await page.getByRole('button', { name: 'Save' }).click();
+
+ // Create a request in the first collection (request-1)
+ await page.locator('#create-new-tab').getByRole('img').click();
+ await page.getByPlaceholder('Request Name').fill('request-1');
+ await page.locator('#new-request-url .CodeMirror').click();
+ await page.locator('textarea').fill('https://httpbin.org/get');
+ await page.getByRole('button', { name: 'Create' }).click();
+
+ // check if request-1 is created and visible in sidebar
+ await expect(page.locator('.collection-item-name').filter({ hasText: 'request-1' })).toBeVisible();
+
+ // Create second collection (target-collection)
+ await page.locator('.dropdown-icon').click();
+ await page.locator('.dropdown-item').filter({ hasText: 'Create Collection' }).click();
+ await page.getByLabel('Name').fill('target-collection');
+ await page.getByLabel('Location').fill(await createTmpDir('target-collection'));
+ await page.getByRole('button', { name: 'Create', exact: true }).click();
+
+ // Open collection
+ await expect(page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' })).toBeVisible();
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'target-collection' }).click();
+ await page.getByLabel('Safe Mode').check();
+ await page.getByRole('button', { name: 'Save' }).click();
+
+ // Create a request in the target collection with the same name (request-1)
+ await page.locator('#create-new-tab').getByRole('img').click();
+ await page.getByPlaceholder('Request Name').fill('request-1');
+ await page.locator('#new-request-url .CodeMirror').click();
+ await page.locator('textarea').fill('https://httpbin.org/post');
+ await page.getByRole('button', { name: 'Create' }).click();
+
+ // Go back to source collection to drag the request
+ await page.locator('#sidebar-collection-name').filter({ hasText: 'source-collection' }).click();
+ const sourceRequest = page.locator('.collection-item-name').filter({ hasText: 'request-1' }).first();
+ await expect(sourceRequest).toBeVisible();
+
+ // Locate the target collection area
+ const targetCollection = page.locator('.collection-name').filter({ hasText: 'target-collection' });
+ await expect(targetCollection).toBeVisible();
+
+ // Perform drag and drop operation to target-collection
+ await sourceRequest.dragTo(targetCollection);
+
+ // check for error toast notification
+ await expect(page.getByText(/Error: Cannot copy.*already exists/i)).toBeVisible();
+
+ // source and target collection request should remain unchanged
+ const targetCollectionContainer = page
+ .locator('.collection-name')
+ .filter({ hasText: 'target-collection' })
+ .locator('..');
+ await expect(
+ targetCollectionContainer.locator('.collection-item-name').filter({ hasText: 'request-1' })
+ ).toBeVisible();
+
+ const sourceCollectionContainer = page
+ .locator('.collection-name')
+ .filter({ hasText: 'source-collection' })
+ .locator('..');
+ await expect(
+ sourceCollectionContainer.locator('.collection-item-name').filter({ hasText: 'request-1' })
+ ).toBeVisible();
+ });
+});