-
Notifications
You must be signed in to change notification settings - Fork 101
(test) O3-4553: Add unit tests for question component #419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(test) O3-4553: Add unit tests for question component #419
Conversation
new translation keys:isQuestionInfoProvided,questionInfo,questionInfoPlaceholder
enhanced some code to match conventions
This commit has the test file for the question component. - For now only the tests only include the feature being introduced in this PR. - Future tests will be in a different PR. - Few changes has been made to the `Question` component: * Radio buttons in the same group should have both the attribute `defaultChecked` * Replaced `defaultChecked` with `checked`
…rm/question/question.test.tsx Co-authored-by: Nethmi Rodrigo <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @MohamedAliMefteh, a couple of suggestions:
jest.mock('../rendering-types/rendering-type.component', () => ({ | ||
__esModule: true, | ||
default: jest.fn(() => <div data-testid="render-type-component" />), | ||
})); | ||
|
||
jest.mock('../question-types/question-type.component', () => ({ | ||
__esModule: true, | ||
default: jest.fn(() => <div data-testid="question-type-component" />), | ||
})); | ||
|
||
jest.mock('../common/required-label/required-label.component', () => ({ | ||
__esModule: true, | ||
default: jest.fn(({ isRequired, text }) => ( | ||
<div data-testid="required-label"> | ||
{text} {isRequired ? '(Required)' : ''} | ||
</div> | ||
)), | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these internal components mocked? We should try to avoid mocking internal components like these as much as possible.
jest.mock('./question.scss', () => ({ | ||
questionIdLabel: 'mock-question-id-label', | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is a stylesheet mocked?
}); | ||
|
||
it('should render without crashing', () => { | ||
renderWithFormFieldProvider(<Question checkIfQuestionIdExists={jest.fn()} />); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Define checkIfQuestionIdExists
as a mock function at the top so you can just pass it in instead of defining it as jest.fn
every time.
src/components/interactive-builder/modals/question/question-form/question/question.test.tsx
Outdated
Show resolved
Hide resolved
// it('should validate duplicate question ids', () => { | ||
// const checkIfQuestionIdExists = jest.fn(() => true); | ||
// renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, { | ||
// formField: { ...initialFormField, id: 'duplicateId' }, | ||
// }); | ||
|
||
// expect(checkIfQuestionIdExists).toHaveBeenCalledWith('duplicateId'); | ||
// expect(screen.getByText(/This question ID already exists/i)).toBeInTheDocument(); | ||
// }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't include commented out test cases
// it('should not show label and required inputs when rendering type is markdown', () => { | ||
// renderWithFormFieldProvider(<Question checkIfQuestionIdExists={jest.fn()} />, { | ||
// formField: { ...initialFormField, questionOptions: { rendering: 'markdown' } }, | ||
// }); | ||
|
||
// expect(screen.queryByRole('textbox', { name: /Label/i })).not.toBeInTheDocument(); | ||
// expect(screen.queryByText(/Is this question a required or optional field?/i)).not.toBeInTheDocument(); | ||
// }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
it('should render RenderTypeComponent when rendering type is selected', () => { | ||
renderWithFormFieldProvider(<Question checkIfQuestionIdExists={jest.fn()} />, { | ||
formField: { ...initialFormField, questionOptions: { rendering: 'text' } }, | ||
}); | ||
|
||
expect(screen.getByTestId('render-type-component')).toBeInTheDocument(); | ||
expect(RenderTypeComponent).toHaveBeenCalled(); | ||
}); | ||
|
||
it('should render QuestionTypeComponent when question type is selected', () => { | ||
renderWithFormFieldProvider(<Question checkIfQuestionIdExists={jest.fn()} />, { | ||
formField: { ...initialFormField, type: 'obs' }, | ||
}); | ||
|
||
expect(screen.getByTestId('question-type-component')).toBeInTheDocument(); | ||
expect(QuestionTypeComponent).toHaveBeenCalled(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These components should be rendered even if they are not in the field.
const renderingTypeSelect = screen.getByLabelText(/Rendering type/i); | ||
fireEvent.click(renderingTypeSelect); | ||
|
||
// TODO: test the select options. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
// TODO: check if it gets the updated prop | ||
|
||
const optionalRadio = screen.getByLabelText(/Optional/i); | ||
fireEvent.click(optionalRadio); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer to use userEvents over fireEvent - https://testing-library.com/docs/user-event/intro#differences-from-fireevent
const optionalRadio = screen.getByLabelText(/Optional/i); | ||
fireEvent.click(optionalRadio); | ||
|
||
// TODO: check if it gets the updated prop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto
When writing these tests, I didn't take in consideration other tests written in openMRS form-builder. |
…ith useEvents and fixed other tests
enhanced some code to match conventions
This commit has the test file for the question component. - For now only the tests only include the feature being introduced in this PR. - Future tests will be in a different PR. - Few changes has been made to the `Question` component: * Radio buttons in the same group should have both the attribute `defaultChecked` * Replaced `defaultChecked` with `checked`
…ith useEvents and fixed other tests
…ithub.com/MohamedAliMefteh/openmrs-esm-form-builder into feat/add-unit-test-for-question-component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this file should have been changed. Maybe a bad rebase?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup definitely, I think it should be fixed now.
useFormField: () => ({ formField, setFormField: mockSetFormField }), | ||
})); | ||
const renderWithFormFieldProvider = ( | ||
ui: React.ReactElement, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ui: React.ReactElement, | |
component: React.ReactElement, |
) => { | ||
return render( | ||
<FormFieldProvider initialFormField={formField} selectedConcept={selectedConcept}> | ||
{ui} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ui} | |
{component} |
jest.mock('react-i18next', () => ({ | ||
useTranslation: jest.fn(() => ({ | ||
t: (key, fallback) => fallback || key, | ||
})), | ||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be mocked?
it('should render all required fields', () => { | ||
renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />); | ||
|
||
expect(screen.getByRole('textbox', { name: /Question ID/i })).toBeInTheDocument(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the /i
is there, the text doesn't need to strictly be in the case we expect.
expect(screen.getByRole('textbox', { name: /Question ID/i })).toBeInTheDocument(); | |
expect(screen.getByRole('textbox', { name: /question id/i })).toBeInTheDocument(); |
|
||
expect(screen.getByLabelText(/Question type/i)).toBeInTheDocument(); | ||
|
||
expect(screen.getByLabelText(/Rendering type/i)).toBeInTheDocument(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect(screen.getByLabelText(/Rendering type/i)).toBeInTheDocument(); | |
expect(screen.getByLabelText(/rendering type/i)).toBeInTheDocument(); |
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not required since this is done by default in our jest.config
).toBeInTheDocument(); | ||
expect(screen.getByRole('radio', { name: /Yes/i })).toBeInTheDocument(); | ||
expect(screen.getByRole('radio', { name: /No/i })).toBeInTheDocument(); | ||
const checkIfQuestionIdExists = jest.fn(() => false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be at the top of the file?
await user.clear(idInput); | ||
await user.type(idInput, 'newTestId'); | ||
|
||
expect((idInput as HTMLInputElement)).toHaveValue('newTestId'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be casted as an HTMLInputElement
?
it('should show appropriate rendering types for the selected question type', async () => { | ||
const user = userEvent.setup(); | ||
renderWithFormFieldProvider(<Question checkIfQuestionIdExists={checkIfQuestionIdExists} />, { | ||
formField: { ...initialFormField, type: 'encounterDatetime' }, | ||
}); | ||
|
||
const renderingTypeSelect = screen.getByLabelText(/Rendering type/i); | ||
|
||
expect(renderingTypeSelect).toBeInTheDocument(); | ||
|
||
expect(screen.getByLabelText(/Question type/i)).toHaveValue('encounterDatetime'); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this test case is supposed to test if the appropriate rendering types are shown, we should do more than checking if the select rendering type dropdown is there, we should also test the select options available. In this case, the question type is encounterDatetime
, so the rendering type options should only be date
or datetime
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added that and I just added the test cases for other question options to check renderingtypes
for each option.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @MohamedAliMefteh, very close now, a few final nitpicks:
await user.clear(questionInfoInput); | ||
await user.type(questionInfoInput, 'New question info'); | ||
|
||
expect((questionInfoInput as HTMLInputElement).value).toBe('New question info'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect((questionInfoInput as HTMLInputElement).value).toBe('New question info'); | |
expect(questionInfoInput).toHaveValue('New question info'); |
expect((screen.getByLabelText(/additional question info/i) as HTMLInputElement).value).toBe( | ||
'Initial question info', | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expect((screen.getByLabelText(/additional question info/i) as HTMLInputElement).value).toBe( | |
'Initial question info', | |
); | |
expect(screen.getByLabelText(/additional question info/i)).toHaveValue('Initial question info'); |
}); | ||
}); | ||
|
||
it('should handle initial state with questionInfo properly', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it('should handle initial state with questionInfo properly', () => { | |
it('should load question info from the form field object', () => { |
…ithub.com/MohamedAliMefteh/openmrs-esm-form-builder into feat/add-unit-test-for-question-component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @MohamedAliMefteh!
Thank you for the help! |
Requirements
Summary
This PR adds unit tests for the Question Component used within modals to ensure the correct rendering, behavior, and functionality. The tests cover a wide range of scenarios, including interactions with form fields, conditional rendering, and component-specific functionality such as converting labels to camel case, toggling question visibility, and updating question info.
However, some of the tests are currently failing and require adjustments. Additionally, a few test cases are incomplete and need to be finalized later.
Key Changes:
Rendering & Interactions: Tests ensure the Question component renders correctly and interacts with the form fields, such as updating the questionId, toggling visibility of additional question info, and updating question info text.
Question Info Visibility: Tests handle toggling the visibility of the "Additional Question Info" section when radio buttons (Yes/No) are clicked.
Label Conversion: Added tests for converting the question label to camel-case when the corresponding button is clicked.
Component Rendering: Tests ensure that child components like RenderTypeComponent and QuestionTypeComponent are rendered when their corresponding properties are set.
Known Issues:
Test Failures: There are a few tests that are currently not passing. The failing tests are marked with comments in the code, and they need to be reviewed and adjusted to handle specific behaviors or edge cases correctly.
Duplicate Question ID Validation: The test for checking duplicate question IDs is commented out due to an issue in mocking the validation function (checkIfQuestionIdExists). It needs to be reworked to properly test the validation logic.
Rendering Type with Markdown: The test to ensure that label and required inputs are hidden when the rendering type is set to "markdown" is commented out. This needs to be revisited to handle conditional rendering based on the rendering property.
Incomplete Tests: There are two tests marked with TODO comments that need to be completed later:
Question Required Status: The test for toggling between required and optional statuses is incomplete and needs to be updated to verify that the prop value is correctly passed down and updated.
Rendering Type Select Options: The test for checking the select options when rendering type is clicked is a work-in-progress and needs to be finalized to validate the available options for the user.
Screenshots
Related Issue
https://openmrs.atlassian.net/browse/O3-4553