Skip to content

Commit

Permalink
refactor(de forms): make required fields required & add test for new …
Browse files Browse the repository at this point in the history
…DE form (#356)

* refactor(de form): make required fields required

* test(new data element view): test required fields & cancel button

* chore: update @dhis2/ui

* chore: deduplicate @dhis2/ui to make "yarn build" work again

* refactor(de custom fields): extract and interpolate field labels in required fields

* chore(new de view test): remove as many "waitFor" and "act" as possible

* chore(new de view test): fix infinite recursion in console.warn stub

* chore(new de view test): implement Mozafar's PR feedback (2023-10-11)
  • Loading branch information
Mohammer5 authored Oct 12, 2023
1 parent a9daea7 commit ad57a5f
Show file tree
Hide file tree
Showing 11 changed files with 1,049 additions and 89 deletions.
26 changes: 10 additions & 16 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2023-09-13T14:05:55.693Z\n"
"PO-Revision-Date: 2023-09-13T14:05:55.693Z\n"
"POT-Creation-Date: 2023-10-11T10:29:42.492Z\n"
"PO-Revision-Date: 2023-10-11T10:29:42.492Z\n"

msgid "schemas"
msgstr "schemas"
Expand Down Expand Up @@ -555,14 +555,14 @@ msgstr "Basic information"
msgid "Set up the information for this data element"
msgstr "Set up the information for this data element"

msgid "Name (required)"
msgstr "Name (required)"
msgid "{{fieldLabel}} (required)"
msgstr "{{fieldLabel}} (required)"

msgid "A data element name should be concise and easy to recognize."
msgstr "A data element name should be concise and easy to recognize."

msgid "Short name (required)"
msgstr "Short name (required)"
msgid "Short name"
msgstr "Short name"

msgid "Often used in reports where space is limited"
msgstr "Often used in reports where space is limited"
Expand Down Expand Up @@ -635,9 +635,6 @@ msgstr ""
"A color and icon are helpful for identifying data elements in "
"information-dense screens."

msgid "Domain (required)"
msgstr "Domain (required)"

msgid "A data element can either be aggregated or tracked data."
msgstr "A data element can either be aggregated or tracked data."

Expand All @@ -650,21 +647,18 @@ msgstr "Refresh list"
msgid "Add new"
msgstr "Add new"

msgid "Value type (required)"
msgstr "Value type (required)"
msgid "Value type"
msgstr "Value type"

msgid "The type of data that will be recorded."
msgstr "The type of data that will be recorded."

msgid "Aggregation type (required)"
msgstr "Aggregation type (required)"
msgid "Aggregation type"
msgstr "Aggregation type"

msgid "The default way to aggregate this data element in analytics."
msgstr "The default way to aggregate this data element in analytics."

msgid "Category combination (required)"
msgstr "Category combination (required)"

msgid "Choose how this data element is disaggregated"
msgstr "Choose how this data element is disaggregated"

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
},
"dependencies": {
"@dhis2/app-runtime": "^3.9.3",
"@dhis2/ui": "^8.13.10",
"@dhis2/ui": "^8.14.7",
"@types/lodash": "^4.14.198",
"lodash": "^4.17.21",
"react-color": "^2.19.3",
Expand Down
201 changes: 201 additions & 0 deletions src/pages/dataElements/New.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import {
RenderResult,
act,
fireEvent,
render,
waitFor,
} from '@testing-library/react'
import React from 'react'
import { RouterProvider, createMemoryRouter } from 'react-router-dom'
import dataElementSchemaMock from '../../__mocks__/schema/dataElementsSchema.json'
import { useSchemaStore } from '../../lib/schemas/schemaStore'
import { ModelSchemas } from '../../lib/useLoadApp'
import { ComponentWithProvider } from '../../testUtils/TestComponentWithRouter'
import attributes from './__mocks__/attributes.json'
import categoryCombosPage1 from './__mocks__/categoryCombosPage1.json'
import { Component as New } from './New'

// @TODO: For some reason string interpolation somewhere within the Transfer
// component does not get transpiled correctly for the cjs build when
// building the UI library. We'll have to address that at some point.
// See: https://dhis2.atlassian.net/browse/LIBS-537
jest.mock('@dhis2/ui', () => {
const ui = jest.requireActual('@dhis2/ui')

return {
__esModule: true,
...ui,
Transfer: () => <div />,
}
})

async function changeSingleSelect(
result: RenderResult,
selectLabelText: string,
text: string
) {
const selectLabel = await result.findByText(selectLabelText)
const field = selectLabel.parentNode?.parentNode as HTMLElement
expect(field).toBeTruthy()

const trigger = field.querySelector(
'[data-test="dhis2-uicore-select-input"].root'
) as HTMLElement
expect(trigger).toBeTruthy()
fireEvent.click(trigger)

await result.findByTestId('dhis2-uicore-layer')
const optionElement = await result.findByText(text, {
selector: '[data-value]',
})

fireEvent.click(optionElement)
}

describe('Data Elements / New', () => {
const consoleWarn = console.warn
jest.spyOn(console, 'warn').mockImplementation((value) => {
if (!value.match(/The query should be static/)) {
consoleWarn(value)
}
})

useSchemaStore.getState().setSchemas({
dataElement: dataElementSchemaMock,
} as unknown as ModelSchemas)

const customData = {
attributes: attributes,
categoryCombos: categoryCombosPage1,
}

it('should return to the list view when cancelling', async () => {
const router = createMemoryRouter(
[
{ path: '/new', element: <New /> },
{ path: '/dataElements', element: <div>List view</div> },
],
{ initialEntries: ['/new'] }
)

const result = render(
<ComponentWithProvider dataForCustomProvider={customData}>
<RouterProvider router={router} />
</ComponentWithProvider>
)

const cancelButton = await result.findByText('Exit without saving', {
selector: 'button',
})

expect(result.queryByText('List view')).toBeNull()

fireEvent.click(cancelButton)

const listView = await result.findByText('List view')
expect(listView).toBeTruthy()
})

it('should not submit when required values are missing', async () => {
const router = createMemoryRouter([{ path: '/', element: <New /> }])
const result = render(
<ComponentWithProvider dataForCustomProvider={customData}>
<RouterProvider router={router} />
</ComponentWithProvider>
)

const submitButton = await result.findByText('Create data element', {
selector: 'button',
})

fireEvent.click(submitButton as HTMLButtonElement)

expect(
result.container.querySelectorAll(
'.error[data-test$="-validation"]'
)
).toHaveLength(4)

const nameRequiredError = await result.findByText('Required', {
selector: '[data-test="dataelementsformfields-name-validation"]',
})
expect(nameRequiredError).toBeTruthy()

const shortNameRequiredError = await result.findByText('Required', {
selector:
'[data-test="dataelementsformfields-shortname-validation"]',
})
expect(shortNameRequiredError).toBeTruthy()

const valueTypeRequiredError = await result.findByText('Required', {
selector:
'[data-test="dataelementsformfields-valuetype-validation"]',
})
expect(valueTypeRequiredError).toBeTruthy()

const aggregationTypeRequiredError = await result.findByText(
'Required',
{
selector:
'[data-test="dataelementsformfields-aggregationtype-validation"]',
}
)
expect(aggregationTypeRequiredError).toBeTruthy()
})

it('should submit the data and return to the list view on success', async () => {
const dataElementCustomData = jest.fn(() => Promise.resolve({}))
const router = createMemoryRouter(
[
{ path: '/new', element: <New /> },
{ path: '/dataElements', element: <div>List view</div> },
],
{
initialIndex: 0,
initialEntries: ['/new'],
}
)
const result = render(
<>
<div id="dhis2-portal-root" />
<ComponentWithProvider
dataForCustomProvider={{
...customData,
dataElements: dataElementCustomData,
}}
>
<RouterProvider router={router} />
</ComponentWithProvider>
</>
)

const submitButton = await result.findByText('Create data element', {
selector: 'button',
})

expect(submitButton).toBeTruthy()

fireEvent.change(
result.getByRole('textbox', {
name: 'Name (required) *',
}) as HTMLElement,
{ target: { value: 'Data element name' } }
)

fireEvent.change(
result.getByRole('textbox', {
name: 'Short name (required) *',
}) as HTMLElement,
{ target: { value: 'Data element short name' } }
)

await changeSingleSelect(result, 'Value type (required)', 'Text')

await changeSingleSelect(result, 'Aggregation type (required)', 'Sum')

fireEvent.click(submitButton)

const listView = await result.findByText('List view')
expect(listView).toBeTruthy()
})
})
4 changes: 2 additions & 2 deletions src/pages/dataElements/New.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ export const Component = () => {

if (error && !loading) {
// @TODO(Edit): Implement error screen
return `Error: ${error.toString()}`
return <>Error: {error.toString()}</>
}

if (loading) {
// @TODO(Edit): Implement loading screen
return 'Loading...'
return <>Loading...</>
}

const initialValues = computeInitialValues(customAttributesQuery.data)
Expand Down
66 changes: 66 additions & 0 deletions src/pages/dataElements/__mocks__/attributes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[
{
"valueType": "TEXT",
"mandatory": false,
"optionSet": {
"options": [
{
"code": "INPUT",
"name": "Input",
"displayName": "Input",
"id": "Fr4igNnPYTv"
},
{
"code": "ACTIVITY",
"name": "Activity",
"displayName": "Activity",
"id": "xuAA8BQjdZq"
},
{
"code": "OUTPUT",
"name": "Output",
"displayName": "Output",
"id": "tpJ71rNdmeo"
},
{
"code": "IMPACT",
"name": "Impact",
"displayName": "Impact",
"id": "QIbgv5M77In"
}
]
},
"displayFormName": "Classification",
"id": "Z4X3J7jMLYV"
},
{
"valueType": "LONG_TEXT",
"mandatory": false,
"displayFormName": "Collection method",
"id": "qXS2NDUEAOS"
},
{
"valueType": "TEXT",
"mandatory": false,
"displayFormName": "NGO ID",
"id": "n2xYlNbsfko"
},
{
"valueType": "TEXT",
"mandatory": false,
"displayFormName": "PEPFAR ID",
"id": "dLHLR5O4YFI"
},
{
"valueType": "TEXT",
"mandatory": false,
"displayFormName": "Rationale",
"id": "AhsCAtM3L0g"
},
{
"valueType": "TEXT",
"mandatory": false,
"displayFormName": "Unit of measure",
"id": "Y1LUDU8sWBR"
}
]
Loading

0 comments on commit ad57a5f

Please sign in to comment.