From f96f91cf8939a7bec22c0fbd367ce01556856eb4 Mon Sep 17 00:00:00 2001 From: wodpachua Date: Mon, 9 Sep 2024 18:04:28 +0300 Subject: [PATCH 1/6] (test) add test to repeat component --- src/components/repeat/repeat.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/repeat/repeat.test.ts b/src/components/repeat/repeat.test.ts index d9bbe237a..1fe2c0268 100644 --- a/src/components/repeat/repeat.test.ts +++ b/src/components/repeat/repeat.test.ts @@ -26,4 +26,12 @@ describe('RepeatingFieldComponent - handleExpressionFieldIdUpdate', () => { "myValue > today() || myValue <= '1/1/1890' || myValue > useFieldValue('visit_date') || myValue < useFieldValue('visit_date')", ); }); + + it('Should update field ID in expressions when adding repeated fields', () => { + const expression = "infantStatus !== 'someValue'"; + const fieldIds = ['birthDate', 'infantStatus', 'deathDate']; + const updatedExpression = updateFieldIdInExpression(expression, 2, fieldIds); + + expect(updatedExpression).toEqual("infantStatus_2 !== 'someValue'"); + }); }); From 947c20c7691329271820066afa9714f14d828f13 Mon Sep 17 00:00:00 2001 From: wodpachua Date: Tue, 15 Oct 2024 01:48:47 +0300 Subject: [PATCH 2/6] test: Add tests to Repeat Component --- src/components/repeat/repeat.test.ts | 69 +++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/src/components/repeat/repeat.test.ts b/src/components/repeat/repeat.test.ts index 1fe2c0268..279ba9d6d 100644 --- a/src/components/repeat/repeat.test.ts +++ b/src/components/repeat/repeat.test.ts @@ -1,4 +1,14 @@ import { updateFieldIdInExpression } from './helpers'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import Repeat from './repeat.component'; +import { useFormProviderContext } from '../../provider/form-provider'; + +jest.mock('../../provider/form-provider', () => ({ + useFormProviderContext: jest.fn(), +})); +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ t: (key) => key }), +})); describe('RepeatingFieldComponent - handleExpressionFieldIdUpdate', () => { it('Should handle update of expression with ids in repeat group', () => { @@ -26,12 +36,59 @@ describe('RepeatingFieldComponent - handleExpressionFieldIdUpdate', () => { "myValue > today() || myValue <= '1/1/1890' || myValue > useFieldValue('visit_date') || myValue < useFieldValue('visit_date')", ); }); +}); + +describe('Repeat Component Tests', () => { + const mockField = { + id: 'testField', + label: 'Test Field', + questionOptions: { concept: '57def3cb-e6de-41d5-9a55-097878f2c5bd', rendering: 'obsGroup' }, + questions: [{ id: 'testQuestion1' }, { id: 'testQuestion2' }], + }; - it('Should update field ID in expressions when adding repeated fields', () => { - const expression = "infantStatus !== 'someValue'"; - const fieldIds = ['birthDate', 'infantStatus', 'deathDate']; - const updatedExpression = updateFieldIdInExpression(expression, 2, fieldIds); + const mockContext = { + patient: {}, + sessionMode: 'edit', + formFields: [mockField], + methods: { getValues: jest.fn(), setValue: jest.fn() }, + addFormField: jest.fn(), + formFieldAdapters: { obsGroup: { transformFieldValue: jest.fn() } }, + }; - expect(updatedExpression).toEqual("infantStatus_2 !== 'someValue'"); + beforeEach(() => { + (useFormProviderContext as jest.Mock).mockReturnValue(mockContext); }); -}); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('Should add a repeatable field instance on clicking "Add"', () => { + + render(); + + const addButton = screen.getByText(/add/i); + fireEvent.click(addButton); + + expect(mockContext.addFormField).toHaveBeenCalledTimes(1); + expect(mockContext.addFormField).toHaveBeenCalledWith(expect.objectContaining({ id: 'testField_1' })); + }); + + it('Should remove the instance when delete button is clicked ', async () => { + mockContext.formFields = [ + { ...mockField, id: 'testField' }, + { ...mockField, id: 'testField_1' }, + ]; + + render(); + + const deleteButton = screen.getAllByText(/delete/i)[0]; + fireEvent.click(deleteButton); + + await waitFor(() => { + expect(mockContext.formFields).not.toEqual( + expect.arrayContaining([{ id: 'testField_1' }]) + ); + }); + }); +}); \ No newline at end of file From 5f68d39d9d10e49b5c24e33623995fb94ca12d82 Mon Sep 17 00:00:00 2001 From: wodpachua Date: Tue, 15 Oct 2024 12:09:00 +0300 Subject: [PATCH 3/6] test: add more test coverage --- src/components/repeat/repeat.test.ts | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/components/repeat/repeat.test.ts b/src/components/repeat/repeat.test.ts index 279ba9d6d..8e2e60899 100644 --- a/src/components/repeat/repeat.test.ts +++ b/src/components/repeat/repeat.test.ts @@ -91,4 +91,44 @@ describe('Repeat Component Tests', () => { ); }); }); + + it('Should submit origin and cloned instances successfully', async () => { + const { methods: { setValue } } = mockContext; + setValue.mockImplementation((id, value) => { + mockContext.formFields.push({ id, value }); + }); + + render(); + + fireEvent.click(screen.getByText(/add/i)); + fireEvent.click(screen.getByText(/add/i)); + + await waitFor(() => { + expect(mockContext.formFields.length).toBe(3); + }); + + expect(setValue).toHaveBeenCalledTimes(2); + expect(mockContext.formFields).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: 'testField', value: undefined }), + expect.objectContaining({ id: 'testField_1', value: undefined }), + expect.objectContaining({ id: 'testField_2', value: undefined }), + ]) + ); + }); + + it('Should Initialize repeat field in edit mode with instances', async () => { + const prePopulatedFields = [ + { ...mockField, id: 'testField' }, + { ...mockField, id: 'testField_1' }, + ]; + mockContext.formFields = prePopulatedFields; + + render(); + + await waitFor(() => { + expect(screen.getAllByText('Test Field').length).toBe(2); + }); + }); + }); \ No newline at end of file From 2e84aa0b1939da1f21de09c10b9d52902c1e4d10 Mon Sep 17 00:00:00 2001 From: wodpachua Date: Fri, 8 Nov 2024 03:01:59 +0300 Subject: [PATCH 4/6] reset test file --- src/components/repeat/repeat.test.ts | 134 --------------------------- 1 file changed, 134 deletions(-) delete mode 100644 src/components/repeat/repeat.test.ts diff --git a/src/components/repeat/repeat.test.ts b/src/components/repeat/repeat.test.ts deleted file mode 100644 index 8e2e60899..000000000 --- a/src/components/repeat/repeat.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { updateFieldIdInExpression } from './helpers'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; -import Repeat from './repeat.component'; -import { useFormProviderContext } from '../../provider/form-provider'; - -jest.mock('../../provider/form-provider', () => ({ - useFormProviderContext: jest.fn(), -})); -jest.mock('react-i18next', () => ({ - useTranslation: () => ({ t: (key) => key }), -})); - -describe('RepeatingFieldComponent - handleExpressionFieldIdUpdate', () => { - it('Should handle update of expression with ids in repeat group', () => { - const expression = - "infantStatus !== '151849AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' && infantStatus !== '154223AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'"; - const fieldIds = ['birthDate', 'infantStatus', 'deathDate']; - const index = 2; - - const updatedExpression = updateFieldIdInExpression(expression, index, fieldIds); - - expect(updatedExpression).toEqual( - "infantStatus_2 !== '151849AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' && infantStatus_2 !== '154223AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'", - ); - }); - - it('Should handle update of expression with ids not in repeat group', () => { - const expression = - "myValue > today() || myValue <= '1/1/1890' || myValue > useFieldValue('visit_date') || myValue < useFieldValue('visit_date')"; - const fieldIds = ['birthDate', 'infantStatus', 'deathDate']; - const index = 1; - - const updatedExpression = updateFieldIdInExpression(expression, index, fieldIds); - - expect(updatedExpression).toEqual( - "myValue > today() || myValue <= '1/1/1890' || myValue > useFieldValue('visit_date') || myValue < useFieldValue('visit_date')", - ); - }); -}); - -describe('Repeat Component Tests', () => { - const mockField = { - id: 'testField', - label: 'Test Field', - questionOptions: { concept: '57def3cb-e6de-41d5-9a55-097878f2c5bd', rendering: 'obsGroup' }, - questions: [{ id: 'testQuestion1' }, { id: 'testQuestion2' }], - }; - - const mockContext = { - patient: {}, - sessionMode: 'edit', - formFields: [mockField], - methods: { getValues: jest.fn(), setValue: jest.fn() }, - addFormField: jest.fn(), - formFieldAdapters: { obsGroup: { transformFieldValue: jest.fn() } }, - }; - - beforeEach(() => { - (useFormProviderContext as jest.Mock).mockReturnValue(mockContext); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('Should add a repeatable field instance on clicking "Add"', () => { - - render(); - - const addButton = screen.getByText(/add/i); - fireEvent.click(addButton); - - expect(mockContext.addFormField).toHaveBeenCalledTimes(1); - expect(mockContext.addFormField).toHaveBeenCalledWith(expect.objectContaining({ id: 'testField_1' })); - }); - - it('Should remove the instance when delete button is clicked ', async () => { - mockContext.formFields = [ - { ...mockField, id: 'testField' }, - { ...mockField, id: 'testField_1' }, - ]; - - render(); - - const deleteButton = screen.getAllByText(/delete/i)[0]; - fireEvent.click(deleteButton); - - await waitFor(() => { - expect(mockContext.formFields).not.toEqual( - expect.arrayContaining([{ id: 'testField_1' }]) - ); - }); - }); - - it('Should submit origin and cloned instances successfully', async () => { - const { methods: { setValue } } = mockContext; - setValue.mockImplementation((id, value) => { - mockContext.formFields.push({ id, value }); - }); - - render(); - - fireEvent.click(screen.getByText(/add/i)); - fireEvent.click(screen.getByText(/add/i)); - - await waitFor(() => { - expect(mockContext.formFields.length).toBe(3); - }); - - expect(setValue).toHaveBeenCalledTimes(2); - expect(mockContext.formFields).toEqual( - expect.arrayContaining([ - expect.objectContaining({ id: 'testField', value: undefined }), - expect.objectContaining({ id: 'testField_1', value: undefined }), - expect.objectContaining({ id: 'testField_2', value: undefined }), - ]) - ); - }); - - it('Should Initialize repeat field in edit mode with instances', async () => { - const prePopulatedFields = [ - { ...mockField, id: 'testField' }, - { ...mockField, id: 'testField_1' }, - ]; - mockContext.formFields = prePopulatedFields; - - render(); - - await waitFor(() => { - expect(screen.getAllByText('Test Field').length).toBe(2); - }); - }); - -}); \ No newline at end of file From df1baf276055726124a4d99b72241f36c22461c8 Mon Sep 17 00:00:00 2001 From: wodpachua Date: Fri, 8 Nov 2024 03:06:08 +0300 Subject: [PATCH 5/6] rename test file --- src/components/repeat/repeat.test.tsx | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/components/repeat/repeat.test.tsx diff --git a/src/components/repeat/repeat.test.tsx b/src/components/repeat/repeat.test.tsx new file mode 100644 index 000000000..ce5a43eb7 --- /dev/null +++ b/src/components/repeat/repeat.test.tsx @@ -0,0 +1,29 @@ +import { updateFieldIdInExpression } from './helpers'; + +describe('RepeatingFieldComponent - handleExpressionFieldIdUpdate', () => { + it('Should handle update of expression with ids in repeat group', () => { + const expression = + "infantStatus !== '151849AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' && infantStatus !== '154223AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'"; + const fieldIds = ['birthDate', 'infantStatus', 'deathDate']; + const index = 2; + + const updatedExpression = updateFieldIdInExpression(expression, index, fieldIds); + + expect(updatedExpression).toEqual( + "infantStatus_2 !== '151849AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' && infantStatus_2 !== '154223AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'", + ); + }); + + it('Should handle update of expression with ids not in repeat group', () => { + const expression = + "myValue > today() || myValue <= '1/1/1890' || myValue > useFieldValue('visit_date') || myValue < useFieldValue('visit_date')"; + const fieldIds = ['birthDate', 'infantStatus', 'deathDate']; + const index = 1; + + const updatedExpression = updateFieldIdInExpression(expression, index, fieldIds); + + expect(updatedExpression).toEqual( + "myValue > today() || myValue <= '1/1/1890' || myValue > useFieldValue('visit_date') || myValue < useFieldValue('visit_date')", + ); + }); +}); \ No newline at end of file From c2c557fa2b33ed439c8800811f86635b4dc4c779 Mon Sep 17 00:00:00 2001 From: wodpachua Date: Fri, 15 Nov 2024 18:14:55 +0300 Subject: [PATCH 6/6] refactor tests --- .../repeating-component-test-form.json | 89 +++++++++++++ src/components/repeat/repeat.test.tsx | 123 ++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 __mocks__/forms/rfe-forms/repeating-component-test-form.json diff --git a/__mocks__/forms/rfe-forms/repeating-component-test-form.json b/__mocks__/forms/rfe-forms/repeating-component-test-form.json new file mode 100644 index 000000000..5a9b9a9f6 --- /dev/null +++ b/__mocks__/forms/rfe-forms/repeating-component-test-form.json @@ -0,0 +1,89 @@ +{ + "name": "Repeating Component Test Form", + "pages": [ + { + "label": "Emergency Contact", + "sections": [ + { + "label": "Contacts", + "isExpanded": "true", + "questions": [ + { + "label": "Contact", + "type": "obsGroup", + "required": true, + "id": "patientContact", + "questionOptions": { + "rendering": "repeating" + }, + "questions": [ + { + "label": "Contact relationship", + "type": "obs", + "required": true, + "id": "patientContactRelationship", + "questionOptions": { + "rendering": "radio", + "concept": "164352AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "conceptMappings": [ + { + "relationship": "SAME-AS", + "type": "CIEL", + "value": "164352" + } + ], + "answers": [ + { + "concept": "1528AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "label": "Child" + }, + { + "concept": "972AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "label": "Brother/Sister" + }, + { + "concept": "1527AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "label": "Father/Mother" + }, + { + "concept": "5617AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "label": "Partner/Spouse" + }, + { + "concept": "5622AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "label": "Other" + } + ] + } + }, + { + "label": "Phone", + "type": "obs", + "required": true, + "id": "phoneNumber", + "questionOptions": { + "rendering": "text", + "concept": "1650AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "conceptMappings": [ + { + "relationship": "SAME-AS", + "type": "CIEL", + "value": "1650" + } + ] + } + } + ] + } + ] + } + ] + } + ], + "processor": "EncounterFormProcessor", + "encounterType": "dd528487-82a5-4082-9c72-ed246bd49591", + "referencedForms": [], + "uuid": "a8817ad2-ef92-46c8-bbf7-db336505027c", + "description": "test-repeating", + "version": "1.0" +} \ No newline at end of file diff --git a/src/components/repeat/repeat.test.tsx b/src/components/repeat/repeat.test.tsx index ce5a43eb7..68196ac6f 100644 --- a/src/components/repeat/repeat.test.tsx +++ b/src/components/repeat/repeat.test.tsx @@ -1,4 +1,14 @@ import { updateFieldIdInExpression } from './helpers'; +import { render, screen, waitFor } from '@testing-library/react'; +import repeatingComponentTestForm from '../../../__mocks__/forms/rfe-forms/repeating-component-test-form.json'; +import { useFormProviderContext } from '../../provider/form-provider'; +import { usePatient, useSession } from '@openmrs/esm-framework'; +import { type FormSchema, type SessionMode } from '../../types'; +import { FormEngine } from '../../..'; +import { mockPatient } from '../../../__mocks__/patient.mock'; +import { mockSessionDataResponse } from '../../../__mocks__/session.mock'; +import userEvent from '@testing-library/user-event'; +import React, { act } from 'react'; describe('RepeatingFieldComponent - handleExpressionFieldIdUpdate', () => { it('Should handle update of expression with ids in repeat group', () => { @@ -26,4 +36,117 @@ describe('RepeatingFieldComponent - handleExpressionFieldIdUpdate', () => { "myValue > today() || myValue <= '1/1/1890' || myValue > useFieldValue('visit_date') || myValue < useFieldValue('visit_date')", ); }); +}); + +describe('Repeat Component Tests', () => { + const mockUsePatient = jest.mocked(usePatient); + const mockUseSession = jest.mocked(useSession); + + global.ResizeObserver = require('resize-observer-polyfill'); + + jest.mock('@openmrs/esm-framework', () => { + const originalModule = jest.requireActual('@openmrs/esm-framework'); + return { + ...originalModule, + usePatient: jest.fn(), + useSession: jest.fn(), + createGlobalStore: jest.fn(), + ActionMenu: jest.fn(() =>
), + }; + }); + + jest.mock('../../provider/form-provider', () => { + const originalModule = jest.requireActual('../../provider/form-provider'); + return { + ...originalModule, + useFormProviderContext: jest.fn(), + }; + }); + + jest.mock('../../api', () => ({})); + + const renderForm = async (mode: SessionMode = 'enter') => { + await act(async () => { + render( + , + ); + }); + }; + + const user = userEvent.setup(); + + const mockContext = { + patient: {}, + sessionMode: 'enter', + formFields: repeatingComponentTestForm.pages[0].sections[0].questions, + methods: { getValues: jest.fn(), setValue: jest.fn() }, + addFormField: jest.fn(), + formFieldAdapters: { obsGroup: { transformFieldValue: jest.fn() } }, + }; + + beforeEach(() => { + Object.defineProperty(window, 'i18next', { + writable: true, + configurable: true, + value: { + language: 'en', + t: jest.fn(), + }, + }); + + mockUsePatient.mockImplementation(() => ({ + patient: mockPatient, + isLoading: false, + error: undefined, + patientUuid: mockPatient.id, + })); + + mockUseSession.mockImplementation(() => mockSessionDataResponse.data); + (useFormProviderContext as jest.Mock).mockReturnValue(mockContext); + }); + + it('Should add a repeatable field instance on clicking "Add"', async () => { + await renderForm(); + const addButton = screen.getByText(/add/i); + await user.click(addButton); + + expect(mockContext.addFormField).toHaveBeenCalledTimes(1); + expect(mockContext.addFormField).toHaveBeenCalledWith(expect.objectContaining({ id: 'patientContact_1' })); + }); + + it('Should clone the field at origin on clicking "Add"', async () => { + await renderForm(); + const addButton = screen.getByText(/add/i); + await user.click(addButton); + + const clonedField = screen.getByLabelText(/Contact relationship/i); + expect(clonedField).toBeInTheDocument(); + }); + + it('Should submit both the origin and its instances\' values successfully', async () => { + await renderForm(); + const addButton = screen.getByText(/add/i); + await user.click(addButton); + + const contactRelationshipField = screen.getByLabelText(/Contact relationship/i); + const phoneNumberField = screen.getByLabelText(/Phone/i); + + await user.type(contactRelationshipField, 'Child'); + await user.type(phoneNumberField, '123456789'); + + const submitButton = screen.getByText(/save/i); + await user.click(submitButton); + + await waitFor(() => { + expect(mockContext.methods.getValues).toHaveBeenCalledWith(expect.objectContaining({ + patientContactRelationship: 'Child', + phoneNumber: '123456789', + })); + }); + }); }); \ No newline at end of file