diff --git a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js index 713ee9d32c8..9f54e3c2e53 100644 --- a/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/CreateCollection/index.js @@ -45,23 +45,24 @@ const CreateCollection = ({ onClose, defaultLocation: propDefaultLocation, initi }, validationSchema: Yup.object({ collectionName: Yup.string() - .min(1, 'must be at least 1 character') - .max(255, 'must be 255 characters or less') - .required('collection name is required'), + .trim() + .min(1, 'Collection name can\'t be empty') + .max(255, 'Must be 255 characters or less') + .required('Collection name is required'), collectionFolderName: Yup.string() - .min(1, 'must be at least 1 character') - .max(255, 'must be 255 characters or less') + .min(1, 'Must be at least 1 character') + .max(255, 'Must be 255 characters or less') .test('is-valid-collection-name', function (value) { const isValid = validateName(value); return isValid ? true : this.createError({ message: validateNameError(value) }); }) - .required('folder name is required'), - collectionLocation: Yup.string().min(1, 'location is required').required('location is required'), - format: Yup.string().oneOf(['bru', 'yml'], 'invalid format').required('format is required') + .required('Folder name is required'), + collectionLocation: Yup.string().min(1, 'Location is required').required('Location is required'), + format: Yup.string().oneOf(['bru', 'yml'], 'invalid format').required('Format is required') }), onSubmit: async (values) => { try { - await dispatch(createCollection(values.collectionName, + await dispatch(createCollection(values.collectionName.trim(), values.collectionFolderName, values.collectionLocation, { format: values.format })); @@ -126,8 +127,17 @@ const CreateCollection = ({ onClose, defaultLocation: propDefaultLocation, initi ref={inputRef} className="block textbox mt-2 w-full" onChange={(e) => { + const collectionName = e.target.value; + if (!isEditing) { + formik.setValues((values) => ({ + ...values, + collectionName, + collectionFolderName: sanitizeName(collectionName) + })); + return; + } + formik.handleChange(e); - !isEditing && formik.setFieldValue('collectionFolderName', sanitizeName(e.target.value)); }} autoComplete="off" autoCorrect="off" diff --git a/packages/bruno-app/src/components/WorkspaceSidebar/CreateWorkspace/index.js b/packages/bruno-app/src/components/WorkspaceSidebar/CreateWorkspace/index.js index 26b9e7af4e1..eafb6d81024 100644 --- a/packages/bruno-app/src/components/WorkspaceSidebar/CreateWorkspace/index.js +++ b/packages/bruno-app/src/components/WorkspaceSidebar/CreateWorkspace/index.js @@ -33,7 +33,8 @@ const CreateWorkspace = ({ onClose }) => { }, validationSchema: Yup.object({ workspaceName: Yup.string() - .min(1, 'Must be at least 1 character') + .trim() + .min(1, 'Workspace name can\'t be empty') .max(255, 'Must be 255 characters or less') .required('Workspace name is required') .test('unique-name', 'A workspace with this name already exists', function (value) { @@ -58,7 +59,7 @@ const CreateWorkspace = ({ onClose }) => { try { setIsSubmitting(true); - await dispatch(createWorkspaceAction(values.workspaceName, values.workspaceFolderName, values.workspaceLocation)); + await dispatch(createWorkspaceAction(values.workspaceName.trim(), values.workspaceFolderName, values.workspaceLocation)); toast.success('Workspace created!'); onClose(); } catch (error) { @@ -116,10 +117,17 @@ const CreateWorkspace = ({ onClose }) => { autoCapitalize="off" spellCheck="false" onChange={(e) => { - formik.handleChange(e); + const workspaceName = e.target.value; if (!isEditing) { - formik.setFieldValue('workspaceFolderName', sanitizeName(e.target.value)); + formik.setValues((values) => ({ + ...values, + workspaceName, + workspaceFolderName: sanitizeName(workspaceName) + })); + return; } + + formik.handleChange(e); }} value={formik.values.workspaceName || ''} /> diff --git a/tests/collection/create/create-collection.spec.ts b/tests/collection/create/create-collection.spec.ts index 82a3d6a56f9..0dc68ed16ea 100644 --- a/tests/collection/create/create-collection.spec.ts +++ b/tests/collection/create/create-collection.spec.ts @@ -7,6 +7,52 @@ test.describe('Create collection', () => { await closeAllCollections(page); }); + test('should show validation error for empty name in modal and keep modal open', async ({ page }) => { + await page.getByTestId('collections-header-add-menu').click(); + await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click(); + + const inlineCreator = page.locator('.inline-collection-creator'); + await inlineCreator.waitFor({ state: 'visible', timeout: 5000 }); + await inlineCreator.locator('.cog-btn').click(); + + const createCollectionModal = page.locator('.bruno-modal-card').filter({ hasText: 'Create Collection' }); + await createCollectionModal.waitFor({ state: 'visible', timeout: 5000 }); + + const submitButton = createCollectionModal.getByRole('button', { name: 'Create', exact: true }); + await createCollectionModal.getByLabel('Name').fill(''); + await expect(submitButton).toBeEnabled(); + await submitButton.click(); + + await expect(createCollectionModal).toBeVisible(); + await expect(submitButton).toBeEnabled(); + await expect(createCollectionModal.getByText('Collection name is required')).toBeVisible({ timeout: 2000 }); + + await createCollectionModal.getByRole('button', { name: 'Cancel' }).click(); + }); + + test('should show validation error for whitespace-only name in modal and keep modal open', async ({ page }) => { + await page.getByTestId('collections-header-add-menu').click(); + await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click(); + + const inlineCreator = page.locator('.inline-collection-creator'); + await inlineCreator.waitFor({ state: 'visible', timeout: 5000 }); + await inlineCreator.locator('.cog-btn').click(); + + const createCollectionModal = page.locator('.bruno-modal-card').filter({ hasText: 'Create Collection' }); + await createCollectionModal.waitFor({ state: 'visible', timeout: 5000 }); + + const submitButton = createCollectionModal.getByRole('button', { name: 'Create', exact: true }); + await createCollectionModal.getByLabel('Name').fill(' '); + await expect(submitButton).toBeEnabled(); + await submitButton.click(); + + await expect(createCollectionModal).toBeVisible(); + await expect(submitButton).toBeEnabled(); + await expect(createCollectionModal.getByText('Collection name can\'t be empty')).toBeVisible({ timeout: 2000 }); + + await createCollectionModal.getByRole('button', { name: 'Cancel' }).click(); + }); + test('Create collection and add a simple HTTP request', async ({ page, createTmpDir }) => { const collectionName = 'test-collection'; const requestName = 'ping'; diff --git a/tests/workspace/create-workspace/create-workspace.spec.ts b/tests/workspace/create-workspace/create-workspace.spec.ts index c60ef09d101..27e8291b566 100644 --- a/tests/workspace/create-workspace/create-workspace.spec.ts +++ b/tests/workspace/create-workspace/create-workspace.spec.ts @@ -362,7 +362,7 @@ test.describe('Create Workspace', () => { await closeElectronApp(app); }); - test('should show validation error for empty name in modal', async ({ launchElectronApp, createTmpDir }) => { + test('should show validation error for empty name in modal and keep modal open', async ({ launchElectronApp, createTmpDir }) => { const wsLocation = await createTmpDir('ws-location-modal-empty'); const app = await launchElectronApp({ initUserDataPath, templateVars: { wsLocation } }); @@ -376,20 +376,57 @@ test.describe('Create Workspace', () => { await page.locator('.cog-btn').click(); }); - await test.step('Clear name and try to submit', async () => { + await test.step('Clear name and submit', async () => { const modal = page.locator('.bruno-modal-card').filter({ hasText: 'Create Workspace' }); await modal.waitFor({ state: 'visible', timeout: 5000 }); + const submitButton = modal.getByRole('button', { name: 'Create Workspace' }); - // Ensure name field is empty await modal.locator('#workspace-name').fill(''); - await modal.getByRole('button', { name: 'Create Workspace' }).click(); + await expect(submitButton).toBeEnabled(); + await submitButton.click(); + }); + + await test.step('Verify validation error appears and modal stays open', async () => { + const modal = page.locator('.bruno-modal-card').filter({ hasText: 'Create Workspace' }); + const submitButton = modal.getByRole('button', { name: 'Create Workspace' }); + await expect(modal).toBeVisible(); + await expect(submitButton).toBeEnabled(); + await expect(modal.getByText('Workspace name is required')).toBeVisible({ timeout: 2000 }); + }); + + await closeElectronApp(app); + }); + + test('should show validation error for whitespace-only name in modal and keep modal open', async ({ launchElectronApp, createTmpDir }) => { + const wsLocation = await createTmpDir('ws-location-modal-whitespace'); + + const app = await launchElectronApp({ initUserDataPath, templateVars: { wsLocation } }); + const page = await app.firstWindow(); + await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 }); + + await test.step('Start inline creation and open advanced modal', async () => { + await page.locator('.workspace-name-container').click(); + await page.locator('.dropdown-item').filter({ hasText: 'Create workspace' }).click(); + await expect(page.locator('.workspace-name-input')).toBeVisible({ timeout: 5000 }); + await page.locator('.cog-btn').click(); + }); + + await test.step('Enter whitespace-only name and submit', async () => { + const modal = page.locator('.bruno-modal-card').filter({ hasText: 'Create Workspace' }); + await modal.waitFor({ state: 'visible', timeout: 5000 }); + const submitButton = modal.getByRole('button', { name: 'Create Workspace' }); + + await modal.locator('#workspace-name').fill(' '); + await expect(submitButton).toBeEnabled(); + await submitButton.click(); }); await test.step('Verify validation error appears and modal stays open', async () => { const modal = page.locator('.bruno-modal-card').filter({ hasText: 'Create Workspace' }); + const submitButton = modal.getByRole('button', { name: 'Create Workspace' }); await expect(modal).toBeVisible(); - const error = modal.locator('.text-red-500'); - await expect(error.first()).toBeVisible({ timeout: 2000 }); + await expect(submitButton).toBeEnabled(); + await expect(modal.getByText('Workspace name can\'t be empty')).toBeVisible({ timeout: 2000 }); }); await closeElectronApp(app);