Skip to content

Commit

Permalink
(fix) Properly sanitize and initialize cloned fields (#429)
Browse files Browse the repository at this point in the history
* Properly sanitize and initialize cloned fields

* Fix tests
  • Loading branch information
samuelmale authored Nov 26, 2024
1 parent c175478 commit 5401a64
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 22 deletions.
4 changes: 3 additions & 1 deletion src/components/inputs/text/text.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ const TextField: React.FC<FormFieldInputProps> = ({ field, value, errors, warnin
<TextInput
id={field.id}
labelText={<FieldLabel field={field} />}
onChange={setFieldValue}
onChange={(event) => {
setFieldValue(event.target.value);
}}
onBlur={onBlur}
name={field.id}
value={value}
Expand Down
12 changes: 3 additions & 9 deletions src/components/inputs/text/text.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,11 @@ describe('Text field input', () => {
it('should record new obs', async () => {
await renderForm(textValues);
const inputField = screen.getByLabelText('Indicate your notes');

await user.type(inputField, 'Updated patient notes');
await user.click(inputField);
await user.paste('Updated patient notes');

await act(async () => {
expect(mockSetFieldValue).toHaveBeenCalledWith(
expect.objectContaining({
target: expect.objectContaining({
value: 'Updated patient notes',
}),
}),
);
expect(mockSetFieldValue).toHaveBeenCalledWith('Updated patient notes');
});
});

Expand Down
1 change: 0 additions & 1 deletion src/components/renderer/form/form-renderer.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { formStateReducer, initialState } from './state';
import { useEvaluateFormFieldExpressions } from '../../../hooks/useEvaluateFormFieldExpressions';
import { useFormFactory } from '../../../provider/form-factory-provider';
import { FormProvider, type FormContextProps } from '../../../provider/form-provider';
import { isTrue } from '../../../utils/boolean-utils';
import { type FormProcessorContextProps } from '../../../types';
import { useFormStateHelpers } from '../../../hooks/useFormStateHelpers';
import { pageObserver } from '../../sidebar/page-observer';
Expand Down
1 change: 1 addition & 0 deletions src/components/repeat/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export function cloneRepeatField(srcField: FormField, value: OpenmrsResource, id
childField.id = `${childField.id}_${idSuffix}`;
childField.meta.groupId = clonedField.id;
childField.meta.previousValue = null;
childField.fieldDependents = new Set();
clearSubmission(childField);

// cleanup expressions
Expand Down
20 changes: 12 additions & 8 deletions src/components/repeat/repeat.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@ const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {

const handleAdd = useCallback(
(counter: number) => {
const clonedFieldsBuffer: FormField[] = [];
function evaluateExpressions(field: FormField) {
if (field.hide?.hideWhenExpression) {
field.isHidden = evaluateExpression(
field.hide.hideWhenExpression,
{ value: field, type: 'field' },
formFields,
[...formFields, ...clonedFieldsBuffer],
getValues(),
{
mode: sessionMode,
Expand All @@ -64,7 +65,7 @@ const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
evaluateAsyncExpression(
field.questionOptions.calculate?.calculateExpression,
{ value: field, type: 'field' },
formFields,
[...formFields, ...clonedFieldsBuffer],
getValues(),
{
mode: sessionMode,
Expand All @@ -80,16 +81,19 @@ const Repeat: React.FC<FormFieldInputProps> = ({ field }) => {
}

const clonedField = cloneRepeatField(field, null, counter);
// run necessary expressions
clonedFieldsBuffer.push(clonedField);

// Handle nested questions
if (clonedField.type === 'obsGroup') {
clonedField.questions?.forEach((childField) => {
evaluateExpressions(childField);
addFormField(childField);
clonedFieldsBuffer.push(childField);
});
} else {
evaluateExpressions(clonedField);
}
addFormField(clonedField);

clonedFieldsBuffer.forEach((field) => {
evaluateExpressions(field);
addFormField(field);
});
setRows([...rows, clonedField]);
},
[formFields, field, rows, context],
Expand Down
21 changes: 18 additions & 3 deletions src/hooks/useFormStateHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,24 @@ export function useFormStateHelpers(dispatch: Dispatch<Action>, formFields: Form
const addFormField = useCallback((field: FormField) => {
dispatch({ type: 'ADD_FORM_FIELD', value: field });
}, []);
const updateFormField = useCallback((field: FormField) => {
dispatch({ type: 'UPDATE_FORM_FIELD', value: cloneDeep(field) });
}, []);
const updateFormField = useCallback(
(field: FormField) => {
if (field.meta.groupId) {
const group = formFields.find((f) => f.id === field.meta.groupId);
if (group) {
group.questions = group.questions.map((child) => {
if (child.id === field.id) {
return field;
}
return child;
});
updateFormField(group);
}
}
dispatch({ type: 'UPDATE_FORM_FIELD', value: cloneDeep(field) });
},
[formFields],
);

const getFormField = useCallback(
(fieldId: string) => {
Expand Down

0 comments on commit 5401a64

Please sign in to comment.