diff --git a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js index 0cd73b96081..9958b1e5d9b 100644 --- a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js +++ b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js @@ -52,7 +52,7 @@ const MultipartFormParams = ({ item, collection }) => { }, [dispatch, collection.uid, item.uid]); const handleBrowseFiles = useCallback((row, onChange) => { - dispatch(browseFiles()) + dispatch(browseFiles([], ['multiSelections'])) .then((filePaths) => { const processedPaths = filePaths.map((filePath) => { const collectionDir = collection.pathname; diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js index ef327f9a2b9..9c800900e14 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js @@ -48,7 +48,7 @@ const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, edit const handleBrowseFiles = useCallback((row, onChange) => { if (!editMode) return; - dispatch(browseFiles()) + dispatch(browseFiles([], ['multiSelections'])) .then((filePaths) => { const processedPaths = filePaths.map((filePath) => { const collectionDir = collection.pathname; @@ -212,11 +212,11 @@ const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, edit
{}} + onSave={() => { }} theme={storedTheme} value={value || ''} onChange={(newValue) => handleValueChange(row, newValue, onChange)} - onRun={() => {}} + onRun={() => { }} allowNewlines={true} collection={collection} item={item} @@ -243,12 +243,12 @@ const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, edit readOnly: !editMode, render: ({ value, onChange }) => ( {}} + onSave={() => { }} theme={storedTheme} placeholder={!value ? 'Auto' : ''} value={value || ''} onChange={onChange} - onRun={() => {}} + onRun={() => { }} collection={collection} readOnly={!editMode} /> diff --git a/tests/request/multipart-form/multipart-form-multi-file-select.spec.ts b/tests/request/multipart-form/multipart-form-multi-file-select.spec.ts new file mode 100644 index 00000000000..157d83620e9 --- /dev/null +++ b/tests/request/multipart-form/multipart-form-multi-file-select.spec.ts @@ -0,0 +1,138 @@ +import { test, expect } from '../../../playwright'; +import { + closeAllCollections, + createCollection, + createRequest, + openCollection, + openRequest, + saveRequest, + selectRequestPaneTab +} from '../../utils/page'; +import { buildCommonLocators } from '../../utils/page/locators'; +import * as fs from 'fs'; +import * as path from 'path'; + +test.describe.serial('Multipart Form - Multi-File Selection', () => { + let tmpDir: string; + let testFile1Path: string; + let testFile2Path: string; + let testFile3Path: string; + + test.afterEach(async ({ electronApp }) => { + await electronApp.evaluate(({ dialog }) => { + if ((dialog as any).__originalShowOpenDialog) { + dialog.showOpenDialog = (dialog as any).__originalShowOpenDialog; + } + }); + }); + + test.afterAll(async ({ page }) => { + await closeAllCollections(page); + }); + + test.beforeAll(async ({ page, electronApp, createTmpDir }) => { + tmpDir = await createTmpDir('multipart-multi-file-select'); + + testFile1Path = path.join(tmpDir, 'file1.txt'); + testFile2Path = path.join(tmpDir, 'file2.txt'); + testFile3Path = path.join(tmpDir, 'file3.pdf'); + + await fs.promises.writeFile(testFile1Path, '1'); + await fs.promises.writeFile(testFile2Path, '2'); + await fs.promises.writeFile(testFile3Path, '3'); + + await electronApp.evaluate(({ dialog }, filePaths: string[]) => { + (dialog as any).__originalShowOpenDialog = dialog.showOpenDialog; + dialog.showOpenDialog = async () => ({ + canceled: false, + filePaths + }); + }, [testFile1Path, testFile2Path, testFile3Path]); + + await test.step('Create collection and request', async () => { + await createCollection(page, 'multipart-multi-file', tmpDir); + await createRequest(page, 'test-multi-file', 'multipart-multi-file', { + url: 'https://testbench-sanity.usebruno.com/api/echo/json', + method: 'POST', + inFolder: false + }); + }); + + await test.step('Open request and set multipart', async () => { + await openCollection(page, 'multipart-multi-file'); + await openRequest(page, 'multipart-multi-file', 'test-multi-file', { persist: true }); + + await selectRequestPaneTab(page, 'Body'); + const locators = buildCommonLocators(page); + await locators.request.bodyModeSelector().click(); + await page.locator('.dropdown-item').filter({ hasText: 'Multipart Form' }).click(); + }); + }); + + test('should select multiple files', async ({ page }) => { + const table = buildCommonLocators(page).table('editable-table'); + const row = table.allRows().first(); + + await test.step('Enter key and upload', async () => { + await row.locator('td').nth(1).locator('input').fill('attachments'); + await page.keyboard.press('Tab'); + + const uploadBtn = row.locator('.upload-btn'); + await expect(uploadBtn).toBeVisible(); + await uploadBtn.click(); + }); + + await test.step('Verify file count', async () => { + const fileCell = row.locator('.file-value-cell'); + await expect(fileCell).toContainText('3 file(s)'); + }); + + await saveRequest(page); + }); + + test('should show filename for single file', async ({ page, electronApp }) => { + const table = buildCommonLocators(page).table('editable-table'); + const row = table.allRows().last(); + + await electronApp.evaluate(({ dialog }, filePath: string) => { + if ((dialog as any).__originalShowOpenDialog === undefined) { + (dialog as any).__originalShowOpenDialog = dialog.showOpenDialog; + } + dialog.showOpenDialog = async () => ({ + canceled: false, + filePaths: [filePath] + }); + }, testFile1Path); + + await test.step('Upload single file', async () => { + await row.locator('td').nth(1).locator('input').fill('single'); + await page.keyboard.press('Tab'); + + await row.locator('.upload-btn').click(); + }); + + await test.step('Verify filename', async () => { + const fileCell = row.locator('.file-value-cell'); + await expect(fileCell).toContainText('file1.txt'); + }); + + await saveRequest(page); + }); + + test('should clear files', async ({ page }) => { + const table = buildCommonLocators(page).table('editable-table'); + const row = table.allRows().first(); + + await test.step('Clear files', async () => { + const clearBtn = row.locator('.clear-file-btn'); + await expect(clearBtn).toBeVisible(); + await clearBtn.click(); + }); + + await test.step('Verify cleared', async () => { + await expect(row.locator('.upload-btn')).toBeVisible(); + }); + + await saveRequest(page); + }); +}); diff --git a/tests/request/multipart-form/multipart-form-file-select.spec.ts b/tests/request/multipart-form/multipart-form-single-file-select.spec.ts similarity index 100% rename from tests/request/multipart-form/multipart-form-file-select.spec.ts rename to tests/request/multipart-form/multipart-form-single-file-select.spec.ts