From 587d8c6eddb891da0d87e9b5c73e019f154cbdc1 Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Wed, 30 Oct 2024 21:18:51 +0100 Subject: [PATCH] Add example of sdc form tests based on allergies questionnaire --- src/__tests__/sdc-helpers.ts | 34 ++++++++++ .../widgets/inline-choice/index.tsx | 6 +- .../inline-choice/inline-choice.stories.tsx | 4 +- .../widgets/number.tsx | 6 +- .../widgets/radio.tsx | 2 +- .../widgets/string.tsx | 6 +- .../__tests__/allergies-form.test.tsx | 67 +++++++++++++++++++ src/setupTests.ts | 37 ++++++++++ 8 files changed, 151 insertions(+), 11 deletions(-) create mode 100644 src/__tests__/sdc-helpers.ts create mode 100644 src/containers/PatientDetails/PatientDocument/__tests__/allergies-form.test.tsx diff --git a/src/__tests__/sdc-helpers.ts b/src/__tests__/sdc-helpers.ts new file mode 100644 index 00000000..f9dc5e6e --- /dev/null +++ b/src/__tests__/sdc-helpers.ts @@ -0,0 +1,34 @@ +import { screen, fireEvent, within } from '@testing-library/react'; + +export async function chooseSelectOption(questionId: string, optionLabel: string) { + const questionElement = await screen.findByTestId(questionId); + fireEvent.focus(questionElement.querySelector('input')!); + fireEvent.keyDown(questionElement.querySelector('input')!, { key: 'ArrowDown', code: 40 }); + + const optionLabelMatcher = (_content: string, element: Element | null) => element?.textContent === optionLabel; + const option = await screen.findByText(optionLabelMatcher, { + selector: '.react-select__option', + }); + + fireEvent.click(option); + await screen.findByText(optionLabelMatcher, { + selector: '.react-select__single-value,.react-select__multi-value', + }); +} + +export async function chooseInlineOption(questionId: string, optionLabel: string) { + const questionElement = await screen.findByTestId(questionId); + const optionLabelMatcher = (_content: string, element: Element | null) => element?.textContent === optionLabel; + const option = await within(questionElement).findByText(optionLabelMatcher, { + selector: 'label', + }); + + fireEvent.click(option); +} + +export async function inputText(questionId: string, value: string) { + const questionElement = await screen.findByTestId(questionId); + const input = (questionElement.querySelector('input') ?? questionElement.querySelector('textarea'))!; + + fireEvent.change(input, { target: { value: value } }); +} diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/inline-choice/index.tsx b/src/components/BaseQuestionnaireResponseForm/widgets/inline-choice/index.tsx index b0bed48b..55d7a1bf 100644 --- a/src/components/BaseQuestionnaireResponseForm/widgets/inline-choice/index.tsx +++ b/src/components/BaseQuestionnaireResponseForm/widgets/inline-choice/index.tsx @@ -24,7 +24,7 @@ export function InlineChoice(props: InlineChoiceProps) { const arrayValue = (value || []) as QuestionnaireItemAnswerOption[]; return ( - + {answerOptionList?.map((answerOption) => ( onMultiChange(answerOption)} + // TODO: use linkId + __ + code instead data-testid={`inline-choice__${_.kebabCase( JSON.stringify(getDisplay(answerOption.value!)), )}`} @@ -44,7 +45,7 @@ export function InlineChoice(props: InlineChoiceProps) { ); } else { return ( - + {answerOptionList?.map((answerOption) => ( onChange(answerOption)} + // TODO: use linkId + __ + code instead data-testid={`inline-choice__${_.kebabCase( JSON.stringify(getDisplay(answerOption.value!)), )}`} diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/inline-choice/inline-choice.stories.tsx b/src/components/BaseQuestionnaireResponseForm/widgets/inline-choice/inline-choice.stories.tsx index 3e82af2a..49f05dc9 100644 --- a/src/components/BaseQuestionnaireResponseForm/widgets/inline-choice/inline-choice.stories.tsx +++ b/src/components/BaseQuestionnaireResponseForm/widgets/inline-choice/inline-choice.stories.tsx @@ -21,7 +21,7 @@ export const Default: Story = { render: () => , play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const getQuestion = () => canvas.findAllByTestId('question-inline-choice'); + const getQuestion = () => canvas.findAllByTestId('type'); const questions: HTMLElement[] = await getQuestion(); expect(questions.length > 0).toBe(true); @@ -47,7 +47,7 @@ export const Multiple: Story = { render: () => , play: async ({ canvasElement }) => { const canvas = within(canvasElement); - const getQuestion = () => canvas.findAllByTestId('question-inline-choice'); + const getQuestion = () => canvas.findAllByTestId('type'); const questions: HTMLElement[] = await getQuestion(); expect(questions.length > 0).toBe(true); diff --git a/src/components/BaseQuestionnaireResponseForm/widgets/number.tsx b/src/components/BaseQuestionnaireResponseForm/widgets/number.tsx index b1923f44..05ae4397 100644 --- a/src/components/BaseQuestionnaireResponseForm/widgets/number.tsx +++ b/src/components/BaseQuestionnaireResponseForm/widgets/number.tsx @@ -17,7 +17,7 @@ export function QuestionInteger({ parentPath, questionItem }: QuestionItemProps) const { value, onChange, disabled, formItem, placeholder } = useFieldController(fieldName, questionItem); return ( - + + + + + ); @@ -21,7 +21,7 @@ export function QuestionText({ parentPath, questionItem }: QuestionItemProps) { const { value, onChange, disabled, formItem, placeholder } = useFieldController(fieldName, questionItem); return ( - + ); -} \ No newline at end of file +} diff --git a/src/containers/PatientDetails/PatientDocument/__tests__/allergies-form.test.tsx b/src/containers/PatientDetails/PatientDocument/__tests__/allergies-form.test.tsx new file mode 100644 index 00000000..2d31dad9 --- /dev/null +++ b/src/containers/PatientDetails/PatientDocument/__tests__/allergies-form.test.tsx @@ -0,0 +1,67 @@ +import { i18n } from '@lingui/core'; +import { I18nProvider } from '@lingui/react'; +import { screen, render, waitFor, fireEvent, act } from '@testing-library/react'; +import { expect, test, vi } from 'vitest'; + +import { extractBundleResources, getFHIRResources } from 'aidbox-react/lib/services/fhir'; + +import { AllergyIntolerance } from '@beda.software/aidbox-types'; +import { ensure, withRootAccess } from '@beda.software/fhir-react'; + +import { chooseInlineOption, inputText } from 'src/__tests__/sdc-helpers'; +import { PatientDocument } from 'src/containers/PatientDetails/PatientDocument'; +import { axiosInstance } from 'src/services/fhir'; +import { createPatient, loginAdminUser } from 'src/setupTests'; + +test('Create new Clinical Trial Test', async () => { + await loginAdminUser(); + const { patient } = await withRootAccess(axiosInstance, async () => { + const patient = await createPatient({ + name: [{ given: ['John'], family: 'Smith' }], + }); + + return { patient }; + }); + + const onSuccess = vi.fn(); + act(() => { + i18n.activate('en'); + }); + + render( + + + , + ); + + await chooseInlineOption('type', 'Food'); + await chooseInlineOption('reaction', 'Headache'); + await chooseInlineOption('substance-food', 'Eggs'); + await inputText('notes', 'Notes'); + + fireEvent.click(screen.getByTestId('submit-button')); + + await waitFor(() => expect(onSuccess).toHaveBeenCalled()); + + await withRootAccess(axiosInstance, async () => { + const allergyIntoleranceList = await waitFor(async () => { + const allergyIntoleranceResponse = await getFHIRResources('AllergyIntolerance', { + patient: `Patient/${patient.id}`, + }); + return extractBundleResources(ensure(allergyIntoleranceResponse)).AllergyIntolerance; + }); + + expect(allergyIntoleranceList.length).toBe(1); + + // TODO: Looks like a bug in the extract, category is undefined + // expect(allergyIntoleranceList[0]?.category?.[0]).toBe('food'); + expect(allergyIntoleranceList[0]?.code?.coding?.[0]).toMatchObject({ + code: '102263004', + display: 'Eggs', + }); + expect(allergyIntoleranceList[0]?.reaction?.[0]?.manifestation?.[0]?.coding?.[0]).toMatchObject({ + code: '25064002', + display: 'Headache', + }); + }); +}, 60000); diff --git a/src/setupTests.ts b/src/setupTests.ts index 582f8701..000134f2 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -225,3 +225,40 @@ afterEach(async () => { // afterAll(() => { // vi.clearAllTimers(); // }); + +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: vi.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), // deprecated + removeListener: vi.fn(), // deprecated + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), +}); + +Object.defineProperty(window, 'localStorage', { + value: { + getItem: vi.fn(), + setItem: vi.fn(), + removeItem: vi.fn(), + clear: vi.fn(), + }, + writable: true, +}); + +const reactRouterDomModule = (await vi.importActual('react-router-dom')) as any; +vi.mock('react-router-dom', () => ({ + ...reactRouterDomModule, + useNavigate: () => vi.fn(), + useLocation: vi.fn().mockReturnValue({ + pathname: '/testroute', + search: '', + hash: '', + state: null, + }), + useParams: vi.fn().mockReturnValue({}), +}));