fix(editor): Addressing internal testing feedback for folders (no-changelog) (#13997)

This commit is contained in:
Milorad FIlipović
2025-03-20 15:48:10 +01:00
committed by GitHub
parent 305ea0fb32
commit 1f56a24bbd
35 changed files with 1277 additions and 145 deletions

View File

@@ -21,7 +21,7 @@ const switchBetweenEditorAndWorkflowlist = () => {
cy.getByTestId('menu-item').first().click();
cy.wait(['@getUsers', '@getWorkflows', '@getActiveWorkflows', '@getProjects']);
cy.getByTestId('resources-list-item').first().click();
cy.getByTestId('resources-list-item-workflow').first().click();
workflowPage.getters.canvasNodes().first().should('be.visible');
workflowPage.getters.canvasNodes().last().should('be.visible');

View File

@@ -514,7 +514,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
workflowsPage.getters.workflowCards().should('have.length', 3);
workflowsPage.getters
.workflowCards()
.filter(':has(.n8n-badge:contains("Project"))')
.filter(':has([data-test-id="workflow-card-breadcrumbs"]:contains("Project"))')
.should('have.length', 2);
workflowsPage.getters.workflowCardActions('Workflow in Home project').click();
workflowsPage.getters.workflowMoveButton().click();

View File

@@ -4,6 +4,12 @@ import {
createFolderFromListHeaderButton,
createFolderFromProjectHeader,
createFolderInsideFolder,
createNewProject,
createWorkflowFromEmptyState,
createWorkflowFromListDropdown,
createWorkflowFromProjectHeader,
deleteAndTransferFolderContentsFromCardDropdown,
deleteAndTransferFolderContentsFromListDropdown,
deleteEmptyFolderFromCardDropdown,
deleteEmptyFolderFromListDropdown,
deleteFolderWithContentsFromCardDropdown,
@@ -14,14 +20,27 @@ import {
getFolderCardActionItem,
getFolderCardActionToggle,
getFolderCards,
getFolderEmptyState,
getHomeProjectBreadcrumb,
getListBreadcrumbItem,
getListBreadcrumbs,
getMainBreadcrumbsEllipsis,
getMainBreadcrumbsEllipsisMenuItems,
getNewFolderModalErrorMessage,
getNewFolderNameInput,
getOverviewMenuItem,
getPersonalProjectMenuItem,
getProjectEmptyState,
getProjectMenuItem,
getVisibleListBreadcrumbs,
getWorkflowCard,
getWorkflowCardBreadcrumbs,
getWorkflowCardBreadcrumbsEllipsis,
getWorkflowCards,
goToPersonalProject,
moveFolderFromFolderCardActions,
moveFolderFromListActions,
moveWorkflowToFolder,
renameFolderFromCardActions,
renameFolderFromListActions,
} from '../composables/folders';
@@ -44,6 +63,7 @@ describe('Folders', () => {
describe('Create and navigate folders', () => {
it('should create folder from the project header', () => {
getPersonalProjectMenuItem().click();
createFolderFromProjectHeader('My Folder');
getFolderCards().should('have.length.greaterThan', 0);
// Clicking on the success toast should navigate to the folder
@@ -51,6 +71,33 @@ describe('Folders', () => {
getCurrentBreadcrumb().should('contain.text', 'My Folder');
});
it('should not allow illegal folder names', () => {
// Validation logic is thoroughly tested in unit tests
// Here we just make sure everything is working in the full UI
const ILLEGAL_CHARACTERS_NAME = 'hello[';
const ONLY_DOTS_NAME = '...';
const REGULAR_NAME = 'My Folder';
getPersonalProjectMenuItem().click();
getAddResourceDropdown().click();
cy.getByTestId('action-folder').click();
getNewFolderNameInput().type(ILLEGAL_CHARACTERS_NAME, { delay: 50 });
getNewFolderModalErrorMessage().should(
'contain.text',
'Folder name cannot contain the following characters',
);
getNewFolderNameInput().clear();
getNewFolderNameInput().type(ONLY_DOTS_NAME, { delay: 50 });
getNewFolderModalErrorMessage().should(
'contain.text',
'Folder name cannot contain only dots',
);
getNewFolderNameInput().clear();
getNewFolderModalErrorMessage().should('contain.text', 'Folder name cannot be empty');
getNewFolderNameInput().type(REGULAR_NAME, { delay: 50 });
getNewFolderModalErrorMessage().should('not.exist');
});
it('should create folder from the list header button', () => {
goToPersonalProject();
// First create a folder so list appears
@@ -78,9 +125,9 @@ describe('Folders', () => {
getFolderCard('Created from card dropdown').should('exist');
createFolderFromCardActions('Created from card dropdown', 'Child Folder');
successToast().should('exist');
// Open parent folder to see the new child folder
getFolderCard('Created from card dropdown').click();
// Should be automatically navigated to the new folder
getFolderCard('Child Folder').should('exist');
getCurrentBreadcrumb().should('contain.text', 'Created from card dropdown');
});
it('should navigate folders using breadcrumbs and dropdown menu', () => {
@@ -88,7 +135,7 @@ describe('Folders', () => {
createFolderFromProjectHeader('Navigate Test');
// Open folder using menu item
getFolderCardActionToggle('Navigate Test').click();
getFolderCardActionItem('open').click();
getFolderCardActionItem('Navigate Test', 'open').click();
getCurrentBreadcrumb().should('contain.text', 'Navigate Test');
// Create new child folder and navigate to it
createFolderFromListHeaderButton('Child Folder');
@@ -165,12 +212,72 @@ describe('Folders', () => {
// In personal, we should see previously created folders
getPersonalProjectMenuItem().click();
getAddResourceDropdown().click();
cy.getByTestId('action-folder').should('exist');
createFolderFromProjectHeader('Personal Folder');
getFolderCards().should('exist');
});
});
describe('Empty State', () => {
it('should show project empty state when no folders exist', () => {
createNewProject('Test empty project', { openAfterCreate: true });
getProjectEmptyState().should('exist');
});
it('should toggle folder empty state correctly', () => {
createNewProject('Test empty folder', { openAfterCreate: true });
createFolderFromProjectHeader('My Folder');
getProjectEmptyState().should('not.exist');
getFolderCard('My Folder').should('exist');
getFolderCard('My Folder').click();
getFolderEmptyState().should('exist');
// Create a new workflow from the empty state
createWorkflowFromEmptyState('My Workflow');
// Toast should inform that the workflow was created in the folder
successToast().should(
'contain.text',
'Workflow successfully created in "Test empty folder", within "My Folder"',
);
// Go back to the folder
getProjectMenuItem('Test empty folder').click();
getFolderCard('My Folder').should('exist');
getFolderCard('My Folder').click();
// Should not show empty state anymore
getFolderEmptyState().should('not.exist');
getWorkflowCards().should('have.length.greaterThan', 0);
// Also when filtering and there are no results, empty state CTA should not show
cy.getByTestId('resources-list-search').type('non-existing', { delay: 20 });
getWorkflowCards().should('not.exist');
getFolderEmptyState().should('not.exist');
// But there should be a message saying that no results were found
cy.getByTestId('resources-list-empty').should('exist');
});
});
describe('Create workflows inside folders', () => {
it('should create workflows in folders in all supported ways', () => {
goToPersonalProject();
createFolderFromProjectHeader('Workflows go here');
// 1. From empty state
getFolderCard('Workflows go here').should('exist').click();
createWorkflowFromEmptyState('Created from empty state');
goToPersonalProject();
getFolderCard('Workflows go here').click();
getWorkflowCard('Created from empty state').should('exist');
// 2. From the project header
createWorkflowFromProjectHeader('Workflows go here', 'Created from project header');
goToPersonalProject();
getFolderCard('Workflows go here').click();
getWorkflowCard('Created from project header').should('exist');
// 3. From list breadcrumbs
createWorkflowFromListDropdown('Created from list breadcrumbs');
goToPersonalProject();
getFolderCard('Workflows go here').click();
getWorkflowCard('Created from list breadcrumbs').should('exist');
});
});
describe('Rename and delete folders', () => {
it('should rename folder from main dropdown', () => {
goToPersonalProject();
@@ -224,6 +331,175 @@ describe('Folders', () => {
deleteFolderWithContentsFromCardDropdown('I also have family');
});
// TODO: Once we have backend endpoint that lists project folders, test transfer when deleting
it('should transfer contents when deleting non-empty folder - from card dropdown', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move my contents');
createFolderFromProjectHeader('Destination');
createFolderInsideFolder('Child 1', 'Move my contents');
getHomeProjectBreadcrumb().click();
getFolderCard('Move my contents').should('exist');
deleteAndTransferFolderContentsFromCardDropdown('Move my contents', 'Destination');
getFolderCard('Destination').click();
// Should show the contents of the moved folder
getFolderCard('Child 1').should('exist');
});
it('should transfer contents when deleting non-empty folder - from list breadcrumbs', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move me too');
createFolderFromProjectHeader('Destination 2');
createFolderInsideFolder('Child 1', 'Move me too');
deleteAndTransferFolderContentsFromListDropdown('Destination 2');
getFolderCard('Destination').click();
// Should show the contents of the moved folder
getFolderCard('Child 1').should('exist');
});
});
describe('Move folders and workflows', () => {
it('should move empty folder to another folder - from folder card action', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move me - I am empty');
createFolderFromProjectHeader('Destination 3');
moveFolderFromFolderCardActions('Move me - I am empty', 'Destination 3');
getFolderCard('Destination 3').click();
getFolderCard('Move me - I am empty').should('exist');
getFolderCard('Move me - I am empty').click();
getFolderEmptyState().should('exist');
successToast().should('contain.text', 'Move me - I am empty has been moved to Destination 3');
// Breadcrumbs should show the destination folder
getListBreadcrumbItem('Destination 3').should('exist');
});
it('should move folder with contents to another folder - from folder card action', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move me - I have family');
createFolderFromProjectHeader('Destination 4');
// Create a workflow and a folder inside the folder
createFolderInsideFolder('Child 1', 'Move me - I have family');
createWorkflowFromProjectHeader('Move me - I have family');
goToPersonalProject();
// Move the folder
moveFolderFromFolderCardActions('Move me - I have family', 'Destination 4');
successToast().should(
'contain.text',
'Move me - I have family has been moved to Destination 4',
);
// Go to destination folder and check if contents are there
getFolderCard('Destination 4').click();
// Moved folder should be there
getFolderCard('Move me - I have family').should('exist').click();
// Both the workflow and the folder should be there
getFolderCards().should('have.length', 1);
getWorkflowCards().should('have.length', 1);
// Breadcrumbs should show the destination folder
getListBreadcrumbItem('Destination 4').should('exist');
});
it('should move empty folder to another folder - from list breadcrumbs', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move me too - I am empty');
createFolderFromProjectHeader('Destination 5');
moveFolderFromListActions('Move me too - I am empty', 'Destination 5');
// Since we moved the current folder, we should be in the destination folder
getCurrentBreadcrumb().should('contain.text', 'Destination 5');
});
it('should move folder with contents to another folder - from list dropdown', () => {
goToPersonalProject();
createFolderFromProjectHeader('Move me - I have family 2');
createFolderFromProjectHeader('Destination 6');
// Create a workflow and a folder inside the folder
createFolderInsideFolder('Child 1', 'Move me - I have family 2');
createWorkflowFromProjectHeader('Move me - I have family 2');
// Navigate back to folder
goToPersonalProject();
getFolderCard('Move me - I have family 2').should('exist');
// Move the folder
moveFolderFromListActions('Move me - I have family 2', 'Destination 6');
// Since we moved the current folder, we should be in the destination folder
getCurrentBreadcrumb().should('contain.text', 'Destination 6');
// Moved folder should be there
getFolderCard('Move me - I have family 2').should('exist').click();
// After navigating to the moved folder, both the workflow and the folder should be there
getFolderCards().should('have.length', 1);
getWorkflowCards().should('have.length', 1);
// Breadcrumbs should show the destination folder
getListBreadcrumbItem('Destination 6').should('exist');
});
it('should move folder to project root - from folder card action', () => {
goToPersonalProject();
createFolderFromProjectHeader('Test parent');
createFolderInsideFolder('Move me to root', 'Test parent');
moveFolderFromFolderCardActions('Move me to root', 'Personal');
// Parent folder should be empty
getFolderEmptyState().should('exist');
// Child folder should be in the root
goToPersonalProject();
getFolderCard('Move me to root').should('exist');
// Navigate to the moved folder and check breadcrumbs
getFolderCard('Move me to root').click();
getHomeProjectBreadcrumb().should('contain.text', 'Personal');
getListBreadcrumbs().findChildByTestId('breadcrumbs-item').should('not.exist');
getCurrentBreadcrumb().should('contain.text', 'Move me to root');
});
it('should move workflow from project root to folder', () => {
goToPersonalProject();
createWorkflowFromProjectHeader(undefined, 'Move me');
goToPersonalProject();
createFolderFromProjectHeader('Workflow destination');
moveWorkflowToFolder('Move me', 'Workflow destination');
successToast().should('contain.text', 'Move me has been moved to Workflow destination');
// Navigate to the destination folder
getFolderCard('Workflow destination').click();
// Moved workflow should be there
getWorkflowCards().should('have.length', 1);
getWorkflowCard('Move me').should('exist');
});
it('should move workflow to another folder', () => {
goToPersonalProject();
createFolderFromProjectHeader('Moving workflow from here');
createFolderFromProjectHeader('Moving workflow to here');
getFolderCard('Moving workflow from here').click();
createWorkflowFromProjectHeader(undefined, 'Move me');
goToPersonalProject();
getFolderCard('Moving workflow from here').click();
getWorkflowCard('Move me').should('exist');
moveWorkflowToFolder('Move me', 'Moving workflow to here');
// Now folder should be empty
getFolderEmptyState().should('exist');
// Navigate to the destination folder
getHomeProjectBreadcrumb().click();
getFolderCard('Moving workflow to here').click();
// Moved workflow should be there
getWorkflowCards().should('have.length', 1);
getWorkflowCard('Move me').should('exist');
});
});
describe('Workflow card breadcrumbs', () => {
it('should correctly show workflow card breadcrumbs', () => {
createNewProject('Test workflow breadcrumbs', { openAfterCreate: true });
createFolderFromProjectHeader('Parent Folder');
createFolderInsideFolder('Child Folder', 'Parent Folder');
getFolderCard('Child Folder').click();
createFolderFromListHeaderButton('Child Folder 2');
getFolderCard('Child Folder 2').click();
createWorkflowFromEmptyState('Breadcrumbs Test');
// Go to overview page
getOverviewMenuItem().click();
getWorkflowCard('Breadcrumbs Test').should('exist');
getWorkflowCardBreadcrumbs('Breadcrumbs Test').should('exist');
getWorkflowCardBreadcrumbsEllipsis('Breadcrumbs Test').should('exist');
getWorkflowCardBreadcrumbsEllipsis('Breadcrumbs Test').realHover({ position: 'topLeft' });
cy.get('[role=tooltip]').should('exist');
cy.get('[role=tooltip]').should(
'contain.text',
'est workflow breadcrumbs / Parent Folder / Child Folder / Child Folder 2',
);
});
});
});