mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-12 10:21:30 +00:00
fix: copy response based on preview toggle and selected format (#6436)
This commit is contained in:
@@ -1,15 +1,27 @@
|
||||
import React, { useState, useEffect, forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
import React, { useState, useEffect, forwardRef, useImperativeHandle, useRef, useMemo } from 'react';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
import { IconCopy, IconCheck } from '@tabler/icons';
|
||||
import classnames from 'classnames';
|
||||
import ActionIcon from 'ui/ActionIcon/index';
|
||||
import { formatResponse } from 'utils/common';
|
||||
|
||||
// Hook to get copy response function
|
||||
export const useResponseCopy = (item) => {
|
||||
const response = item.response || {};
|
||||
export const useResponseCopy = (item, selectedFormat, selectedTab, data, dataBuffer) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const textToCopy = useMemo(() => {
|
||||
// If preview is on, copy raw data (what's shown in TextPreview)
|
||||
if (selectedTab === 'preview') {
|
||||
return typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
||||
}
|
||||
// If editor is on, copy formatted data based on selected format
|
||||
if (selectedFormat && data && dataBuffer) {
|
||||
return formatResponse(data, dataBuffer, selectedFormat, null);
|
||||
}
|
||||
return typeof data === 'string' ? data : JSON.stringify(data, null, 2);
|
||||
}, [data, dataBuffer, selectedFormat, selectedTab]);
|
||||
|
||||
useEffect(() => {
|
||||
if (copied) {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -21,10 +33,6 @@ export const useResponseCopy = (item) => {
|
||||
|
||||
const copyResponse = async () => {
|
||||
try {
|
||||
const textToCopy = typeof response.data === 'string'
|
||||
? response.data
|
||||
: JSON.stringify(response.data, null, 2);
|
||||
|
||||
await navigator.clipboard.writeText(textToCopy);
|
||||
toast.success('Response copied to clipboard');
|
||||
setCopied(true);
|
||||
@@ -33,11 +41,11 @@ export const useResponseCopy = (item) => {
|
||||
}
|
||||
};
|
||||
|
||||
return { copyResponse, copied, hasData: !!response.data };
|
||||
return { copyResponse, copied, hasData: !!data };
|
||||
};
|
||||
|
||||
const ResponseCopy = forwardRef(({ item, children }, ref) => {
|
||||
const { copyResponse, copied, hasData } = useResponseCopy(item);
|
||||
const ResponseCopy = forwardRef(({ item, children, selectedFormat, selectedTab, data, dataBuffer }, ref) => {
|
||||
const { copyResponse, copied, hasData } = useResponseCopy(item, selectedFormat, selectedTab, data, dataBuffer);
|
||||
const elementRef = useRef(null);
|
||||
|
||||
const isDisabled = !hasData ? true : false;
|
||||
|
||||
@@ -37,7 +37,7 @@ const MenuIcon = forwardRef((props, ref) => (
|
||||
|
||||
MenuIcon.displayName = 'MenuIcon';
|
||||
|
||||
const ResponsePaneActions = ({ item, collection, responseSize }) => {
|
||||
const ResponsePaneActions = ({ item, collection, responseSize, selectedFormat, selectedTab, data, dataBuffer }) => {
|
||||
const { orientation } = useResponseLayoutToggle();
|
||||
|
||||
// Refs to access child component imperative handles (click, isDisabled)
|
||||
@@ -111,13 +111,19 @@ const ResponsePaneActions = ({ item, collection, responseSize }) => {
|
||||
</MenuDropdown>
|
||||
</div>
|
||||
<div className="actions-buttons flex items-center gap-[2px]">
|
||||
<ResponseCopy ref={copyButtonRef} item={item} />
|
||||
<ResponseCopy
|
||||
ref={copyButtonRef}
|
||||
item={item}
|
||||
selectedFormat={selectedFormat}
|
||||
selectedTab={selectedTab}
|
||||
data={data}
|
||||
dataBuffer={dataBuffer}
|
||||
/>
|
||||
<ResponseBookmark ref={bookmarkButtonRef} item={item} collection={collection} responseSize={responseSize} />
|
||||
<ResponseDownload ref={downloadButtonRef} item={item} />
|
||||
<ResponseClear ref={clearButtonRef} item={item} collection={collection} />
|
||||
<ResponseLayoutToggle ref={layoutToggleButtonRef} />
|
||||
</div>
|
||||
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -248,8 +248,16 @@ const ResponsePane = ({ item, collection }) => {
|
||||
<div className="flex items-center response-pane-actions">
|
||||
{focusedTab?.responsePaneTab === 'timeline' ? (
|
||||
<ClearTimeline item={item} collection={collection} />
|
||||
) : (item?.response && !item?.response?.error) ? (
|
||||
<ResponsePaneActions item={item} collection={collection} responseSize={responseSize} />
|
||||
) : item?.response && !item?.response?.error ? (
|
||||
<ResponsePaneActions
|
||||
item={item}
|
||||
collection={collection}
|
||||
responseSize={responseSize}
|
||||
selectedFormat={selectedFormat}
|
||||
selectedTab={selectedTab}
|
||||
data={response.data}
|
||||
dataBuffer={response.dataBuffer}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,9 +4,10 @@ import {
|
||||
closeAllCollections,
|
||||
createCollection,
|
||||
createRequest,
|
||||
sendRequest
|
||||
sendRequest,
|
||||
switchResponseFormat,
|
||||
switchToEditorTab
|
||||
} from '../utils/page/actions';
|
||||
import { buildCommonLocators } from '../utils/page/locators';
|
||||
|
||||
test.describe('Response Pane Actions', () => {
|
||||
test.afterAll(async ({ page }) => {
|
||||
@@ -15,7 +16,6 @@ test.describe('Response Pane Actions', () => {
|
||||
|
||||
test('should copy response to clipboard', async ({ page, createTmpDir }) => {
|
||||
const collectionName = 'response-copy-test';
|
||||
const locators = buildCommonLocators(page);
|
||||
|
||||
await test.step('Create collection and request', async () => {
|
||||
await createCollection(page, collectionName, await createTmpDir(collectionName));
|
||||
@@ -31,4 +31,33 @@ test.describe('Response Pane Actions', () => {
|
||||
await expect(page.getByText('Response copied to clipboard')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test('should copy Base64 when editor mode and Base64 format selected', async ({ page, createTmpDir }) => {
|
||||
const collectionName = 'response-copy-base64-test';
|
||||
|
||||
await test.step('Create collection and request', async () => {
|
||||
await createCollection(page, collectionName, await createTmpDir(collectionName));
|
||||
await createRequest(page, 'base64-copy-test', collectionName, {
|
||||
url: 'https://testbench-sanity.usebruno.com/ping'
|
||||
});
|
||||
});
|
||||
|
||||
await test.step('Send request and wait for response', async () => {
|
||||
await sendRequest(page, 200);
|
||||
});
|
||||
|
||||
await test.step('Switch to Base64 format (editor mode - preview OFF)', async () => {
|
||||
await switchToEditorTab(page);
|
||||
await switchResponseFormat(page, 'Base64');
|
||||
});
|
||||
|
||||
await test.step('Copy response and verify clipboard contains Base64', async () => {
|
||||
await clickResponseAction(page, 'response-copy-btn');
|
||||
await expect(page.getByText('Response copied to clipboard')).toBeVisible();
|
||||
|
||||
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
|
||||
// "pong" in Base64 is "cG9uZw=="
|
||||
expect(clipboardText).toBe('cG9uZw==');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user