From 0c3d20b19834ce09d06524ee4fd7714a3b7d69be Mon Sep 17 00:00:00 2001 From: Pooja Date: Sat, 31 Jan 2026 09:08:42 +0530 Subject: [PATCH] fix: restore cursor focus on save and show placeholder for empty cells (#6795) --- .../CollectionSettings/Headers/index.js | 8 +-- .../Vars/VarsTable/index.js | 4 +- .../src/components/EditableTable/index.js | 4 +- .../EnvironmentVariablesTable/index.js | 2 +- .../FolderSettings/Headers/index.js | 8 +-- .../FolderSettings/Vars/VarsTable/index.js | 4 +- .../RequestPane/Assertions/index.js | 4 +- .../RequestPane/FormUrlEncodedParams/index.js | 4 +- .../RequestPane/MultipartFormParams/index.js | 8 +-- .../RequestPane/QueryParams/index.js | 4 +- .../RequestPane/RequestHeaders/index.js | 8 +-- .../RequestPane/Vars/VarsTable/index.js | 4 +- .../ResponseExampleFileBody/index.js | 4 +- .../index.js | 4 +- .../ResponseExampleHeaders/index.js | 8 +-- .../index.js | 6 +- .../ResponseExampleParams/index.js | 12 ++-- .../ResponseExampleResponseHeaders/index.js | 8 +-- .../ReduxStore/slices/collections/index.js | 57 +++++++++++++++++-- tests/editable-table/editable-table.spec.ts | 49 ++++++++++++++++ 20 files changed, 152 insertions(+), 58 deletions(-) create mode 100644 tests/editable-table/editable-table.spec.ts diff --git a/packages/bruno-app/src/components/CollectionSettings/Headers/index.js b/packages/bruno-app/src/components/CollectionSettings/Headers/index.js index 9eb0a6dd3..1385a98ae 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Headers/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Headers/index.js @@ -56,7 +56,7 @@ const Headers = ({ collection }) => { isKeyField: true, placeholder: 'Name', width: '30%', - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { onChange={(newValue) => onChange(newValue.replace(/[\r\n]/g, ''))} autocomplete={headerAutoCompleteList} collection={collection} - placeholder={isLastEmptyRow ? 'Name' : ''} + placeholder={!value ? 'Name' : ''} /> ) }, @@ -72,7 +72,7 @@ const Headers = ({ collection }) => { key: 'value', name: 'Value', placeholder: 'Value', - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { onChange={onChange} collection={collection} autocomplete={MimeTypes} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> ) } diff --git a/packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js b/packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js index 686c29613..e4f23be7e 100644 --- a/packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js +++ b/packages/bruno-app/src/components/CollectionSettings/Vars/VarsTable/index.js @@ -46,14 +46,14 @@ const VarsTable = ({ collection, vars, varType }) => { ), placeholder: varType === 'request' ? 'Value' : 'Expr', - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( ) } diff --git a/packages/bruno-app/src/components/EditableTable/index.js b/packages/bruno-app/src/components/EditableTable/index.js index 64f0e5132..800831aac 100644 --- a/packages/bruno-app/src/components/EditableTable/index.js +++ b/packages/bruno-app/src/components/EditableTable/index.js @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState, useEffect } from 'react'; import { IconTrash, IconAlertCircle, IconGripVertical, IconMinusVertical } from '@tabler/icons'; import { Tooltip } from 'react-tooltip'; import { uuid } from 'utils/common'; @@ -312,7 +312,7 @@ const EditableTable = ({ className="mousetrap" value={value || ''} readOnly={column.readOnly} - placeholder={isEmpty ? column.placeholder || column.name : ''} + placeholder={!value ? column.placeholder || column.name : ''} onChange={(e) => handleValueChange(row.uid, column.key, e.target.value)} /> {errorIcon} diff --git a/packages/bruno-app/src/components/EnvironmentVariablesTable/index.js b/packages/bruno-app/src/components/EnvironmentVariablesTable/index.js index 8ae4e801d..08998e9cd 100644 --- a/packages/bruno-app/src/components/EnvironmentVariablesTable/index.js +++ b/packages/bruno-app/src/components/EnvironmentVariablesTable/index.js @@ -472,7 +472,7 @@ const EnvironmentVariablesTable = ({ id={`${actualIndex}.name`} name={`${actualIndex}.name`} value={variable.name} - placeholder={isLastEmptyRow ? 'Name' : ''} + placeholder={!variable.value || (typeof variable.value === 'string' && variable.value.trim() === '') ? 'Value' : ''} onChange={(e) => handleNameChange(actualIndex, e)} onBlur={() => handleNameBlur(actualIndex)} onKeyDown={(e) => handleNameKeyDown(actualIndex, e)} diff --git a/packages/bruno-app/src/components/FolderSettings/Headers/index.js b/packages/bruno-app/src/components/FolderSettings/Headers/index.js index 1ac148368..ac3b97b64 100644 --- a/packages/bruno-app/src/components/FolderSettings/Headers/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Headers/index.js @@ -60,7 +60,7 @@ const Headers = ({ collection, folder }) => { isKeyField: true, placeholder: 'Name', width: '30%', - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { onChange={(newValue) => onChange(newValue.replace(/[\r\n]/g, ''))} autocomplete={headerAutoCompleteList} collection={collection} - placeholder={isLastEmptyRow ? 'Name' : ''} + placeholder={!value ? 'Name' : ''} /> ) }, @@ -76,7 +76,7 @@ const Headers = ({ collection, folder }) => { key: 'value', name: 'Value', placeholder: 'Value', - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { collection={collection} item={folder} autocomplete={MimeTypes} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> ) } diff --git a/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js index 1851cff6b..b573065db 100644 --- a/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js +++ b/packages/bruno-app/src/components/FolderSettings/Vars/VarsTable/index.js @@ -51,7 +51,7 @@ const VarsTable = ({ folder, collection, vars, varType }) => { ), placeholder: varType === 'request' ? 'Value' : 'Expr', - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { onChange={onChange} collection={collection} item={folder} - placeholder={isLastEmptyRow ? (varType === 'request' ? 'Value' : 'Expr') : ''} + placeholder={!value ? (varType === 'request' ? 'Value' : 'Expr') : ''} /> ) } diff --git a/packages/bruno-app/src/components/RequestPane/Assertions/index.js b/packages/bruno-app/src/components/RequestPane/Assertions/index.js index 8bb617f9b..7d6864cf7 100644 --- a/packages/bruno-app/src/components/RequestPane/Assertions/index.js +++ b/packages/bruno-app/src/components/RequestPane/Assertions/index.js @@ -125,7 +125,7 @@ const Assertions = ({ item, collection }) => { key: 'value', name: 'Value', width: '30%', - render: ({ row, value, onChange, isLastEmptyRow }) => { + render: ({ row, value, onChange }) => { const { operator, value: assertionValue } = parseAssertionOperator(value); if (isUnaryOperator(operator)) { @@ -141,7 +141,7 @@ const Assertions = ({ item, collection }) => { onRun={handleRun} collection={collection} item={item} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> ); } diff --git a/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js b/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js index e86a04523..6535f4b03 100644 --- a/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js +++ b/packages/bruno-app/src/components/RequestPane/FormUrlEncodedParams/index.js @@ -47,7 +47,7 @@ const FormUrlEncodedParams = ({ item, collection }) => { key: 'value', name: 'Value', placeholder: 'Value', - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { onRun={handleRun} collection={collection} item={item} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> ) } diff --git a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js index e5ce53337..08db324a1 100644 --- a/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js +++ b/packages/bruno-app/src/components/RequestPane/MultipartFormParams/index.js @@ -157,7 +157,7 @@ const MultipartFormParams = ({ item, collection }) => { allowNewlines={true} collection={collection} item={item} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> {!hasTextValue && !isLastEmptyRow && ( @@ -177,12 +177,12 @@ const MultipartFormParams = ({ item, collection }) => { key: 'contentType', name: 'Content-Type', placeholder: 'Auto', - width: '30%', - render: ({ row, value, onChange, isLastEmptyRow }) => ( + width: '20%', + render: ({ value, onChange }) => ( { key: 'value', name: 'Value', placeholder: 'Value', - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { collection={collection} item={item} variablesAutocomplete={true} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> ) } diff --git a/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js b/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js index a7765024d..058651543 100644 --- a/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js +++ b/packages/bruno-app/src/components/RequestPane/RequestHeaders/index.js @@ -66,7 +66,7 @@ const RequestHeaders = ({ item, collection, addHeaderText }) => { isKeyField: true, placeholder: 'Name', width: '30%', - render: ({ value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { onRun={handleRun} collection={collection} item={item} - placeholder={isLastEmptyRow ? 'Name' : ''} + placeholder={!value ? 'Name' : ''} /> ) }, @@ -84,7 +84,7 @@ const RequestHeaders = ({ item, collection, addHeaderText }) => { key: 'value', name: 'Value', placeholder: 'Value', - render: ({ value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { autocomplete={MimeTypes} collection={collection} item={item} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> ) } diff --git a/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js b/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js index df9534a8c..747d3eab4 100644 --- a/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js +++ b/packages/bruno-app/src/components/RequestPane/Vars/VarsTable/index.js @@ -61,7 +61,7 @@ const VarsTable = ({ item, collection, vars, varType }) => { ), placeholder: varType === 'request' ? 'Value' : 'Expr', - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { onRun={handleRun} collection={collection} item={item} - placeholder={isLastEmptyRow ? (varType === 'request' ? 'Value' : 'Expr') : ''} + placeholder={!value ? (varType === 'request' ? 'Value' : 'Expr') : ''} /> ) } diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js index 62a8de94d..19935a2f1 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFileBody/index.js @@ -130,12 +130,12 @@ const ResponseExampleFileBody = ({ item, collection, exampleUid, editMode = fals placeholder: 'Auto', width: '30%', readOnly: !editMode, - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( {}} theme={storedTheme} - placeholder={isLastEmptyRow ? 'Auto' : ''} + placeholder={!value ? 'Auto' : ''} value={value || ''} onChange={onChange} onRun={() => {}} diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js index 3df926e32..c3a2ee4e1 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleFormUrlEncodedParams/index.js @@ -58,7 +58,7 @@ const ResponseExampleFormUrlEncodedParams = ({ item, collection, exampleUid, edi placeholder: 'Value', width: '60%', readOnly: !editMode, - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( {}} collection={collection} item={item} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> ) } diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js index 86faee75f..1087cec62 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleHeaders/index.js @@ -68,7 +68,7 @@ const ResponseExampleHeaders = ({ editMode, item, collection, exampleUid }) => { placeholder: 'Key', width: '40%', readOnly: !editMode, - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { autocomplete={headerAutoCompleteList} onRun={() => {}} collection={collection} - placeholder={isLastEmptyRow ? 'Key' : ''} + placeholder={!value ? 'Key' : ''} /> ) }, @@ -88,7 +88,7 @@ const ResponseExampleHeaders = ({ editMode, item, collection, exampleUid }) => { placeholder: 'Value', width: '60%', readOnly: !editMode, - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { allowNewlines={true} collection={collection} item={item} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> ) } 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 6d6b769c5..1b6d782ee 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleMultipartFormParams/index.js @@ -206,7 +206,7 @@ const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, edit collection={collection} item={item} readOnly={!editMode} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> {!hasTextValue && !isLastEmptyRow && ( @@ -228,11 +228,11 @@ const ResponseExampleMultipartFormParams = ({ item, collection, exampleUid, edit placeholder: 'Auto', width: '30%', readOnly: !editMode, - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( {}} theme={storedTheme} - placeholder={isLastEmptyRow ? 'Auto' : ''} + placeholder={!value ? 'Auto' : ''} value={value || ''} onChange={onChange} onRun={() => {}} diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js index 21470d9ad..3ae4b3fd3 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleRequestPane/ResponseExampleParams/index.js @@ -105,7 +105,7 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => { placeholder: 'Name', width: '40%', readOnly: !editMode, - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { collection={collection} variablesAutocomplete={true} readOnly={!editMode} - placeholder={isLastEmptyRow ? 'Name' : ''} + placeholder={!value ? 'Name' : ''} /> ) }, @@ -125,7 +125,7 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => { placeholder: 'Value', width: '60%', readOnly: !editMode, - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { collection={collection} variablesAutocomplete={true} readOnly={!editMode} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> ) } @@ -154,7 +154,7 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => { placeholder: 'Value', width: '60%', readOnly: !editMode, - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( { collection={collection} variablesAutocomplete={true} readOnly={!editMode} - placeholder={isLastEmptyRow ? 'Value' : ''} + placeholder={!value ? 'Value' : ''} /> ) } diff --git a/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js b/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js index bdc6fe4c7..e15a9ee2b 100644 --- a/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js +++ b/packages/bruno-app/src/components/ResponseExample/ResponseExampleResponsePane/ResponseExampleResponseHeaders/index.js @@ -124,7 +124,7 @@ const ResponseExampleResponseHeaders = ({ editMode, item, collection, exampleUid placeholder: 'Key', width: '40%', readOnly: !editMode, - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( {}} collection={collection} readOnly={!editMode} - placeholder={isLastEmptyRow ? 'Key' : ''} + placeholder={!value ? 'Key' : ''} /> ) }, @@ -144,7 +144,7 @@ const ResponseExampleResponseHeaders = ({ editMode, item, collection, exampleUid placeholder: 'Value', width: '60%', readOnly: !editMode, - render: ({ row, value, onChange, isLastEmptyRow }) => ( + render: ({ value, onChange }) => ( ) } diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js index efec9f44a..a31536246 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/collections/index.js @@ -66,6 +66,51 @@ const wsStatusCodes = { 1015: 'TLS_HANDSHAKE' }; +/** + * Preserves UIDs from existing array items when merging with new data. + * UIDs are matched by position to keep React keys stable after file reloads. + */ +const preserveUidsAtPaths = (existing, updated, paths) => { + if (!existing || !updated) return updated; + + const merged = cloneDeep(updated); + + paths.forEach((path) => { + const newArray = get(merged, path); + const existingArray = get(existing, path, []); + + if (Array.isArray(newArray) && newArray.length) { + set( + merged, + path, + newArray.map((item, i) => (existingArray[i]?.uid ? { ...item, uid: existingArray[i].uid } : item)) + ); + } + }); + + return merged; +}; + +// Paths containing arrays with UIDs that need preservation +const REQUEST_UID_PATHS = [ + 'params', + 'headers', + 'vars.req', + 'vars.res', + 'assertions', + 'body.formUrlEncoded', + 'body.multipartForm', + 'body.file' +]; + +const ROOT_UID_PATHS = ['request.headers', 'request.vars.req', 'request.vars.res']; + +const mergeRequestWithPreservedUids = (existingRequest, newRequest) => + preserveUidsAtPaths(existingRequest, newRequest, REQUEST_UID_PATHS); + +const mergeRootWithPreservedUids = (existingRoot, newRoot) => + preserveUidsAtPaths(existingRoot, newRoot, ROOT_UID_PATHS); + const initialState = { collections: [], collectionSortOrder: 'default', @@ -2561,7 +2606,7 @@ export const collectionsSlice = createSlice({ const collection = findCollectionByUid(state.collections, file.meta.collectionUid); if (isCollectionRoot) { if (collection) { - collection.root = file.data; + collection.root = mergeRootWithPreservedUids(collection.root, file.data); } return; } @@ -2573,7 +2618,7 @@ export const collectionsSlice = createSlice({ if (file?.data?.meta?.name) { folderItem.name = file?.data?.meta?.name; } - folderItem.root = file.data; + folderItem.root = mergeRootWithPreservedUids(folderItem.root, file.data); if (file?.data?.meta?.seq) { folderItem.seq = file.data?.meta?.seq; } @@ -2621,7 +2666,7 @@ export const collectionsSlice = createSlice({ currentItem.type = file.data.type; currentItem.seq = file.data.seq; currentItem.tags = file.data.tags; - currentItem.request = file.data.request; + currentItem.request = mergeRequestWithPreservedUids(currentItem.request, file.data.request); currentItem.filename = file.meta.name; currentItem.pathname = file.meta.pathname; currentItem.settings = file.data.settings; @@ -2700,7 +2745,7 @@ export const collectionsSlice = createSlice({ const collection = findCollectionByUid(state.collections, file.meta.collectionUid); if (isCollectionRoot) { if (collection) { - collection.root = file.data; + collection.root = mergeRootWithPreservedUids(collection.root, file.data); } return; } @@ -2715,7 +2760,7 @@ export const collectionsSlice = createSlice({ if (file?.data?.meta?.seq) { folderItem.seq = file?.data?.meta?.seq; } - folderItem.root = file.data; + folderItem.root = mergeRootWithPreservedUids(folderItem.root, file.data); } return; } @@ -2740,7 +2785,7 @@ export const collectionsSlice = createSlice({ item.type = file.data.type; item.seq = file.data.seq; item.tags = file.data.tags; - item.request = file.data.request; + item.request = mergeRequestWithPreservedUids(item.request, file.data.request); item.settings = file.data.settings; item.examples = file.data.examples; item.filename = file.meta.name; diff --git a/tests/editable-table/editable-table.spec.ts b/tests/editable-table/editable-table.spec.ts new file mode 100644 index 000000000..27dd0204e --- /dev/null +++ b/tests/editable-table/editable-table.spec.ts @@ -0,0 +1,49 @@ +import { test, expect } from '../../playwright'; +import { createCollection, closeAllCollections, createRequest, selectRequestPaneTab } from '../utils/page'; + +test.describe('EditableTable - Focus and Placeholder', () => { + test.afterEach(async ({ page }) => { + await closeAllCollections(page); + }); + + test('Cursor focus restored after save and placeholder shown for empty value', async ({ page, createTmpDir }) => { + const collectionName = 'test-editable-table'; + + // Create a new collection + await createCollection(page, collectionName, await createTmpDir()); + + // Create a request + await createRequest(page, 'Test Request', collectionName, { + url: 'https://httpbin.org/get' + }); + + // Navigate to Params tab + await selectRequestPaneTab(page, 'Params'); + + // Find the Query params table + const queryTable = page.locator('table').first(); + const firstRow = queryTable.locator('tbody tr').first(); + + // Get the Name input (regular input) + const nameInput = firstRow.locator('input[type="text"]').first(); + await nameInput.click(); + await page.keyboard.type('testParam'); + + // Verify input has focus before save + await expect(nameInput).toBeFocused(); + + // Save the request + await page.keyboard.press('Meta+s'); + + // Wait for save toast + await expect(page.getByText('Request saved successfully').last()).toBeVisible(); + + // Verify cursor focus is restored after save + await expect(nameInput).toBeFocused(); + + // Verify placeholder shows for empty Value field + const valueCell = firstRow.locator('[data-testid="column-value"]'); + const placeholder = valueCell.locator('pre.CodeMirror-placeholder'); + await expect(placeholder).toHaveText('Value'); + }); +});