diff --git a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js index 9a279e5e5..72c0d1e21 100644 --- a/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js +++ b/packages/bruno-app/src/components/ResponsePane/ResponseCopy/index.js @@ -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; diff --git a/packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js b/packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js index ed905ede0..7d6172d68 100644 --- a/packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js +++ b/packages/bruno-app/src/components/ResponsePane/ResponsePaneActions/index.js @@ -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 }) => {
- +
- ); }; diff --git a/packages/bruno-app/src/components/ResponsePane/index.js b/packages/bruno-app/src/components/ResponsePane/index.js index 7c82ae69e..1cf4a402e 100644 --- a/packages/bruno-app/src/components/ResponsePane/index.js +++ b/packages/bruno-app/src/components/ResponsePane/index.js @@ -248,8 +248,16 @@ const ResponsePane = ({ item, collection }) => {
{focusedTab?.responsePaneTab === 'timeline' ? ( - ) : (item?.response && !item?.response?.error) ? ( - + ) : item?.response && !item?.response?.error ? ( + ) : null}
diff --git a/tests/response/response-actions.spec.ts b/tests/response/response-actions.spec.ts index d85442564..12b4c11b9 100644 --- a/tests/response/response-actions.spec.ts +++ b/tests/response/response-actions.spec.ts @@ -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=='); + }); + }); });