{rows.map((row, index) => {
- const isBlankRow = index === rows.length - 1 && (!row.name || row.name.trim() === '');
+ const isBlankRow = index === rows.length - 1 && isRowBlank(row);
return (
{
expect(names(out)).toEqual(['b', '']);
});
});
+
+describe('a row that has a value but no name', () => {
+ it('counts as a real row, so a fresh blank is added below it', () => {
+ const rows = [row({ name: 'a', value: '1' }), row({})];
+ const out = applyRowPatch(rows, 1, { value: 'orphan' }, false);
+
+ expect(out).toHaveLength(3);
+ expect(out[1]).toMatchObject({ name: '', value: 'orphan' });
+ expect(out[2]).toMatchObject({ name: '', value: '' });
+ });
+
+ it('is handed back to the caller, so it is saved and counted like any other row', () => {
+ const out = committableRows([
+ row({ name: 'a', value: '1' }),
+ row({ name: '', value: 'orphan' }),
+ row({})
+ ]);
+
+ expect(out).toHaveLength(2);
+ expect(out[1]).toMatchObject({ name: '', value: 'orphan' });
+ });
+
+ it('gets a trailing blank of its own when the rows are laid out', () => {
+ const out = withTrailingBlank([row({ name: '', value: 'orphan' })], false);
+
+ expect(out).toHaveLength(2);
+ expect(out[1]).toMatchObject({ name: '', value: '' });
+ });
+
+ it('can be deleted, leaving a blank row to type into', () => {
+ const rows = [row({ name: '', value: 'orphan' }), row({})];
+ const out = removeRowAt(rows, 0, false);
+
+ expect(out).toHaveLength(1);
+ expect(out[0]).toMatchObject({ name: '', value: '' });
+ });
+
+ it('is not enough for the environments table, which waits for both fields', () => {
+ const out = applyRowPatch([row({})], 0, { value: 'orphan' }, false, undefined, true);
+
+ expect(out).toHaveLength(1);
+ });
+});
+
+describe('a row that is empty in both fields', () => {
+ it('is still treated as the trailing blank, so no second blank is added', () => {
+ const out = withTrailingBlank([row({ name: 'a', value: '1' }), row({})], false);
+ expect(out).toHaveLength(2);
+ });
+
+ it('is still dropped when the rows are handed back', () => {
+ const out = committableRows([row({ name: 'a', value: '1' }), row({ name: ' ', value: ' ' })]);
+ expect(names(out)).toEqual(['a']);
+ });
+});
diff --git a/packages/bruno-api-docs/src/hooks/useEditableRows.ts b/packages/bruno-api-docs/src/hooks/useEditableRows.ts
index b886ac4a..a7c12a88 100644
--- a/packages/bruno-api-docs/src/hooks/useEditableRows.ts
+++ b/packages/bruno-api-docs/src/hooks/useEditableRows.ts
@@ -13,14 +13,16 @@ const blankRow = (makeNewRow?: NewRow): KeyValueRow => ({
...makeNewRow?.()
});
-const isBlank = (row?: KeyValueRow): boolean => !row?.name || row.name.trim() === '';
-const isComplete = (row?: KeyValueRow): boolean => !isBlank(row) && (row?.value ?? '').trim() !== '';
+const hasText = (value: unknown): boolean => typeof value === 'string' && value.trim() !== '';
+
+export const isBlankRow = (row?: KeyValueRow): boolean => !hasText(row?.name) && !hasText(row?.value);
+const isComplete = (row?: KeyValueRow): boolean => hasText(row?.name) && hasText(row?.value);
// Whether the last row already serves as the trailing "type here" row, so we
// don't append another. In addWhenComplete mode the trailing row lasts until it
-// has both a name and a value; otherwise until it has a name.
+// has both a name and a value; otherwise until either field has something in it.
const hasTrailing = (last: KeyValueRow | undefined, addWhenComplete: boolean): boolean =>
- addWhenComplete ? !isComplete(last) : isBlank(last);
+ addWhenComplete ? !isComplete(last) : isBlankRow(last);
export const withTrailingBlank = (
data: KeyValueRow[],
@@ -33,7 +35,7 @@ export const withTrailingBlank = (
return [...rows, blankRow(makeNewRow)];
};
-export const committableRows = (rows: KeyValueRow[]): KeyValueRow[] => rows.filter((row) => !isBlank(row));
+export const committableRows = (rows: KeyValueRow[]): KeyValueRow[] => rows.filter((row) => !isBlankRow(row));
export const applyRowPatch = (
rows: KeyValueRow[],
@@ -44,9 +46,9 @@ export const applyRowPatch = (
addWhenComplete = false
): KeyValueRow[] => {
const next = [...rows];
- const wasBlank = isBlank(next[index]);
+ const wasBlank = isBlankRow(next[index]);
next[index] = { ...next[index], ...patch };
- const spawn = addWhenComplete ? isComplete(next[index]) : wasBlank && !isBlank(next[index]);
+ const spawn = addWhenComplete ? isComplete(next[index]) : wasBlank && !isBlankRow(next[index]);
if (!disableNewRow && index === rows.length - 1 && spawn) {
next.push(blankRow(makeNewRow));
}
@@ -60,7 +62,7 @@ export const removeRowAt = (
makeNewRow?: NewRow,
addWhenComplete = false
): KeyValueRow[] => {
- if (index === rows.length - 1 && isBlank(rows[index])) return rows;
+ if (index === rows.length - 1 && isBlankRow(rows[index])) return rows;
const next = rows.filter((_, i) => i !== index);
if (!disableNewRow && (next.length === 0 || !hasTrailing(next[next.length - 1], addWhenComplete))) {
next.push(blankRow(makeNewRow));