From 25dbd57d3706888b416844042bebf8f919e2c414 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Thu, 25 Jun 2026 12:51:30 -0400 Subject: [PATCH] fix(edit-content): parse single-option label|value fields correctly Single-option Checkbox/Radio/Select/Multiselect fields rendered the raw "label|value" string (e.g. "Yes|yes") instead of just the label. The parser used a global isPipeFormat flag that only treated input as pipe format when it contained a line break or started with "|", so a lone "Yes|yes" fell through to the comma branch and was used verbatim as both label and value. Switch getSingleSelectableFieldOptions to per-item pipe detection so each option is split independently. This fixes the single-option case, also corrects comma-separated pipe options, and preserves multi-line, the "|true" checkbox special case, plain comma, and data-type casting. Closes #36157 --- .../src/lib/utils/functions.util.spec.ts | 35 ++++++++++++++++--- .../src/lib/utils/functions.util.ts | 30 ++++++++-------- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/core-web/libs/edit-content/src/lib/utils/functions.util.spec.ts b/core-web/libs/edit-content/src/lib/utils/functions.util.spec.ts index 1f722bfc3407..b80a015cde5c 100644 --- a/core-web/libs/edit-content/src/lib/utils/functions.util.spec.ts +++ b/core-web/libs/edit-content/src/lib/utils/functions.util.spec.ts @@ -438,13 +438,13 @@ describe('Utils Functions', () => { ]); }); - it('should handle mixed formats (pipes take precedence)', () => { - // When pipes are present, comma splitting should not occur + it('should parse comma-separated pipe-format items individually', () => { + // Each comma-separated item has its own pipe applied for label/value expect( getSingleSelectableFieldOptions('label1|value1,label2|value2', 'text') ).toEqual([ - { label: 'label1|value1', value: 'label1|value1' }, - { label: 'label2|value2', value: 'label2|value2' } + { label: 'label1', value: 'value1' }, + { label: 'label2', value: 'value2' } ]); }); @@ -507,6 +507,33 @@ describe('Utils Functions', () => { ]); }); }); + + describe('Single-option pipe format (issue #36157)', () => { + it('should parse a single pipe-format option using label and value (AC8)', () => { + expect(getSingleSelectableFieldOptions('Yes|yes', 'text')).toEqual([ + { label: 'Yes', value: 'yes' } + ]); + }); + + it('should use the whole string as label and value for a single option without pipe (AC9)', () => { + expect(getSingleSelectableFieldOptions('Yes', 'text')).toEqual([ + { label: 'Yes', value: 'Yes' } + ]); + }); + + // AC10: Checkbox, Radio, Select and Multiselect all delegate to this util + // with a text dataType, so a single `Yes|yes` option must resolve identically. + it.each(['Checkbox', 'Radio', 'Select', 'Multiselect'])( + 'should parse single-option pipe format for %s field', + (fieldType) => { + expect(getSingleSelectableFieldOptions('Yes|yes', 'text')).toEqual([ + { label: 'Yes', value: 'yes' } + ]); + // fieldType is documented in the case name to clarify intent + expect(fieldType).toBeDefined(); + } + ); + }); }); describe('getFinalCastedValue', () => { diff --git a/core-web/libs/edit-content/src/lib/utils/functions.util.ts b/core-web/libs/edit-content/src/lib/utils/functions.util.ts index 3d7874bdbe17..8ebba73d00f5 100644 --- a/core-web/libs/edit-content/src/lib/utils/functions.util.ts +++ b/core-web/libs/edit-content/src/lib/utils/functions.util.ts @@ -88,6 +88,11 @@ export const castSingleSelectableValue = ( * Note: If the input contains line breaks, it will be treated as a single option, * preserving the line breaks as part of the option text. * + * Pipe detection is applied per option, so a single option (`label|value`), + * multi-line options (`label|value` per line) and comma-separated options + * (`label|value,label|value`) are all parsed correctly. Options without a pipe + * use the whole string as both label and value. + * * @param options - The string containing the options to parse * @param dataType - The data type of the field * @returns Array of parsed options with label and value @@ -99,23 +104,18 @@ export const getSingleSelectableFieldOptions = ( if (!options?.trim()) return []; const LINE_BREAKS_REGEX = /\r\n|\n|\r/; - const PIPE_REGEX = /\|/; const hasLineBreaks = LINE_BREAKS_REGEX.test(options); - const hasPipes = PIPE_REGEX.test(options); let items: string[] = []; - let isPipeFormat = false; - if (hasPipes && hasLineBreaks) { - // Multi-line pipe format (standard dotCMS format) + if (hasLineBreaks) { + // Multi-line format (standard dotCMS format) items = options.split(LINE_BREAKS_REGEX).filter((line) => line.trim()); - isPipeFormat = true; - } else if (hasPipes && !hasLineBreaks && options.trim().startsWith('|')) { + } else if (options.trim().startsWith('|')) { // Special case: "|true" (checkbox without label) items = [options.trim()]; - isPipeFormat = true; } else { - // Simple comma format or single-line with pipes treated as comma format + // Comma-separated format items = options .split(',') .map((v) => v.trim()) @@ -132,16 +132,16 @@ export const getSingleSelectableFieldOptions = ( let label: string; let value: string; - if (isPipeFormat) { + if (item.includes('|')) { const parts = item.split('|'); - // Si hay pipe, el label es la primera parte y el value es la segunda - // Si no hay segunda parte, el value es igual al label + // If a pipe is present, label is the first part and value the second; + // if there's no second part, value equals label label = (parts[0] || '').trim(); value = parts[1]?.trim() || label; } else { - // Si no hay pipe, tanto label como value son el mismo valor - label = item; - value = item; + // No pipe: label and value are the same + label = item.trim(); + value = label; } if (!value) return null;