diff --git a/packages/bruno-app/src/components/CollectionSettings/Script/index.js b/packages/bruno-app/src/components/CollectionSettings/Script/index.js index 99e4c638d..f7884c607 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Script/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Script/index.js @@ -1,20 +1,37 @@ -import React from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import get from 'lodash/get'; import { useDispatch, useSelector } from 'react-redux'; import CodeEditor from 'components/CodeEditor'; import { updateCollectionRequestScript, updateCollectionResponseScript } from 'providers/ReduxStore/slices/collections'; import { saveCollectionSettings } from 'providers/ReduxStore/slices/collections/actions'; import { useTheme } from 'providers/Theme'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from 'components/Tabs'; import StyledWrapper from './StyledWrapper'; const Script = ({ collection }) => { const dispatch = useDispatch(); + const [activeTab, setActiveTab] = useState('pre-request'); + const preRequestEditorRef = useRef(null); + const postResponseEditorRef = useRef(null); const requestScript = collection.draft?.root ? get(collection, 'draft.root.request.script.req', '') : get(collection, 'root.request.script.req', ''); const responseScript = collection.draft?.root ? get(collection, 'draft.root.request.script.res', '') : get(collection, 'root.request.script.res', ''); const { displayedTheme } = useTheme(); const preferences = useSelector((state) => state.app.preferences); + // Refresh CodeMirror when tab becomes visible + useEffect(() => { + const timer = setTimeout(() => { + if (activeTab === 'pre-request' && preRequestEditorRef.current?.editor) { + preRequestEditorRef.current.editor.refresh(); + } else if (activeTab === 'post-response' && postResponseEditorRef.current?.editor) { + postResponseEditorRef.current.editor.refresh(); + } + }, 0); + + return () => clearTimeout(timer); + }, [activeTab]); + const onRequestScriptEdit = (value) => { dispatch( updateCollectionRequestScript({ @@ -38,38 +55,47 @@ const Script = ({ collection }) => { }; return ( - +
Write pre and post-request scripts that will run before and after any request in this collection is sent.
-
-
Pre Request
- -
-
-
Post Response
- -
+ + + + Pre Request + Post Response + + + + + + + + + +
+ ); +}; + +export const TabsContent = ({ value: contentValue, children, className = '', dataTestId = '' }) => { + const { value } = useContext(TabsContext); + const isActive = value === contentValue; + + return ( +
+ {children} +
+ ); +}; diff --git a/packages/bruno-app/src/themes/dark.js b/packages/bruno-app/src/themes/dark.js index 6e74e47a2..34cb27874 100644 --- a/packages/bruno-app/src/themes/dark.js +++ b/packages/bruno-app/src/themes/dark.js @@ -241,6 +241,16 @@ const darkTheme = { active: { color: '#CCCCCC', border: '#F59E0B' + }, + secondary: { + active: { + bg: '#2D2D2D', + color: '#CCCCCC' + }, + inactive: { + bg: '#3F3F3F', + color: '#CCCCCC' + } } }, diff --git a/packages/bruno-app/src/themes/light.js b/packages/bruno-app/src/themes/light.js index 1ada3438b..794925883 100644 --- a/packages/bruno-app/src/themes/light.js +++ b/packages/bruno-app/src/themes/light.js @@ -242,6 +242,16 @@ const lightTheme = { active: { color: '#343434', border: '#D97706' + }, + secondary: { + active: { + bg: '#FFFFFF', + color: '#343434' + }, + inactive: { + bg: '#ECECEE', + color: '#989898' + } } }, diff --git a/tests/import/openapi/import-openapi-with-examples.spec.ts b/tests/import/openapi/import-openapi-with-examples.spec.ts index 5b7f10d0e..bb3b17b41 100644 --- a/tests/import/openapi/import-openapi-with-examples.spec.ts +++ b/tests/import/openapi/import-openapi-with-examples.spec.ts @@ -60,20 +60,20 @@ test.describe('Import OpenAPI Collection with Examples', () => { } }); - await test.step('Verify OpenAPI import settings modal appears', async () => { - const settingsModal = page.getByRole('dialog'); - await expect(settingsModal.locator('.bruno-modal-header-title')).toContainText('OpenAPI Import Settings'); - await settingsModal.getByRole('button', { name: 'Import' }).click(); + await test.step('Verify Import Collection location modal appears', async () => { + const locationModal = page.getByRole('dialog'); + await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection'); + await expect(locationModal.getByText('API with Examples')).toBeVisible(); }); await test.step('Click Browse link to select collection folder', async () => { - const settingsModal = page.getByRole('dialog'); - await settingsModal.getByText('Browse').click(); + const locationModal = page.getByRole('dialog'); + await locationModal.getByText('Browse').click(); }); await test.step('Complete import by clicking import button', async () => { - const settingsModal = page.getByRole('dialog'); - await settingsModal.getByRole('button', { name: 'Import' }).click(); + const locationModal = page.getByRole('dialog'); + await locationModal.getByRole('button', { name: 'Import' }).click(); }); await test.step('Handle sandbox modal', async () => { @@ -175,34 +175,32 @@ test.describe('Import OpenAPI Collection with Examples', () => { } }); - await test.step('Verify OpenAPI import settings modal appears', async () => { - const settingsModal = page.getByRole('dialog'); - await expect(settingsModal.locator('.bruno-modal-header-title')).toContainText('OpenAPI Import Settings'); + await test.step('Verify Import Collection location modal appears', async () => { + const locationModal = page.getByRole('dialog'); + await expect(locationModal.locator('.bruno-modal-header-title')).toContainText('Import Collection'); + await expect(locationModal.getByText('API with Examples')).toBeVisible(); }); await test.step('Select path-based grouping option from dropdown', async () => { - const settingsModal = page.getByRole('dialog'); + const locationModal = page.getByRole('dialog'); // Click on the grouping dropdown to open it - const groupingDropdown = settingsModal.getByTestId('grouping-dropdown'); + const groupingDropdown = locationModal.getByTestId('grouping-dropdown'); await groupingDropdown.click(); // Wait for dropdown to open and select "Paths" option (note: it's "Paths" not "Path") const pathOption = page.getByTestId('grouping-option-path'); await pathOption.click(); - // click on import button - const importButton = settingsModal.getByRole('button', { name: 'Import' }); - await importButton.click(); }); await test.step('Click Browse link to select collection folder', async () => { - const settingsModal = page.getByRole('dialog'); - await settingsModal.getByText('Browse').click(); + const locationModal = page.getByRole('dialog'); + await locationModal.getByText('Browse').click(); }); await test.step('Complete import by clicking import button', async () => { - const settingsModal = page.getByRole('dialog'); - await settingsModal.getByRole('button', { name: 'Import' }).click(); + const locationModal = page.getByRole('dialog'); + await locationModal.getByRole('button', { name: 'Import' }).click(); }); await test.step('Handle sandbox modal', async () => { diff --git a/tests/request/tests/custom-search/custom-search.spec.ts b/tests/request/tests/custom-search/custom-search.spec.ts index d9fdb72a0..b6c620224 100644 --- a/tests/request/tests/custom-search/custom-search.spec.ts +++ b/tests/request/tests/custom-search/custom-search.spec.ts @@ -8,60 +8,57 @@ test.describe('Custom Search Functionality in Scripts Tab', () => { await page.getByRole('tab', { name: 'Script' }).click(); - await expect(page.getByText('Pre Request')).toBeVisible(); - await expect(page.locator('.title.text-xs').filter({ hasText: 'Post Response' })).toBeVisible(); + // Pre Request tab should be active by default + await expect(page.getByRole('button', { name: 'Pre Request' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Post Response' })).toBeVisible(); - const preRequestEditor = page.locator('text=Pre Request').locator('..').locator('.CodeMirror').first(); + // Click on Pre Request tab to ensure it's active + await page.getByRole('button', { name: 'Pre Request' }).click(); + + const preRequestEditor = page.getByTestId('pre-request-script-editor').locator('.CodeMirror').first(); const preTextarea = preRequestEditor.locator('textarea[tabindex="0"]'); await preTextarea.focus(); const preContent = await preRequestEditor.textContent(); console.log('Pre Request content loaded:', preContent?.substring(0, 100)); - const postResponseEditor = page.locator('text=Post Response').locator('..').locator('.CodeMirror').first(); - const postTextarea = postResponseEditor.locator('textarea[tabindex="0"]'); - await postTextarea.focus(); - - const postContent = await postResponseEditor.textContent(); - console.log('Post Response content loaded:', postContent?.substring(0, 100)); - - await preTextarea.focus(); await page.keyboard.press('Meta+f'); // Verify search box appears - await expect(page.locator('.bruno-search-bar input[placeholder="Search..."]')).toBeVisible(); + const preEditorSearchBar = page.getByTestId('pre-request-script-editor'); + await expect(preEditorSearchBar.locator('.bruno-search-bar input[placeholder="Search..."]')).toBeVisible(); // Test search functionality - const searchInput = page.locator('.bruno-search-bar input[placeholder="Search..."]'); + const searchInput = preEditorSearchBar.locator('.bruno-search-bar input[placeholder="Search..."]'); await searchInput.fill('searchableText'); - await expect(page.locator('.searchbar-result-count')).toContainText('1 / 4'); + await expect(preEditorSearchBar.locator('.searchbar-result-count')).toContainText('1 / 4'); // Test search options - const regexButton = page.locator('.searchbar-icon-btn').filter({ hasText: '' }).first(); - const caseSensitiveButton = page.locator('.searchbar-icon-btn').filter({ hasText: '' }).nth(1); - const wholeWordButton = page.locator('.searchbar-icon-btn').filter({ hasText: '' }).nth(2); + const regexButton = preEditorSearchBar.locator('.searchbar-icon-btn').filter({ hasText: '' }).first(); + const caseSensitiveButton = preEditorSearchBar.locator('.searchbar-icon-btn').filter({ hasText: '' }).nth(1); + const wholeWordButton = preEditorSearchBar.locator('.searchbar-icon-btn').filter({ hasText: '' }).nth(2); // Test regex search await regexButton.click(); await searchInput.fill('test\\w+'); - await expect(page.locator('.searchbar-result-count')).toContainText('1 / 1'); + await expect(preEditorSearchBar.locator('.searchbar-result-count')).toContainText('1 / 1'); // Test case sensitive search await regexButton.click(); await caseSensitiveButton.click(); await searchInput.fill('Test'); - await expect(page.locator('.searchbar-result-count')).toContainText('0 results'); + await expect(preEditorSearchBar.locator('.searchbar-result-count')).toContainText('0 results'); // Test whole word search await caseSensitiveButton.click(); await wholeWordButton.click(); await searchInput.fill('hello'); - await expect(page.locator('.searchbar-result-count')).toContainText('1 / 1'); + await expect(preEditorSearchBar.locator('.searchbar-result-count')).toContainText('1 / 1'); // Test close search - const closeButton = page.locator('.searchbar-icon-btn').last(); + const closeButton = page.getByTestId('pre-request-script-editor').locator('.searchbar-icon-btn').last(); await closeButton.click(); - await expect(page.locator('.bruno-search-bar')).not.toBeVisible(); + await expect(page.getByTestId('pre-request-script-editor').locator('.bruno-search-bar')).not.toBeVisible(); }); test('should handle search in different script editors independently', async ({ pageWithUserData: page }) => { @@ -71,63 +68,75 @@ test.describe('Custom Search Functionality in Scripts Tab', () => { await page.getByRole('tab', { name: 'Script' }).click(); - await expect(page.getByText('Pre Request')).toBeVisible(); - await expect(page.locator('.title.text-xs').filter({ hasText: 'Post Response' })).toBeVisible(); - - const preRequestEditor = page.locator('text=Pre Request').locator('..').locator('.CodeMirror').first(); - const postResponseEditor = page.locator('text=Post Response').locator('..').locator('.CodeMirror').first(); + // Test Pre Request tab + await page.getByRole('button', { name: 'Pre Request' }).click(); + const preRequestEditor = page.getByTestId('pre-request-script-editor').locator('.CodeMirror').first(); const preTextarea = preRequestEditor.locator('textarea[tabindex="0"]'); await preTextarea.focus(); await page.keyboard.press('Meta+f'); - const preSearchInput = page.locator('.bruno-search-bar input[placeholder="Search..."]'); + const preSearchInput = page.getByTestId('pre-request-script-editor').locator('.bruno-search-bar input[placeholder="Search..."]'); await preSearchInput.fill('uniquePreVar'); - await expect(page.locator('.searchbar-result-count')).toContainText('1 / 1'); + await expect(page.getByTestId('pre-request-script-editor').locator('.searchbar-result-count')).toContainText('1 / 1'); await page.keyboard.press('Escape'); + // Switch to Post Response tab + await page.getByRole('button', { name: 'Post Response' }).click(); + + const postResponseEditor = page.getByTestId('post-response-script-editor').locator('.CodeMirror').first(); const postTextarea = postResponseEditor.locator('textarea[tabindex="0"]'); await postTextarea.focus(); await page.keyboard.press('Meta+f'); - const postSearchInput = page.locator('.bruno-search-bar input[placeholder="Search..."]'); + const postSearchInput = page.getByTestId('post-response-script-editor').locator('.bruno-search-bar input[placeholder="Search..."]'); await postSearchInput.fill('uniquePostVar'); - await expect(page.locator('.searchbar-result-count')).toContainText('1 / 1'); + await expect(page.getByTestId('post-response-script-editor').locator('.searchbar-result-count')).toContainText('1 / 1'); await page.keyboard.press('Escape'); }); - test('should maintain search state when switching between editors', async ({ pageWithUserData: page }) => { + test('should maintain search state when switching between tabs', async ({ pageWithUserData: page }) => { await page.getByTitle('custom-search').click(); await page.getByText('search-test-request').click(); await page.getByRole('tab', { name: 'Script' }).click(); - await expect(page.getByText('Pre Request')).toBeVisible(); - await expect(page.locator('.title.text-xs').filter({ hasText: 'Post Response' })).toBeVisible(); - - const preRequestEditor = page.locator('text=Pre Request').locator('..').locator('.CodeMirror').first(); - const postResponseEditor = page.locator('text=Post Response').locator('..').locator('.CodeMirror').first(); - // Open search in Pre Request editor + await page.getByRole('button', { name: 'Pre Request' }).click(); + + const preRequestEditor = page.getByTestId('pre-request-script-editor').locator('.CodeMirror').first(); const preTextarea = preRequestEditor.locator('textarea[tabindex="0"]'); await preTextarea.focus(); await page.keyboard.press('Meta+f'); - const searchInput = page.locator('.bruno-search-bar input[placeholder="Search..."]'); - await searchInput.fill('commonVar'); - await expect(page.locator('.searchbar-result-count')).toContainText('1 / 1'); + const preSearchInput = page.getByTestId('pre-request-script-editor').locator('.bruno-search-bar input[placeholder="Search..."]'); + await preSearchInput.fill('commonVar'); + await expect(page.getByTestId('pre-request-script-editor').locator('.searchbar-result-count')).toContainText('1 / 1'); - // Switch to Post Response editor while search is open + // Switch to Post Response tab while search is open + await page.getByRole('button', { name: 'Post Response' }).click(); + + // Open search in Post Response editor + const postResponseEditor = page.getByTestId('post-response-script-editor').locator('.CodeMirror').first(); const postTextarea = postResponseEditor.locator('textarea[tabindex="0"]'); await postTextarea.focus(); + await page.keyboard.press('Meta+f'); - // Search should still be visible and functional - await expect(page.locator('.bruno-search-bar')).toBeVisible(); - await expect(searchInput).toHaveValue('commonVar'); + const postSearchInput = page.getByTestId('post-response-script-editor').locator('.bruno-search-bar input[placeholder="Search..."]'); + await postSearchInput.fill('postVar'); + await expect(page.getByTestId('post-response-script-editor').locator('.searchbar-result-count')).toContainText('1 / 1'); - const closeButton = page.locator('.searchbar-icon-btn').last(); + // Switch back to Pre Request tab + await page.getByRole('button', { name: 'Pre Request' }).click(); + + // Search state should be maintained in Pre Request editor + await expect(page.getByTestId('pre-request-script-editor').locator('.bruno-search-bar')).toBeVisible(); + await expect(preSearchInput).toHaveValue('commonVar'); + + // Close the search in Pre Request editor + const closeButton = page.getByTestId('pre-request-script-editor').locator('.searchbar-icon-btn').last(); await closeButton.click(); - await expect(page.locator('.bruno-search-bar')).not.toBeVisible(); + await expect(page.getByTestId('pre-request-script-editor').locator('.bruno-search-bar')).not.toBeVisible(); }); });