Skip to content

Commit

Permalink
Fix hide behavior of pages and sections (#420)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelmale authored Oct 28, 2024
1 parent ce3dbdb commit 7f9a081
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 6 deletions.
93 changes: 93 additions & 0 deletions __mocks__/forms/rfe-forms/hide-pages-and-sections-form.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"encounterType": "e22e39fd-7db2-45e7-80f1-60fa0d5a4378",
"name": "Hide Pages and Sections",
"processor": "EncounterFormProcessor",
"referencedForms": [],
"uuid": "7c77485c-7a57-4646-ac21-11d92555a420",
"version": "1.0",
"pages": [
{
"label": "Page 1",
"sections": [
{
"label": "Section 1A",
"isExpanded": "true",
"questions": [
{
"id": "hideSection1B",
"label": "Hide Section 1B",
"type": "obs",
"questionOptions": {
"rendering": "text",
"concept": "7aef2620-76e0-4d88-b9cb-c47ba4f67bce"
}
}
]
},
{
"label": "Section 1B",
"isExpanded": "true",
"hide": {
"hideWhenExpression": "isEmpty(hideSection1B)"
},
"questions": [
{
"id": "hidePage2",
"label": "Hide Page 2",
"type": "obs",
"questionOptions": {
"rendering": "radio",
"concept": "1255AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"answers": [
{
"concept": "1256AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"label": "Choice 1",
"conceptMappings": []
},
{
"concept": "1258AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"label": "Choice 2",
"conceptMappings": []
},
{
"concept": "1259AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"label": "Choice 3",
"conceptMappings": []
}
]
}
}
]
}
]
},
{
"label": "Page 2",
"hide": {
"hideWhenExpression": "hidePage2 === '1258AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'"
},
"sections": [
{
"label": "Section 2A",
"isExpanded": "true",
"questions": [
{
"label": "Date",
"type": "obs",
"required": false,
"id": "date",
"datePickerFormat": "calendar",
"questionOptions": {
"rendering": "date",
"concept": "159599AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"conceptMappings": []
},
"validators": []
}
]
}
]
}
],
"description": "Hide Pages and Sections"
}
6 changes: 3 additions & 3 deletions src/components/renderer/field/fieldLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,9 @@ function evaluateFieldDependents(field: FormField, values: any, context: FormCon
}
shouldUpdateForm = true;
});
}

if (shouldUpdateForm) {
setForm({ ...formJson });
}
if (shouldUpdateForm) {
setForm(formJson);
}
}
44 changes: 44 additions & 0 deletions src/form-engine.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import nextVisitForm from '__mocks__/forms/rfe-forms/next-visit-test-form.json';
import viralLoadStatusForm from '__mocks__/forms/rfe-forms/viral-load-status-form.json';
import readOnlyValidationForm from '__mocks__/forms/rfe-forms/read-only-validation-form.json';
import jsExpressionValidationForm from '__mocks__/forms/rfe-forms/js-expression-validation-form.json';
import hidePagesAndSectionsForm from '__mocks__/forms/rfe-forms/hide-pages-and-sections-form.json';

import FormEngine from './form-engine.component';
import { type SessionMode } from './types';
Expand Down Expand Up @@ -635,6 +636,49 @@ describe('Form engine component', () => {
});
});

describe('Hide pages and sections', () => {
it('should hide/show section based on field value', async () => {
await act(async () => renderForm(null, hidePagesAndSectionsForm));

// assert section "Section 1B" is hidden at initial render
try {
await screen.findByText('Section 1B');
fail('The section named "Section 1B" should be hidden');
} catch (err) {
expect(err.message.includes('Unable to find an element with the text: Section 1B')).toBeTruthy();
}

// user interactions to make section visible
const hideSection1bField = await findTextOrDateInput(screen, 'Hide Section 1B');
await user.type(hideSection1bField, 'Some value');

const section1b = await screen.findByText('Section 1B');
expect(section1b).toBeInTheDocument();
});

it('should hide/show page based on field value', async () => {
await act(async () => renderForm(null, hidePagesAndSectionsForm));

// assert page "Page 2" is visible at initial render
const page2 = await screen.findByText('Page 2');
expect(page2).toBeInTheDocument();

// user interactions to hide page
const hideSection1bField = await findTextOrDateInput(screen, 'Hide Section 1B');
await user.type(hideSection1bField, 'Some value');
const choice2RadioOption = screen.getByRole('radio', { name: /Choice 2/i });
await user.click(choice2RadioOption);

// assert page is hidden
try {
await screen.findByText('Page 2');
fail('The page named "Page 2" should be hidden');
} catch (err) {
expect(err.message.includes('Unable to find an element with the text: Page 2')).toBeTruthy();
}
});
});

describe('Calculated values', () => {
it('should evaluate BMI', async () => {
await act(async () => renderForm(null, bmiForm));
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/useEvaluateFormFieldExpressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { evalConditionalRequired, evaluateConditionalAnswered, evaluateHide } fr
import { isTrue } from '../utils/boolean-utils';
import { isEmpty } from '../validators/form-validator';
import { type QuestionAnswerOption } from '../types/schema';
import { updateFormSectionReferences } from '../utils/common-utils';

export const useEvaluateFormFieldExpressions = (
formValues: Record<string, any>,
Expand Down Expand Up @@ -125,7 +126,7 @@ export const useEvaluateFormFieldExpressions = (
}
});
});
setEvaluatedFormJson(factoryContext.formJson);
setEvaluatedFormJson(updateFormSectionReferences(factoryContext.formJson));
}, [factoryContext.formJson, formFields]);

return { evaluatedFormJson, evaluatedFields };
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/useFormStateHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type Dispatch, useCallback } from 'react';
import { type FormField, type FormSchema } from '../types';
import { type Action } from '../components/renderer/form/state';
import cloneDeep from 'lodash/cloneDeep';
import { updateFormSectionReferences } from '../utils/common-utils';

export function useFormStateHelpers(dispatch: Dispatch<Action>, formFields: FormField[]) {
const addFormField = useCallback((field: FormField) => {
Expand Down Expand Up @@ -35,7 +36,7 @@ export function useFormStateHelpers(dispatch: Dispatch<Action>, formFields: Form
}, []);

const setForm = useCallback((formJson: FormSchema) => {
dispatch({ type: 'SET_FORM_JSON', value: formJson });
dispatch({ type: 'SET_FORM_JSON', value: updateFormSectionReferences(formJson) });
}, []);

return {
Expand Down
14 changes: 13 additions & 1 deletion src/utils/common-utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dayjs from 'dayjs';
import { type FormField, type OpenmrsObs, type RenderType } from '../types';
import { type FormSchema, type FormField, type OpenmrsObs, type RenderType } from '../types';
import { isEmpty } from '../validators/form-validator';
import { formatDate, type FormatDateOptions } from '@openmrs/esm-framework';

Expand Down Expand Up @@ -77,3 +77,15 @@ export function formatDateAsDisplayString(field: FormField, date: Date) {
}
return formatDate(date, options);
}

/**
* Creates a new copy of `formJson` with updated references at the page and section levels.
* This ensures React re-renders properly by providing new references for nested arrays.
*/
export function updateFormSectionReferences(formJson: FormSchema) {
formJson.pages = formJson.pages.map((page) => {
page.sections = Array.from(page.sections);
return page;
});
return { ...formJson };
}

0 comments on commit 7f9a081

Please sign in to comment.