Skip to content

Commit

Permalink
Add example of sdc form tests based on allergies questionnaire
Browse files Browse the repository at this point in the history
  • Loading branch information
ruscoder committed Oct 30, 2024
1 parent 7f600e4 commit 587d8c6
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 11 deletions.
34 changes: 34 additions & 0 deletions src/__tests__/sdc-helpers.ts
Original file line number Diff line number Diff line change
@@ -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 } });
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ export function InlineChoice(props: InlineChoiceProps) {
const arrayValue = (value || []) as QuestionnaireItemAnswerOption[];

return (
<Form.Item {...formItem} data-testid="question-inline-choice">
<Form.Item {...formItem} data-testid={linkId}>
<Space direction={choiceOrientation}>
{answerOptionList?.map((answerOption) => (
<Checkbox
checked={arrayValue.findIndex((v) => _.isEqual(v?.value, answerOption.value)) !== -1}
key={JSON.stringify(answerOption)}
disabled={disabled}
onChange={() => onMultiChange(answerOption)}
// TODO: use linkId + __ + code instead
data-testid={`inline-choice__${_.kebabCase(
JSON.stringify(getDisplay(answerOption.value!)),
)}`}
Expand All @@ -44,14 +45,15 @@ export function InlineChoice(props: InlineChoiceProps) {
);
} else {
return (
<Form.Item {...formItem} data-testid="question-inline-choice">
<Form.Item {...formItem} data-testid={linkId}>
<Space direction={choiceOrientation}>
{answerOptionList?.map((answerOption) => (
<Radio
key={JSON.stringify(answerOption)}
checked={_.isEqual(value?.value, answerOption.value)}
disabled={disabled}
onChange={() => onChange(answerOption)}
// TODO: use linkId + __ + code instead
data-testid={`inline-choice__${_.kebabCase(
JSON.stringify(getDisplay(answerOption.value!)),
)}`}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const Default: Story = {
render: () => <InlineChoice parentPath={[]} questionItem={questionItemDefault} context={{} as ItemContext} />,
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);
Expand All @@ -47,7 +47,7 @@ export const Multiple: Story = {
render: () => <InlineChoice parentPath={[]} questionItem={questionItemMultiple} context={{} as ItemContext} />,
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function QuestionInteger({ parentPath, questionItem }: QuestionItemProps)
const { value, onChange, disabled, formItem, placeholder } = useFieldController(fieldName, questionItem);

return (
<Form.Item {...formItem}>
<Form.Item {...formItem} data-testid={linkId}>
<InputNumber
addonAfter={unit?.display}
style={inputStyle}
Expand All @@ -38,7 +38,7 @@ export function QuestionDecimal({ parentPath, questionItem }: QuestionItemProps)
const { value, onChange, disabled, formItem, placeholder } = useFieldController(fieldName, questionItem);

return (
<Form.Item {...formItem}>
<Form.Item {...formItem} data-testid={linkId}>
<InputNumber
addonAfter={unit?.display}
style={inputStyle}
Expand All @@ -60,7 +60,7 @@ export function QuestionQuantity(props: QuestionItemProps) {
const { value, onChange, disabled, formItem, placeholder } = useFieldController(fieldName, questionItem);

return (
<Form.Item {...formItem}>
<Form.Item {...formItem} data-testid={linkId}>
<InputNumber
addonAfter={unitOption?.display}
style={inputStyle}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function QuestionSolidRadio({ parentPath, questionItem }: QuestionItemPro
const { value, onChange, disabled, formItem } = useFieldController(fieldName, questionItem);

return (
<Form.Item {...formItem}>
<Form.Item {...formItem} data-testid={linkId}>
<RadioItems
options={options}
rightOption={rightOption}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function QuestionString({ parentPath, questionItem }: QuestionItemProps)
const { value, onChange, disabled, formItem, onBlur, placeholder } = useFieldController(fieldName, questionItem);

return (
<Form.Item {...formItem}>
<Form.Item {...formItem} data-testid={linkId}>
<Input value={value} disabled={disabled} onChange={onChange} onBlur={onBlur} placeholder={placeholder} />
</Form.Item>
);
Expand All @@ -21,7 +21,7 @@ export function QuestionText({ parentPath, questionItem }: QuestionItemProps) {
const { value, onChange, disabled, formItem, placeholder } = useFieldController(fieldName, questionItem);

return (
<Form.Item {...formItem}>
<Form.Item {...formItem} data-testid={linkId}>
<Input.TextArea
value={value}
rows={rowsNumber ?? 1}
Expand All @@ -31,4 +31,4 @@ export function QuestionText({ parentPath, questionItem }: QuestionItemProps) {
/>
</Form.Item>
);
}
}
Original file line number Diff line number Diff line change
@@ -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(
<I18nProvider i18n={i18n}>
<PatientDocument patient={patient} author={patient} questionnaireId="allergies" onSuccess={onSuccess} />
</I18nProvider>,
);

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>('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);
37 changes: 37 additions & 0 deletions src/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({}),
}));

0 comments on commit 587d8c6

Please sign in to comment.