Skip to content

Commit faeb6d8

Browse files
authored
GitHub Issue 955: limit text choice option length to 200 characters (#1960)
1 parent cc4d520 commit faeb6d8

6 files changed

Lines changed: 52 additions & 10 deletions

File tree

packages/components/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@labkey/components",
3-
"version": "7.24.1",
3+
"version": "7.24.2",
44
"description": "Components, models, actions, and utility functions for LabKey applications and pages",
55
"sideEffects": false,
66
"files": [

packages/components/releaseNotes/components.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# @labkey/components
22
Components, models, actions, and utility functions for LabKey applications and pages
33

4+
### version 7.24.2
5+
*Released*: 25 March 2026
6+
- GitHub Issue 955: limit text choice option length to 200 characters
7+
48
### version 7.24.1
59
*Released*: 25 March 2026
610
- Factor `EditingForm` out of `EditableDetailPanel` and load model with update columns

packages/components/src/internal/components/domainproperties/TextChoiceAddValuesModal.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,33 @@ describe('TextChoiceAddValuesModal', () => {
7979
validateCounterText('2 values', '3 new values');
8080
});
8181

82+
test('value exceeding max length disables apply and shows error', async () => {
83+
render(<TextChoiceAddValuesModal {...DEFAULT_PROPS} />);
84+
const longValue = 'a'.repeat(201);
85+
await userEvent.type(document.querySelector('textarea'), longValue);
86+
expect(document.querySelector('.btn-success').hasAttribute('disabled')).toBeTruthy();
87+
let errorEls = document.querySelectorAll('.domain-text-choices-error');
88+
expect(errorEls).toHaveLength(1);
89+
expect(errorEls[0].textContent).toContain('Value exceeds maximum of 200 characters');
90+
91+
// clear the long value, error should be gone
92+
await userEvent.clear(document.querySelector('textarea'));
93+
expect(document.querySelectorAll('.domain-text-choices-error')).toHaveLength(0);
94+
95+
// enter a valid short value, no error
96+
await userEvent.type(document.querySelector('textarea'), 'short value');
97+
expect(document.querySelector('.btn-success').hasAttribute('disabled')).toBeFalsy();
98+
expect(document.querySelectorAll('.domain-text-choices-error')).toHaveLength(0);
99+
100+
// multiline input where second line exceeds max length
101+
await userEvent.clear(document.querySelector('textarea'));
102+
await userEvent.type(document.querySelector('textarea'), 'valid\n' + 'b'.repeat(201));
103+
expect(document.querySelector('.btn-success').hasAttribute('disabled')).toBeTruthy();
104+
errorEls = document.querySelectorAll('.domain-text-choices-error');
105+
expect(errorEls).toHaveLength(1);
106+
expect(errorEls[0].textContent).toContain('Value exceeds maximum of 200 characters');
107+
});
108+
82109
test('initial already equal to max', async () => {
83110
render(<TextChoiceAddValuesModal {...DEFAULT_PROPS} initialValueCount={2} maxValueCount={2} />);
84111
expect(document.querySelector('.btn-success').hasAttribute('disabled')).toBeTruthy();

packages/components/src/internal/components/domainproperties/TextChoiceAddValuesModal.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Utils } from '@labkey/api';
44

55
import { Modal } from '../../Modal';
66

7-
import { MAX_VALID_TEXT_CHOICES } from './constants';
7+
import { MAX_TEXT_CHOICE_VALUE_LENGTH, MAX_VALID_TEXT_CHOICES } from './constants';
88
import { getValidValuesFromArray } from './models';
99

1010
interface Props {
@@ -22,27 +22,31 @@ export const TextChoiceAddValuesModal: FC<Props> = memo(props => {
2222
return valueStr?.trim().length > 0 ? getValidValuesFromArray(valueStr.split('\n').map(v => v.trim())) : [];
2323
}, [valueStr]);
2424
const maxValuesToAdd = useMemo(() => maxValueCount - initialValueCount, [initialValueCount]);
25+
const tooLongValue = useMemo(() => parsedValues.find(v => v.length > MAX_TEXT_CHOICE_VALUE_LENGTH), [parsedValues]);
2526
const hasFieldName = useMemo(() => fieldName?.length > 0, [fieldName]);
2627
const onChange = useCallback(evt => {
2728
setValueStr(evt.target.value);
2829
}, []);
2930
const onConfirm = useCallback(() => {
30-
if (parsedValues.length <= maxValuesToAdd) {
31+
if (parsedValues.length <= maxValuesToAdd && !tooLongValue) {
3132
onApply(parsedValues);
3233
}
33-
}, [parsedValues, maxValuesToAdd, onApply]);
34-
const canConfirm = parsedValues.length > 0 && parsedValues.length <= maxValuesToAdd;
34+
}, [parsedValues, maxValuesToAdd, tooLongValue, onApply]);
35+
const canConfirm = parsedValues.length > 0 && parsedValues.length <= maxValuesToAdd && !tooLongValue;
3536
const title = `Add Text Choice Values${hasFieldName ? ' for ' + fieldName : ''}`;
3637
const valueNoun = Utils.pluralize(maxValuesToAdd, 'value', 'values');
3738
return (
3839
<Modal canConfirm={canConfirm} confirmText="Apply" onCancel={onCancel} onConfirm={onConfirm} title={title}>
3940
<p>Enter each value on a new line. {valueNoun} can be added.</p>
4041
<textarea
41-
rows={8}
42-
cols={50}
42+
aria-label="Text choice values"
43+
aria-describedby={tooLongValue ? 'text-choice-length-error' : undefined}
44+
aria-invalid={!!tooLongValue}
4345
className="form-control textarea-fullwidth"
44-
placeholder="Enter new values..."
46+
cols={50}
4547
onChange={onChange}
48+
placeholder="Enter new values..."
49+
rows={8}
4650
value={valueStr}
4751
/>
4852
<div
@@ -52,6 +56,12 @@ export const TextChoiceAddValuesModal: FC<Props> = memo(props => {
5256
>
5357
{parsedValues.length === 1 ? '1 new value provided.' : `${parsedValues.length} new values provided.`}
5458
</div>
59+
{tooLongValue && (
60+
<div className="domain-text-choices-error" id="text-choice-length-error" role="alert">
61+
Value exceeds maximum of {MAX_TEXT_CHOICE_VALUE_LENGTH} characters: &quot;
62+
{tooLongValue.substring(0, 50)}...&quot;
63+
</div>
64+
)}
5565
</Modal>
5666
);
5767
});

packages/components/src/internal/components/domainproperties/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ export const DERIVATION_DATA_SCOPES = {
233233
};
234234

235235
export const MAX_VALID_TEXT_CHOICES = 500;
236+
export const MAX_TEXT_CHOICE_VALUE_LENGTH = 200; // GitHub Issue 955: limit option length to 200
236237

237238
export const LOOKUP_VALIDATOR_VALUES = { type: 'Lookup', name: 'Lookup Validator' };
238239

0 commit comments

Comments
 (0)