diff --git a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/StyledWrapper.js b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/StyledWrapper.js
index 6d7448307..b191ace70 100644
--- a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/StyledWrapper.js
+++ b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/StyledWrapper.js
@@ -15,7 +15,7 @@ const Wrapper = styled.div`
transition: color 0.15s ease;
&:hover {
- color: ${(props) => props.theme.colors.text.link};
+ color: ${(props) => props.theme.text};
}
}
diff --git a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js
index 28d9b1a4b..0cd73b960 100644
--- a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js
+++ b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js
@@ -65,12 +65,21 @@ const MultipartFormParams = ({ item, collection }) => {
const currentParams = item.draft
? get(item, 'draft.request.body.multipartForm')
: get(item, 'request.body.multipartForm');
- const updatedParams = (currentParams || []).map((p) => {
- if (p.uid === row.uid) {
- return { ...p, type: 'file', value: processedPaths };
- }
- return p;
- });
+ const existsInParams = (currentParams || []).some((p) => p.uid === row.uid);
+ let updatedParams;
+ if (existsInParams) {
+ updatedParams = currentParams.map((p) => {
+ if (p.uid === row.uid) {
+ return { ...p, type: 'file', value: processedPaths };
+ }
+ return p;
+ });
+ } else {
+ updatedParams = [
+ ...(currentParams || []),
+ { uid: row.uid, name: row.name || '', enabled: true, type: 'file', value: processedPaths, contentType: '' }
+ ];
+ }
handleParamsChange(updatedParams);
})
.catch((error) => {
@@ -139,13 +148,6 @@ const MultipartFormParams = ({ item, collection }) => {
if (fileName) {
return (
-
{
item={item}
/>
+
);
}
diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/StyledWrapper.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/StyledWrapper.js
index b025e456d..58ade60c7 100644
--- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/StyledWrapper.js
+++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/StyledWrapper.js
@@ -71,7 +71,29 @@ const Wrapper = styled.div`
.upload-btn,
.clear-file-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 4px;
+ color: ${(props) => props.theme.colors.text.muted};
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ border-radius: 4px;
+ transition: color 0.15s ease;
flex-shrink: 0;
+
+ &:hover {
+ color: ${(props) => props.theme.text};
+ }
+ }
+
+ .clear-file-btn:hover {
+ color: ${(props) => props.theme.colors.text.danger};
+ }
+
+ .file-value-cell {
+ width: 100%;
}
.value-cell {
diff --git a/tests/request/multipart-form/multipart-form-file-select.spec.ts b/tests/request/multipart-form/multipart-form-file-select.spec.ts
new file mode 100644
index 000000000..799df32b2
--- /dev/null
+++ b/tests/request/multipart-form/multipart-form-file-select.spec.ts
@@ -0,0 +1,78 @@
+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 - File Select Without Key', () => {
+ let tmpDir: string;
+ let testFilePath: string;
+
+ test.afterAll(async ({ page, electronApp }) => {
+ await electronApp.evaluate(({ dialog }) => {
+ if ((dialog as any).__originalShowOpenDialog) {
+ dialog.showOpenDialog = (dialog as any).__originalShowOpenDialog;
+ delete (dialog as any).__originalShowOpenDialog;
+ }
+ });
+ await closeAllCollections(page);
+ });
+
+ test.beforeAll(async ({ page, electronApp, createTmpDir }) => {
+ tmpDir = await createTmpDir('multipart-file-select');
+
+ // Create a temp file that the mocked dialog will "select"
+ testFilePath = path.join(tmpDir, 'test-file.txt');
+ await fs.promises.writeFile(testFilePath, 'hello world');
+ expect(fs.existsSync(testFilePath)).toBe(true);
+
+ await electronApp.evaluate(({ dialog }, filePath: string) => {
+ (dialog as any).__originalShowOpenDialog = dialog.showOpenDialog;
+ dialog.showOpenDialog = async () => ({
+ canceled: false,
+ filePaths: [filePath]
+ });
+ }, testFilePath);
+
+ await test.step('Create collection and request', async () => {
+ await createCollection(page, 'multipart-file-select', tmpDir);
+ await createRequest(page, 'test-file-select', '', {
+ url: 'https://testbench-sanity.usebruno.com/api/echo/json',
+ method: 'POST',
+ inFolder: false
+ });
+ });
+
+ await test.step('Open the request', async () => {
+ await openCollection(page, 'multipart-file-select');
+ await openRequest(page, 'multipart-file-select', 'test-file-select', { persist: true });
+ });
+
+ await test.step('Switch body mode to Multipart Form', async () => {
+ await selectRequestPaneTab(page, 'Body');
+ const locators = buildCommonLocators(page);
+ await locators.request.bodyModeSelector().click();
+ await page.locator('.dropdown-item').filter({ hasText: 'Multipart Form' }).click();
+ });
+ });
+
+ test('file select should work on empty row without a key', async ({ page }) => {
+ const table = buildCommonLocators(page).table('editable-table');
+
+ await test.step('Click upload on empty last row (no key entered)', async () => {
+ const lastRow = table.allRows().last();
+ const uploadBtn = lastRow.locator('.upload-btn');
+ await expect(uploadBtn).toBeVisible();
+ await uploadBtn.click();
+ });
+
+ await test.step('Verify the file name appears in the row', async () => {
+ const fileCell = table.allRows().locator('.file-value-cell').first();
+ await expect(fileCell).toBeVisible();
+ await expect(fileCell).toContainText('test-file.txt');
+ });
+
+ // Save the request to clear draft state
+ await saveRequest(page);
+ });
+});