import React from 'react'; import { vi, describe, it, expect, test, beforeEach } from 'vitest'; import { Form, Formik } from 'formik'; import { render, screen } from '@testing-library/react'; import { useTranslation } from 'react-i18next'; import { getDefaultsFromConfigSchema, useConfig } from '@openmrs/esm-framework'; import { Field } from './field.component'; import { esmPatientRegistrationSchema, type RegistrationConfig } from '../../config-schema'; import { type Resources } from '../../registration.resource'; import type { AddressTemplate, FormValues } from '../patient-registration.types'; import { PatientRegistrationContextProvider } from '../patient-registration-context'; import { ResourcesContextProvider } from '../../resources-context'; import { renderWithContext } from 'tools'; const mockUseConfig = vi.mocked(useConfig); const predefinedAddressTemplate = { uuid: 'test-address-template-uuid', property: 'layout.address.format', description: 'Test Address Template', display: 'Layout - Address Format = \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n address1\n address2\n cityVillage stateProvince country postalCode\n \n ', value: '\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n address1 address2\r\n cityVillage stateProvince postalCode\r\n country\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n ', }; const mockIdentifierTypes = [ { fieldName: 'openMrsId', format: '', identifierSources: [ { uuid: '8549f706-7e85-4c1d-9424-217d50a2988b', name: 'Generator for OpenMRS ID', description: 'Generator for OpenMRS ID', baseCharacterSet: '0123456789ACDEFGHJKLMNPRTUVWXY', prefix: '', }, ], isPrimary: true, name: 'OpenMRS ID', required: true, uniquenessBehavior: 'UNIQUE' as const, uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', }, { fieldName: 'idCard', format: '', identifierSources: [], isPrimary: false, name: 'ID Card', required: false, uniquenessBehavior: 'UNIQUE' as const, uuid: 'b4143563-16cd-4439-b288-f83d61670fc8', }, { fieldName: 'legacyId', format: '', identifierSources: [], isPrimary: false, name: 'Legacy ID', required: false, uniquenessBehavior: null, uuid: '22348099-3873-459e-a32e-d93b17eda533', }, { fieldName: 'oldIdentificationNumber', format: '', identifierSources: [], isPrimary: false, name: 'Old Identification Number', required: false, uniquenessBehavior: null, uuid: '8d79403a-c2cc-11de-8d13-0010c6dffd0f', }, { fieldName: 'openMrsIdentificationNumber', format: '', identifierSources: [], isPrimary: false, name: 'OpenMRS Identification Number', required: false, uniquenessBehavior: null, uuid: '8d793bee-c2cc-11de-8d13-0010c6dffd0f', }, ]; const mockResourcesContextValue: Resources = { addressTemplate: predefinedAddressTemplate as unknown as AddressTemplate, currentSession: { authenticated: true, sessionId: 'JSESSION', currentProvider: { uuid: 'provider-uuid', identifier: 'PRO-123' }, }, relationshipTypes: { results: [] }, identifierTypes: [...mockIdentifierTypes], }; const initialContextValues = { currentPhoto: 'data:image/png;base64,1234567890', identifierTypes: [], inEditMode: false, initialFormValues: {} as FormValues, setCapturePhotoProps: vi.fn(), setFieldValue: vi.fn(), setFieldTouched: vi.fn(), setInitialFormValues: vi.fn(), validationSchema: null, values: {} as FormValues, }; describe('Field', () => { beforeEach(() => { mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema), }); }); it('should render NameField component when name prop is "name"', () => { mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema), fieldConfigurations: { name: { allowUnidentifiedPatients: true, displayMiddleName: true, defaultUnknownGivenName: 'UNKNOWN', defaultUnknownFamilyName: 'UNKNOWN', }, } as RegistrationConfig['fieldConfigurations'], }); renderWithContext(
, ResourcesContextProvider, mockResourcesContextValue, ); expect(screen.getByText('Full Name')).toBeInTheDocument(); }); it('should render GenderField component when name prop is "gender"', () => { mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema), fieldConfigurations: { gender: [ { value: 'Male', label: 'Male', id: 'male', }, ], } as unknown as RegistrationConfig['fieldConfigurations'], }); renderWithContext(
, ResourcesContextProvider, mockResourcesContextValue, ); expect(screen.getByLabelText('Male')).toBeInTheDocument(); }); it('should render DobField component when name prop is "dob"', () => { renderWithContext(
, ResourcesContextProvider, mockResourcesContextValue, ); expect(screen.getByText('Birth')).toBeInTheDocument(); }); // TODO: Re-enable after migrating the in-test `vi.mock` of `address-hierarchy.resource` // to a top-level mock with `mockReturnValue` set. The current setup leaves // `useOrderedAddressHierarchyLevels` returning undefined and the component crashes. it.skip('should render AddressComponent component when name prop is "address"', () => { vi.mock('./address/address-hierarchy.resource', async () => ({ ...((await vi.importActual('./address/address-hierarchy.resource')) as object), useOrderedAddressHierarchyLevels: vi.fn(), })); mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema), fieldConfigurations: { address: { useAddressHierarchy: { enabled: false, useQuickSearch: false, searchAddressByLevel: false, }, }, } as RegistrationConfig['fieldConfigurations'], }); renderWithContext(
, ResourcesContextProvider, mockResourcesContextValue, ); expect(screen.getByText('Address')).toBeInTheDocument(); }); it('should render Identifiers component when name prop is "id"', () => { mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema), defaultPatientIdentifierTypes: ['OpenMRS ID'], }); const openmrsID = { name: 'OpenMRS ID', fieldName: 'openMrsId', required: true, uuid: '05a29f94-c0ed-11e2-94be-8c13b969e334', format: null, isPrimary: true, identifierSources: [ { uuid: '691eed12-c0f1-11e2-94be-8c13b969e334', name: 'Generator 1 for OpenMRS ID', autoGenerationOption: { manualEntryEnabled: false, automaticGenerationEnabled: true, }, }, { uuid: '01af8526-cea4-4175-aa90-340acb411771', name: 'Generator 2 for OpenMRS ID', autoGenerationOption: { manualEntryEnabled: true, automaticGenerationEnabled: true, }, }, ], identifierUuid: 'openmrs-identifier-uuid', identifierTypeUuid: 'openmrs-id-identifier-type-uuid', initialValue: '12345', identifierValue: '12345', identifierName: 'OpenMRS ID', preferred: true, selectedSource: { uuid: 'openmrs-id-selected-source-uuid', name: 'Generator 1 for OpenMRS ID', autoGenerationOption: { manualEntryEnabled: false, automaticGenerationEnabled: true, }, }, autoGenerationSource: null, }; const updatedContextValues = { currentPhoto: 'data:image/png;base64,1234567890', identifierTypes: [], inEditMode: false, initialFormValues: { identifiers: { openmrsID } } as unknown as FormValues, setCapturePhotoProps: vi.fn(), setFieldValue: vi.fn(), setInitialFormValues: vi.fn(), validationSchema: null, values: { identifiers: { openmrsID } } as unknown as FormValues, setFieldTouched: vi.fn(), }; renderWithContext(
, ResourcesContextProvider, mockResourcesContextValue, ); expect(screen.getByText('Identifiers')).toBeInTheDocument(); }); it('should render the label of a text field and no input', () => { mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema), fieldDefinitions: [ { id: 'studyConsentStatement', type: 'text', label: 'I consent to being a part of the study.' }, ] as RegistrationConfig['fieldDefinitions'], }); render(); expect(screen.getByText('I consent to being a part of the study.')).toBeInTheDocument(); expect(screen.queryByRole('textbox')).not.toBeInTheDocument(); }); it('should translate the label of a text field under its id suffixed with Text', ({ onTestFinished }) => { mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema), fieldDefinitions: [ { id: 'studyConsentStatement', type: 'text', label: 'I consent to being a part of the study.' }, ] as RegistrationConfig['fieldDefinitions'], }); const translate = vi .spyOn(useTranslation(), 't') .mockImplementation((key: string) => (key === 'studyConsentStatementText' ? 'Ninakubali' : key)); onTestFinished(() => translate.mockRestore()); render(); expect(screen.getByText('Ninakubali')).toBeInTheDocument(); }); it('should keep the line breaks of a text field label', () => { mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema), fieldDefinitions: [ { id: 'studyConsentStatement', type: 'text', label: 'First paragraph.\nSecond paragraph.' }, ] as RegistrationConfig['fieldDefinitions'], }); render(); expect(screen.getByText('First paragraph.\nSecond paragraph.', { normalizer: (text) => text })).toHaveClass( 'textField', ); }); it.each([ [false, true], [true, false], ])( 'limits the Identifiers section to half width on desktop: %s preferred selection -> %s', (allowPreferredSelection, isHalfWidth) => { const defaults = getDefaultsFromConfigSchema(esmPatientRegistrationSchema); mockUseConfig.mockReturnValue({ ...defaults, fieldConfigurations: { ...defaults.fieldConfigurations, identifier: { allowPreferredSelection } }, }); renderWithContext(
, ResourcesContextProvider, mockResourcesContextValue, ); const section = screen.getByTestId('identifiers-section'); if (isHalfWidth) { expect(section).toHaveClass('halfWidthInDesktopView'); } else { expect(section).not.toHaveClass('halfWidthInDesktopView'); } }, ); it('should return null and report an error for an invalid field name', () => { const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}); mockUseConfig.mockReturnValue({ ...getDefaultsFromConfigSchema(esmPatientRegistrationSchema), fieldDefinitions: [{ id: 'weight' }] as RegistrationConfig['fieldDefinitions'], }); let error = null; try { renderWithContext(
, ResourcesContextProvider, mockResourcesContextValue, ); } catch (err) { error = err; } expect(error).toMatch(/Invalid field name 'invalidField'. Valid options are /); expect(screen.queryByTestId('invalid-field')).not.toBeInTheDocument(); consoleError.mockRestore(); }); });