diff --git a/packages/components/package-lock.json b/packages/components/package-lock.json index 5fc69e55f4..1f1f183540 100644 --- a/packages/components/package-lock.json +++ b/packages/components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/components", - "version": "7.29.3", + "version": "7.29.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/components", - "version": "7.29.3", + "version": "7.29.4", "license": "SEE LICENSE IN LICENSE.txt", "dependencies": { "@hello-pangea/dnd": "18.0.1", diff --git a/packages/components/package.json b/packages/components/package.json index 83c26fdd23..40d54e6d32 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/components", - "version": "7.29.3", + "version": "7.29.4", "description": "Components, models, actions, and utility functions for LabKey applications and pages", "sideEffects": false, "files": [ diff --git a/packages/components/releaseNotes/components.md b/packages/components/releaseNotes/components.md index 9a94d645ea..5e7639e679 100644 --- a/packages/components/releaseNotes/components.md +++ b/packages/components/releaseNotes/components.md @@ -1,6 +1,10 @@ # @labkey/components Components, models, actions, and utility functions for LabKey applications and pages +### version 7.29.4 +*Released*: 9 April 2026 +- GitHub Issue 954: Add error for duplicate values for parent inputs + ### version 7.29.3 *Released*: 8 April 2026 - GitHub Issue 928: Spaces not shown between text choices in identifying fields in editable grid diff --git a/packages/components/src/internal/components/editable/actions.test.ts b/packages/components/src/internal/components/editable/actions.test.ts index ff3771c6f6..a9198a0bbc 100644 --- a/packages/components/src/internal/components/editable/actions.test.ts +++ b/packages/components/src/internal/components/editable/actions.test.ts @@ -836,6 +836,48 @@ describe('parsePastedLookup', () => { ]), }); }); + + test('duplicate values', () => { + // single duplicate + expect(parsePastedLookup(multiValueLookup, stringLookupValues, 'A,A')).toStrictEqual({ + message: { message: 'Duplicate values not allowed: "A".' }, + valueDescriptors: List([ + { display: 'A', raw: 'a' }, + { display: 'A', raw: 'a' }, + ]), + }); + + // multiple duplicates + expect(parsePastedLookup(multiValueLookup, stringLookupValues, 'A,b,A,b')).toStrictEqual({ + message: { message: 'Duplicate values not allowed: "A", "b".' }, + valueDescriptors: List([ + { display: 'A', raw: 'a' }, + { display: 'b', raw: 'B' }, + { display: 'A', raw: 'a' }, + { display: 'b', raw: 'B' }, + ]), + }); + + // duplicate with other valid values + expect(parsePastedLookup(multiValueLookup, stringLookupValues, 'A,C,A')).toStrictEqual({ + message: { message: 'Duplicate values not allowed: "A".' }, + valueDescriptors: List([ + { display: 'A', raw: 'a' }, + { display: 'C', raw: 'C' }, + { display: 'A', raw: 'a' }, + ]), + }); + + // unmatched takes precedence over duplicates + expect(parsePastedLookup(multiValueLookup, stringLookupValues, 'A,A,notfound')).toStrictEqual({ + message: { message: 'Could not find "notfound"' }, + valueDescriptors: List([ + { display: 'A', raw: 'a' }, + { display: 'A', raw: 'a' }, + { display: 'notfound', raw: 'notfound' }, + ]), + }); + }); }); describe('insertPastedData', () => { diff --git a/packages/components/src/internal/components/editable/actions.ts b/packages/components/src/internal/components/editable/actions.ts index 45402a45a6..910046041c 100644 --- a/packages/components/src/internal/components/editable/actions.ts +++ b/packages/components/src/internal/components/editable/actions.ts @@ -1123,6 +1123,7 @@ export function parsePastedLookup( let message: CellMessage; let values: ValueDescriptor[]; const unmatched: string[] = []; + const dupValues = new Set(); // Parse pasted strings to split properly around quoted values. // Remove the quotes for storing the actual values in the grid. @@ -1134,10 +1135,15 @@ export function parsePastedLookup( unmatched.push(vt); values = [{ display: vt, raw: vt }]; } else { + const foundValues = new Set(); values = parsedValues.flatMap(v => { const vt = v.trim(); if (!vt) return []; + if (foundValues.has(vt)) { + dupValues.add(vt); + } + foundValues.add(vt); const vl = vt.toLowerCase(); const vd = descriptors.find(d => d.display && d.display.toString().toLowerCase() === vl); if (vd) return [vd]; @@ -1153,6 +1159,12 @@ export function parsePastedLookup( .map(u => '"' + u + '"') .join(', '); message = { message: lookupValidationErrorMessage(valueStr, true) }; + } else if (dupValues.size > 0) { + const valueStr = Array.from(dupValues) + .slice(0, 4) + .map(u => '"' + u + '"') + .join(', '); + message = { message: `Duplicate values not allowed: ${valueStr}.` }; } return { message, valueDescriptors: List(values) };