mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
feat: Add tabs component for pre-request and post-response scripts (#5926)
This commit is contained in:
@@ -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 (
|
||||
<StyledWrapper className="w-full flex flex-col h-full">
|
||||
<StyledWrapper className="w-full flex flex-col h-full pt-4">
|
||||
<div className="text-xs mb-4 text-muted">
|
||||
Write pre and post-request scripts that will run before and after any request in this collection is sent.
|
||||
</div>
|
||||
<div className="flex-1 mt-2">
|
||||
<div className="mb-1 title text-xs">Pre Request</div>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
theme={displayedTheme}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'bru']}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 mt-6">
|
||||
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
theme={displayedTheme}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'res', 'bru']}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="pre-request">Pre Request</TabsTrigger>
|
||||
<TabsTrigger value="post-response">Post Response</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="pre-request" className="mt-2">
|
||||
<CodeEditor
|
||||
ref={preRequestEditorRef}
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
theme={displayedTheme}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'bru']}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="post-response" className="mt-2">
|
||||
<CodeEditor
|
||||
ref={postResponseEditorRef}
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
theme={displayedTheme}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'res', 'bru']}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<div className="mt-12">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||
|
||||
@@ -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 { updateFolderRequestScript, updateFolderResponseScript } from 'providers/ReduxStore/slices/collections';
|
||||
import { saveFolderRoot } 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, folder }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [activeTab, setActiveTab] = useState('pre-request');
|
||||
const preRequestEditorRef = useRef(null);
|
||||
const postResponseEditorRef = useRef(null);
|
||||
const requestScript = folder.draft ? get(folder, 'draft.request.script.req', '') : get(folder, 'root.request.script.req', '');
|
||||
const responseScript = folder.draft ? get(folder, 'draft.request.script.res', '') : get(folder, '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(
|
||||
updateFolderRequestScript({
|
||||
@@ -40,38 +57,47 @@ const Script = ({ collection, folder }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col h-full">
|
||||
<StyledWrapper className="w-full flex flex-col h-full pt-4">
|
||||
<div className="text-xs mb-4 text-muted">
|
||||
Pre and post-request scripts that will run before and after any request inside this folder is sent.
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
<div className="title text-xs">Pre Request</div>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
theme={displayedTheme}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'bru']}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
<div className="title text-xs">Post Response</div>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
theme={displayedTheme}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'res', 'bru']}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="pre-request">Pre Request</TabsTrigger>
|
||||
<TabsTrigger value="post-response">Post Response</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="pre-request" className="mt-2">
|
||||
<CodeEditor
|
||||
ref={preRequestEditorRef}
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
theme={displayedTheme}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'bru']}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="post-response" className="mt-2">
|
||||
<CodeEditor
|
||||
ref={postResponseEditorRef}
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
theme={displayedTheme}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode="javascript"
|
||||
onSave={handleSave}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
showHintsFor={['req', 'res', 'bru']}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<div className="mt-12">
|
||||
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||
|
||||
@@ -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 { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from 'components/Tabs';
|
||||
|
||||
const Script = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const [activeTab, setActiveTab] = useState('pre-request');
|
||||
const preRequestEditorRef = useRef(null);
|
||||
const postResponseEditorRef = useRef(null);
|
||||
const requestScript = item.draft ? get(item, 'draft.request.script.req') : get(item, 'request.script.req');
|
||||
const responseScript = item.draft ? get(item, 'draft.request.script.res') : get(item, 'request.script.res');
|
||||
|
||||
const { displayedTheme } = useTheme();
|
||||
const preferences = useSelector((state) => state.app.preferences);
|
||||
|
||||
// Refresh CodeMirror when tab becomes visible
|
||||
useEffect(() => {
|
||||
// Small delay to ensure DOM is updated
|
||||
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(
|
||||
updateRequestScript({
|
||||
@@ -39,38 +56,46 @@ const Script = ({ item, collection }) => {
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col">
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
<div className="title text-xs">Pre Request</div>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
showHintsFor={['req', 'bru']}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 mt-2 gap-y-2">
|
||||
<div className="title text-xs">Post Response</div>
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
showHintsFor={['req', 'res', 'bru']}
|
||||
/>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
<div className="w-full h-full flex flex-col pt-4">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList>
|
||||
<TabsTrigger value="pre-request">Pre Request</TabsTrigger>
|
||||
<TabsTrigger value="post-response">Post Response</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="pre-request" className="mt-2" dataTestId="pre-request-script-editor">
|
||||
<CodeEditor
|
||||
ref={preRequestEditorRef}
|
||||
collection={collection}
|
||||
value={requestScript || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onRequestScriptEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
showHintsFor={['req', 'bru']}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="post-response" className="mt-2" dataTestId="post-response-script-editor">
|
||||
<CodeEditor
|
||||
ref={postResponseEditorRef}
|
||||
collection={collection}
|
||||
value={responseScript || ''}
|
||||
theme={displayedTheme}
|
||||
font={get(preferences, 'font.codeFont', 'default')}
|
||||
fontSize={get(preferences, 'font.codeFontSize')}
|
||||
onEdit={onResponseScriptEdit}
|
||||
mode="javascript"
|
||||
onRun={onRun}
|
||||
onSave={onSave}
|
||||
showHintsFor={['req', 'res', 'bru']}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
59
packages/bruno-app/src/components/Tabs/index.js
Normal file
59
packages/bruno-app/src/components/Tabs/index.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
|
||||
const TabsContext = createContext();
|
||||
|
||||
export const Tabs = ({ value, onValueChange, children, className = '' }) => {
|
||||
return (
|
||||
<TabsContext.Provider value={{ value, onValueChange }}>
|
||||
<div className={`flex flex-col h-full flex-1 ${className}`}>{children}</div>
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const TabsList = ({ children, className = '' }) => {
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`inline-flex h-8 w-fit justify-center rounded-md p-0.5 gap-[2px] ${className}`}
|
||||
style={{ background: theme.tabs.secondary.inactive.bg }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TabsTrigger = ({ value: triggerValue, children, className = '' }) => {
|
||||
const { value, onValueChange } = useContext(TabsContext);
|
||||
const { theme } = useTheme();
|
||||
const isActive = value === triggerValue;
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={() => onValueChange(triggerValue)}
|
||||
className={`inline-flex items-center justify-center rounded-[4px] p-[8px] text-sm whitespace-nowrap transition-all cursor-pointer border border-transparent hover:opacity-90 ${className}`}
|
||||
style={{
|
||||
background: isActive ? theme.tabs.secondary.active.bg : 'transparent',
|
||||
color: isActive ? theme.tabs.secondary.active.color : theme.tabs.secondary.inactive.color
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export const TabsContent = ({ value: contentValue, children, className = '', dataTestId = '' }) => {
|
||||
const { value } = useContext(TabsContext);
|
||||
const isActive = value === contentValue;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`outline-none flex flex-col h-full flex-1 ${className}`}
|
||||
data-testid={dataTestId}
|
||||
style={{ display: isActive ? 'flex' : 'none' }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -241,6 +241,16 @@ const darkTheme = {
|
||||
active: {
|
||||
color: '#CCCCCC',
|
||||
border: '#F59E0B'
|
||||
},
|
||||
secondary: {
|
||||
active: {
|
||||
bg: '#2D2D2D',
|
||||
color: '#CCCCCC'
|
||||
},
|
||||
inactive: {
|
||||
bg: '#3F3F3F',
|
||||
color: '#CCCCCC'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -242,6 +242,16 @@ const lightTheme = {
|
||||
active: {
|
||||
color: '#343434',
|
||||
border: '#D97706'
|
||||
},
|
||||
secondary: {
|
||||
active: {
|
||||
bg: '#FFFFFF',
|
||||
color: '#343434'
|
||||
},
|
||||
inactive: {
|
||||
bg: '#ECECEE',
|
||||
color: '#989898'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user