mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
fix: response viewer not updating when focused (read-only editors) (#7218)
* fix: update cursor state handling in CodeEditor for read-only mode * tests: add response pane update test for re-sent requests * refactor: add data-testid attributes for improved testing in RequestBody and RequestBodyMode components; update locators and tests accordingly * test: verify response status code is 200 * test: enhance response pane update tests to verify body editor content after request re-sent * test: add data-testid for method selector and update locators for improved testability
This commit is contained in:
@@ -236,7 +236,8 @@ export default class CodeEditor extends React.Component {
|
||||
// TODO: temporary fix for keeping cursor state when auto save and new line insertion collide PR#7098
|
||||
const nextValue = this.props.value ?? '';
|
||||
const currentValue = this.editor.getValue();
|
||||
if (this.editor.hasFocus?.() && currentValue !== nextValue) {
|
||||
// Skip updating only when focused and editable; read-only editors (e.g. response viewer) must always show new value
|
||||
if (this.editor.hasFocus?.() && currentValue !== nextValue && !this.props.readOnly) {
|
||||
this.cachedValue = currentValue;
|
||||
} else {
|
||||
const cursor = this.editor.getCursor();
|
||||
|
||||
@@ -179,6 +179,7 @@ const HttpMethodSelector = ({ method = DEFAULT_METHOD, onMethodSelect, showCaret
|
||||
items={menuItems}
|
||||
placement="bottom-start"
|
||||
selectedItemId={selectedItemId}
|
||||
data-testid="method-selector"
|
||||
>
|
||||
<TriggerButton method={method} showCaret={showCaret} methodSpanRef={methodSpanRef} />
|
||||
</MenuDropdown>
|
||||
|
||||
@@ -103,7 +103,7 @@ const RequestBodyMode = ({ item, collection }) => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="inline-flex items-center cursor-pointer body-mode-selector">
|
||||
<div className="inline-flex items-center cursor-pointer body-mode-selector" data-testid="request-body-mode-selector">
|
||||
<MenuDropdown
|
||||
items={menuItems}
|
||||
placement="bottom-end"
|
||||
|
||||
@@ -46,7 +46,7 @@ const RequestBody = ({ item, collection }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full">
|
||||
<StyledWrapper className="w-full" data-testid="request-body-editor">
|
||||
<CodeEditor
|
||||
collection={collection}
|
||||
item={item}
|
||||
|
||||
82
tests/request/response-pane-update-when-focused.spec.ts
Normal file
82
tests/request/response-pane-update-when-focused.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { test, expect } from '../../playwright';
|
||||
import {
|
||||
createCollection,
|
||||
createRequest,
|
||||
sendRequest,
|
||||
openRequest,
|
||||
closeAllCollections,
|
||||
selectRequestPaneTab
|
||||
} from '../utils/page';
|
||||
import { buildCommonLocators } from '../utils/page/locators';
|
||||
|
||||
const runShortcut = process.platform === 'darwin' ? 'Meta+Enter' : 'Control+Enter';
|
||||
const selectAllShortcut = process.platform === 'darwin' ? 'Meta+a' : 'Control+a';
|
||||
const echoUrl = 'https://echo.usebruno.com';
|
||||
|
||||
test.describe.serial('Response pane updates when focused and request is re-sent', () => {
|
||||
const collectionName = 'response-pane-update-test';
|
||||
const requestName = 'Echo Request';
|
||||
|
||||
test.beforeAll(async ({ page, createTmpDir }) => {
|
||||
const collectionPath = await createTmpDir('response-pane-collection');
|
||||
await createCollection(page, collectionName, collectionPath);
|
||||
await createRequest(page, requestName, collectionName, { url: echoUrl, method: 'POST' });
|
||||
});
|
||||
|
||||
test.afterAll(async ({ page }) => {
|
||||
await closeAllCollections(page);
|
||||
});
|
||||
|
||||
test('Response pane shows new response after re-send with Cmd+Enter while focused in response', async ({
|
||||
page
|
||||
}) => {
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
await test.step('Open request and set body to {"run": 1}', async () => {
|
||||
await openRequest(page, collectionName, requestName);
|
||||
|
||||
await selectRequestPaneTab(page, 'Body');
|
||||
await locators.request.bodyModeSelector().click();
|
||||
await locators.dropdown.item('JSON').click();
|
||||
|
||||
const bodyCodeMirror = locators.request.bodyEditor().locator('.CodeMirror');
|
||||
await bodyCodeMirror.click();
|
||||
await page.keyboard.press(selectAllShortcut);
|
||||
await page.keyboard.type('{"run": 1}');
|
||||
await expect(locators.request.bodyEditor()).toContainText('"run": 1', { timeout: 5000 });
|
||||
});
|
||||
|
||||
await test.step('Send first request and verify response contains run: 1', async () => {
|
||||
await sendRequest(page, 200, 15000);
|
||||
const runEquals1 = /"run":\s*1/; // "run" with value 1, optional space after colon
|
||||
await expect(locators.response.previewContainer()).toContainText(runEquals1);
|
||||
});
|
||||
|
||||
await test.step('Change body to {"run": 2}', async () => {
|
||||
await selectRequestPaneTab(page, 'Body');
|
||||
const bodyCodeMirror = locators.request.bodyEditor().locator('.CodeMirror');
|
||||
await bodyCodeMirror.click();
|
||||
await page.keyboard.press(selectAllShortcut);
|
||||
await page.keyboard.type('{"run": 2}');
|
||||
await expect(locators.request.bodyEditor()).toContainText('"run": 2', { timeout: 5000 });
|
||||
});
|
||||
|
||||
await test.step('Click inside response pane (Raw/JSON editor) to give it focus', async () => {
|
||||
const responseEditor = locators.response.previewContainerCodeMirror();
|
||||
await responseEditor.waitFor({ state: 'visible', timeout: 5000 });
|
||||
await responseEditor.click();
|
||||
});
|
||||
|
||||
await test.step('Press Cmd+Enter / Ctrl+Enter to re-send request', async () => {
|
||||
await page.keyboard.press(runShortcut);
|
||||
await expect(locators.response.statusCode()).toContainText('200', { timeout: 15000 });
|
||||
});
|
||||
|
||||
await test.step('Response pane must show new response (run: 2)', async () => {
|
||||
const responseBody = locators.response.previewContainer();
|
||||
// Must show the new response body: single JSON object with "run": 2
|
||||
const runEquals2 = /"run":\s*2/;
|
||||
await expect(responseBody).toContainText(runEquals2, { timeout: 5000 });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -89,8 +89,11 @@ const createCollection = async (page, collectionName: string, collectionLocation
|
||||
});
|
||||
};
|
||||
|
||||
const STANDARD_HTTP_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD', 'TRACE', 'CONNECT'];
|
||||
|
||||
type CreateRequestOptions = {
|
||||
url?: string;
|
||||
method?: string;
|
||||
inFolder?: boolean;
|
||||
};
|
||||
|
||||
@@ -241,7 +244,7 @@ const createRequest = async (
|
||||
parentName: string,
|
||||
options: CreateRequestOptions = {}
|
||||
) => {
|
||||
const { url, inFolder = false } = options;
|
||||
const { url, method, inFolder = false } = options;
|
||||
const parentType = inFolder ? 'folder' : 'collection';
|
||||
|
||||
await test.step(`Create request "${requestName}" in ${parentType} "${parentName}"`, async () => {
|
||||
@@ -258,6 +261,19 @@ const createRequest = async (
|
||||
await locators.dropdown.item('New Request').click();
|
||||
await page.getByPlaceholder('Request Name').fill(requestName);
|
||||
|
||||
if (method) {
|
||||
await page.locator('.bruno-modal .method-selector').click();
|
||||
const isStandardMethod = STANDARD_HTTP_METHODS.includes(method.toUpperCase());
|
||||
if (isStandardMethod) {
|
||||
await locators.modal.newRequestMethodOption(method).click();
|
||||
} else {
|
||||
await locators.modal.newRequestMethodOption('add-custom').click();
|
||||
await page.locator('.bruno-modal .method-selector input').fill(method);
|
||||
await page.keyboard.press('Enter');
|
||||
}
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
if (url) {
|
||||
await page.locator('#new-request-url .CodeMirror').click();
|
||||
await page.keyboard.type(url);
|
||||
|
||||
@@ -51,7 +51,8 @@ export const buildCommonLocators = (page: Page) => ({
|
||||
closeButton: () => page.locator('.bruno-modal').getByTestId('modal-close-button'),
|
||||
card: () => page.locator('.bruno-modal-card'),
|
||||
footer: () => page.locator('.bruno-modal-footer'),
|
||||
submitButton: () => page.locator('.bruno-modal-footer .submit')
|
||||
submitButton: () => page.locator('.bruno-modal-footer .submit'),
|
||||
newRequestMethodOption: (id: string) => page.getByTestId(`method-selector-${id.toLowerCase()}`)
|
||||
},
|
||||
environment: {
|
||||
selector: () => page.getByTestId('environment-selector-trigger'),
|
||||
@@ -73,7 +74,9 @@ export const buildCommonLocators = (page: Page) => ({
|
||||
methodDropdown: () => page.getByTestId('request-method-selector'),
|
||||
newRequestUrl: () => page.locator('#new-request-url .CodeMirror'),
|
||||
requestNameInput: () => page.getByPlaceholder('Request Name'),
|
||||
requestTestId: () => page.getByTestId('request-name')
|
||||
requestTestId: () => page.getByTestId('request-name'),
|
||||
bodyModeSelector: () => page.getByTestId('request-body-mode-selector'),
|
||||
bodyEditor: () => page.getByTestId('request-body-editor')
|
||||
},
|
||||
tags: {
|
||||
input: () => page.getByTestId('tag-input').getByRole('textbox'),
|
||||
@@ -88,6 +91,7 @@ export const buildCommonLocators = (page: Page) => ({
|
||||
formatTab: () => page.getByTestId('format-response-tab'),
|
||||
formatTabDropdown: () => page.getByTestId('format-response-tab-dropdown'),
|
||||
previewContainer: () => page.getByTestId('response-preview-container'),
|
||||
previewContainerCodeMirror: () => page.getByTestId('response-preview-container').locator('.CodeMirror').first(),
|
||||
codeLine: () => page.locator('.response-pane .editor-container .CodeMirror-line'),
|
||||
jsonTreeLine: () => page.locator('.response-pane .object-content')
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user