diff --git a/i18n/en.pot b/i18n/en.pot index 7592ac8d9..7de727ae6 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -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: 2024-10-18T12:58:59.146Z\n" -"PO-Revision-Date: 2024-10-18T12:58:59.146Z\n" +"POT-Creation-Date: 2024-11-29T16:17:15.582Z\n" +"PO-Revision-Date: 2024-11-29T16:17:15.582Z\n" msgid "Not authorized" msgstr "Not authorized" @@ -81,6 +81,9 @@ msgstr "Couldn't validate the form. This does not effect form completion!" msgid "The form can't be completed while invalid" msgstr "The form can't be completed while invalid" +msgid "Compulsory fields must be filled out before completing the form" +msgstr "Compulsory fields must be filled out before completing the form" + msgid "1 comment required" msgstr "1 comment required" @@ -213,6 +216,9 @@ msgstr "Warning, saved" msgid "Locked, not editable" msgstr "Locked, not editable" +msgid "Compulsory field" +msgstr "Compulsory field" + msgid "Help" msgstr "Help" diff --git a/src/bottom-bar/complete-button.test.js b/src/bottom-bar/complete-button.test.js new file mode 100644 index 000000000..12be2aea5 --- /dev/null +++ b/src/bottom-bar/complete-button.test.js @@ -0,0 +1,220 @@ +import { waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import React from 'react' +import { useMetadata } from '../shared/metadata/use-metadata.js' +import { useDataSetId } from '../shared/use-context-selection/use-context-selection.js' +import { useDataValueSet } from '../shared/use-data-value-set/use-data-value-set.js' +import { useImperativeValidate } from '../shared/validation/use-imperative-validate.js' +import { render } from '../test-utils/render.js' +import CompleteButton from './complete-button.js' + +const mockShow = jest.fn() +const mockSetFormCompletion = jest + .fn() + .mockImplementation(() => Promise.resolve()) +const mockValidate = jest.fn().mockImplementation(() => + Promise.resolve({ + commentRequiredViolations: [], + validationRuleViolations: [], + }) +) +const mockSetCompleteAttempted = jest.fn() +const mockIsComplete = jest.fn() + +jest.mock('@dhis2/app-runtime', () => ({ + ...jest.requireActual('@dhis2/app-runtime'), + useAlert: jest.fn(() => ({ + show: mockShow, + })), +})) + +jest.mock('../shared/use-context-selection/use-context-selection.js', () => ({ + ...jest.requireActual( + '../shared/use-context-selection/use-context-selection.js' + ), + useDataSetId: jest.fn(), +})) + +jest.mock('../shared/metadata/use-metadata.js', () => ({ + useMetadata: jest.fn(), +})) + +jest.mock('../shared/validation/use-imperative-validate.js', () => ({ + useImperativeValidate: jest.fn(), +})) + +jest.mock('../shared/completion/use-set-form-completion-mutation.js', () => ({ + ...jest.requireActual( + '../shared/completion/use-set-form-completion-mutation.js' + ), + useSetFormCompletionMutation: jest.fn(() => ({ + mutateAsync: mockSetFormCompletion, + })), +})) + +jest.mock('../shared/stores/entry-form-store.js', () => ({ + ...jest.requireActual('../shared/stores/entry-form-store.js'), + useEntryFormStore: jest.fn().mockImplementation((func) => { + const state = { + setCompleteAttempted: mockSetCompleteAttempted, + } + return func(state) + }), +})) + +jest.mock('../shared/stores/data-value-store.js', () => ({ + ...jest.requireActual('../shared/stores/data-value-store.js'), + useValueStore: jest.fn().mockImplementation((func) => { + const state = { + isComplete: mockIsComplete, + } + return func(state) + }), +})) + +jest.mock('../shared/use-data-value-set/use-data-value-set.js', () => ({ + useDataValueSet: jest.fn(), +})) + +const MOCK_METADATA = { + dataSets: { + data_set_id_1: { + id: 'data_set_id_1', + }, + data_set_id_compulsory_validation_without_cdeo: { + id: 'data_set_id_compulsory_validation_without_cdeo', + compulsoryDataElementOperands: [], + compulsoryFieldsCompleteOnly: true, + }, + data_set_id_compulsory_validation_with_cdeo: { + id: 'data_set_id_compulsory_validation_without_cdeo', + compulsoryDataElementOperands: [ + { + dataElement: { + id: 'de-id-1', + }, + categoryOptionCombo: { + id: 'coc-id-1', + }, + }, + { + dataElement: { + id: 'de-id-2', + }, + categoryOptionCombo: { + id: 'coc-id-2', + }, + }, + ], + compulsoryFieldsCompleteOnly: true, + }, + }, +} + +const MOCK_DATA = { + dataValues: { + 'de-id-1': { + 'coc-id-1': { + value: '5', + }, + }, + 'de-id-2': { + 'coc-id-2': { + value: '10', + }, + }, + }, +} + +const MOCK_DATA_INCOMPLETE = { + dataValues: { + 'de-id-1': { + 'coc-id-1': { + value: '5', + }, + }, + }, +} + +describe('CompleteButton', () => { + beforeEach(() => { + jest.clearAllMocks() + useImperativeValidate.mockReturnValue(mockValidate) + }) + + it('validates form and completes when clicked', () => { + mockIsComplete.mockReturnValue(false) + useDataSetId.mockReturnValue(['data_set_id_1']) + useDataValueSet.mockReturnValue({ data: MOCK_DATA }) + useMetadata.mockReturnValue({ data: MOCK_METADATA }) + const { getByText } = render() + + userEvent.click(getByText('Mark complete')) + expect(mockValidate).toHaveBeenCalledOnce() + expect(mockSetFormCompletion).toHaveBeenCalledWith({ completed: true }) + }) + + it('completes if the compulsoryFieldsCompleteOnly:true but there are no compulsory data element operands', () => { + mockIsComplete.mockReturnValue(false) + useDataSetId.mockReturnValue([ + 'data_set_id_compulsory_validation_without_cdeo', + ]) + useDataValueSet.mockReturnValue({ data: MOCK_DATA }) + useMetadata.mockReturnValue({ data: MOCK_METADATA }) + const { getByText } = render() + + userEvent.click(getByText('Mark complete')) + expect(mockValidate).toHaveBeenCalledOnce() + expect(mockSetFormCompletion).toHaveBeenCalledWith({ completed: true }) + // completeAttempted only set if the complete is rejected due to compulsory data element operands + expect(mockSetCompleteAttempted).not.toHaveBeenCalled() + }) + + it('does not complete and shows error if the compulsoryFieldsCompleteOnly:true and there are compulsory data element operands without values', async () => { + mockIsComplete.mockReturnValue(false) + useDataSetId.mockReturnValue([ + 'data_set_id_compulsory_validation_with_cdeo', + ]) + useDataValueSet.mockReturnValue({ data: MOCK_DATA_INCOMPLETE }) + useMetadata.mockReturnValue({ data: MOCK_METADATA }) + const { getByText } = render() + + userEvent.click(getByText('Mark complete')) + expect(mockValidate).not.toHaveBeenCalled() + expect(mockSetFormCompletion).not.toHaveBeenCalled() + expect(mockSetCompleteAttempted).toHaveBeenCalledWith(true) + await waitFor(() => + expect(mockShow).toHaveBeenCalledWith( + 'Compulsory fields must be filled out before completing the form' + ) + ) + }) + + it('completes if the compulsoryFieldsCompleteOnly:true and there are compulsory data element operands but all have values', () => { + mockIsComplete.mockReturnValue(false) + useDataSetId.mockReturnValue([ + 'data_set_id_compulsory_validation_with_cdeo', + ]) + useDataValueSet.mockReturnValue({ data: MOCK_DATA }) + useMetadata.mockReturnValue({ data: MOCK_METADATA }) + const { getByText } = render() + + userEvent.click(getByText('Mark complete')) + expect(mockValidate).toHaveBeenCalledOnce() + expect(mockSetFormCompletion).toHaveBeenCalledWith({ completed: true }) + // completeAttempted only set if the complete is rejected due to compulsory data element operands + expect(mockSetCompleteAttempted).not.toHaveBeenCalled() + }) + + it('marks form as incomplete if form is completed', () => { + mockIsComplete.mockReturnValue(true) + useDataSetId.mockReturnValue(['data_set_id_1']) + useDataValueSet.mockReturnValue({ data: MOCK_DATA }) + useMetadata.mockReturnValue({ data: MOCK_METADATA }) + const { getByText } = render() + + userEvent.click(getByText('Mark incomplete')) + expect(mockValidate).not.toHaveBeenCalledOnce() + expect(mockSetFormCompletion).toHaveBeenCalledWith({ completed: false }) + }) +}) diff --git a/src/bottom-bar/use-on-complete-callback.js b/src/bottom-bar/use-on-complete-callback.js index f999616b8..16235204d 100644 --- a/src/bottom-bar/use-on-complete-callback.js +++ b/src/bottom-bar/use-on-complete-callback.js @@ -12,6 +12,8 @@ import { useSetFormCompletionMutation, useSetFormCompletionMutationKey, validationResultsSidebarId, + useHasCompulsoryDataElementOperandsToFillOut, + useEntryFormStore, } from '../shared/index.js' const validationFailedMessage = i18n.t( @@ -121,7 +123,9 @@ export default function useOnCompleteCallback() { const { offline } = useConnectionStatus() const { data: metadata } = useMetadata() const [dataSetId] = useDataSetId() - const dataSet = selectors.getDataSetById(metadata, dataSetId) + const dataSet = dataSetId + ? selectors.getDataSetById(metadata, dataSetId) + : {} const { validCompleteOnly } = dataSet const { show: showErrorAlert } = useAlert((message) => message, { critical: true, @@ -135,11 +139,26 @@ export default function useOnCompleteCallback() { useOnCompleteWhenValidNotRequiredClick() const onCompleteWithoutValidationClick = useOnCompleteWithoutValidationClick() + const hasCompulsoryDataElementOperandsToFillOut = + useHasCompulsoryDataElementOperandsToFillOut() + const setCompleteAttempted = useEntryFormStore( + (state) => state.setCompleteAttempted + ) return () => { let promise - if (isLoading && offline) { + if (hasCompulsoryDataElementOperandsToFillOut) { + setCompleteAttempted(true) + cancelCompletionMutation({ completedBoolean: false }) + promise = Promise.reject( + new Error( + i18n.t( + 'Compulsory fields must be filled out before completing the form' + ) + ) + ) + } else if (isLoading && offline) { cancelCompletionMutation() // No need to complete when the completion request // hasn't been sent yet due to being offline. diff --git a/src/context-selection/contextual-help-sidebar/cell.js b/src/context-selection/contextual-help-sidebar/cell.js index b6b2335fa..547ceed86 100644 --- a/src/context-selection/contextual-help-sidebar/cell.js +++ b/src/context-selection/contextual-help-sidebar/cell.js @@ -25,6 +25,9 @@ const Cell = ({ value, state }) => ( {state === 'SYNCED' && (
)} + {state === 'COMPULSORY' && ( +
*
+ )}
{state === 'HAS_COMMENT' && ( diff --git a/src/context-selection/contextual-help-sidebar/cell.module.css b/src/context-selection/contextual-help-sidebar/cell.module.css index 0354cce3e..833448878 100644 --- a/src/context-selection/contextual-help-sidebar/cell.module.css +++ b/src/context-selection/contextual-help-sidebar/cell.module.css @@ -10,6 +10,7 @@ .input { composes: densePadding from '../../data-workspace/inputs/inputs.module.css'; composes: alignToEnd from '../../data-workspace/inputs/inputs.module.css'; + padding-inline-end: 16px; outline: 1px solid var(--colors-grey400); } @@ -45,3 +46,7 @@ .bottomRightTriangle { composes: bottomRightTriangle from '../../data-workspace/data-entry-cell/data-entry-cell.module.css'; } + +.topRightAsterisk { + composes: topRightAsterisk from '../../data-workspace/data-entry-cell/data-entry-cell.module.css'; +} diff --git a/src/context-selection/contextual-help-sidebar/cells-legend.js b/src/context-selection/contextual-help-sidebar/cells-legend.js index ef945953a..10f038ca1 100644 --- a/src/context-selection/contextual-help-sidebar/cells-legend.js +++ b/src/context-selection/contextual-help-sidebar/cells-legend.js @@ -67,6 +67,12 @@ export default function CellsLegend() { name={i18n.t('Locked, not editable')} state="LOCKED" /> + + + ) } diff --git a/src/data-workspace/category-combo-table-body/category-combo-table-body.test.js b/src/data-workspace/category-combo-table-body/category-combo-table-body.test.js index 4070ee92c..fea40ac11 100644 --- a/src/data-workspace/category-combo-table-body/category-combo-table-body.test.js +++ b/src/data-workspace/category-combo-table-body/category-combo-table-body.test.js @@ -2,10 +2,21 @@ import { Table } from '@dhis2/ui' import { getAllByTestId, getByTestId, getByText } from '@testing-library/react' import React from 'react' import { useMetadata } from '../../shared/metadata/use-metadata.js' +import { useDataSetId } from '../../shared/use-context-selection/use-context-selection.js' import { render } from '../../test-utils/index.js' import { FinalFormWrapper } from '../final-form-wrapper.js' import { CategoryComboTableBody } from './category-combo-table-body.js' +jest.mock( + '../../shared/use-context-selection/use-context-selection.js', + () => ({ + ...jest.requireActual( + '../../shared/use-context-selection/use-context-selection.js' + ), + useDataSetId: jest.fn(), + }) +) + jest.mock('../../shared/metadata/use-metadata.js', () => ({ useMetadata: jest.fn(), })) @@ -453,6 +464,7 @@ const metadata = { describe('', () => { useMetadata.mockReturnValue({ data: metadata }) + useDataSetId.mockReturnValue(['dataSet1']) it('should render rows and columns based on the data elements and categorycombo', () => { const tableDataElements = [ diff --git a/src/data-workspace/data-entry-cell/data-entry-cell.module.css b/src/data-workspace/data-entry-cell/data-entry-cell.module.css index 873505a26..758a253a7 100644 --- a/src/data-workspace/data-entry-cell/data-entry-cell.module.css +++ b/src/data-workspace/data-entry-cell/data-entry-cell.module.css @@ -108,6 +108,11 @@ border-inline-start: 3px solid transparent; } +.topRightAsterisk { + color: var(--colors-grey600); + inline-size: 8px; +} + .locked { background-color: var(--colors-grey050); } diff --git a/src/data-workspace/data-entry-cell/inner-wrapper.js b/src/data-workspace/data-entry-cell/inner-wrapper.js index 5ca5b79b7..60651230b 100644 --- a/src/data-workspace/data-entry-cell/inner-wrapper.js +++ b/src/data-workspace/data-entry-cell/inner-wrapper.js @@ -10,12 +10,13 @@ import { useValueStore, useSyncErrorsStore, useEntryFormStore, + useIsCompulsoryDataElementOperand, } from '../../shared/index.js' import styles from './data-entry-cell.module.css' import { ValidationTooltip } from './validation-tooltip.js' /** Three dots or triangle in top-right corner of cell */ -const SyncStatusIndicator = ({ error, isLoading, isSynced }) => { +const SyncStatusIndicator = ({ error, isLoading, isSynced, isRequired }) => { let statusIcon = null if (isLoading) { statusIcon = @@ -23,6 +24,8 @@ const SyncStatusIndicator = ({ error, isLoading, isSynced }) => { statusIcon = } else if (isSynced) { statusIcon =
+ } else if (isRequired) { + statusIcon =
*
} return (
@@ -33,6 +36,7 @@ const SyncStatusIndicator = ({ error, isLoading, isSynced }) => { SyncStatusIndicator.propTypes = { error: PropTypes.object, isLoading: PropTypes.bool, + isRequired: PropTypes.bool, isSynced: PropTypes.bool, } @@ -65,6 +69,13 @@ export function InnerWrapper({ categoryOptionComboId: cocId, }) ) + const isRequired = useIsCompulsoryDataElementOperand({ + dataElementId: deId, + categoryOptionComboId: cocId, + }) + const completeAttempted = useEntryFormStore((state) => + state.getCompleteAttempted() + ) const { input: { value }, @@ -96,6 +107,7 @@ export function InnerWrapper({ (state) => state.clearErrorByDataValueParams ) const warning = useEntryFormStore((state) => state.getWarning(fieldname)) + const fieldErrorMessage = error ?? warning const errorMessage = @@ -109,16 +121,18 @@ export function InnerWrapper({ ) const valueSynced = data.lastSyncedValue === value - const showSynced = dirty && valueSynced + const showSynced = dirty && valueSynced && (!isRequired || !!value) // todo: maybe use mutation state to improve this style handling // see https://dhis2.atlassian.net/browse/TECH-1316 - const cellStateClassName = invalid - ? styles.invalid - : warning - ? styles.warning - : activeMutations === 0 && showSynced - ? styles.synced - : null + + const cellStateClassName = + invalid || (isRequired && !value && completeAttempted) + ? styles.invalid + : warning + ? styles.warning + : activeMutations === 0 && showSynced + ? styles.synced + : null // initalize lastSyncedValue useEffect( @@ -156,6 +170,7 @@ export function InnerWrapper({ > {children} 0} isSynced={showSynced} error={syncError} diff --git a/src/data-workspace/data-workspace.js b/src/data-workspace/data-workspace.js index 8c1a550ce..aff50d1e1 100644 --- a/src/data-workspace/data-workspace.js +++ b/src/data-workspace/data-workspace.js @@ -15,6 +15,7 @@ import { useIsValidSelection, useValueStore, dataValueSetQueryKey, + useEntryFormStore, } from '../shared/index.js' import styles from './data-workspace.module.css' import { EntryForm } from './entry-form.js' @@ -34,6 +35,10 @@ export const DataWorkspace = ({ selectionHasNoFormMessage }) => { updateStore(initialDataValuesFetch.data) }, [updateStore, initialDataValuesFetch.data]) + const setCompleteAttempted = useEntryFormStore( + (state) => state.setCompleteAttempted + ) + const isValidSelection = useIsValidSelection() const [dataSetId] = useDataSetId() // used to reset form-state when context-selection is changed @@ -55,8 +60,11 @@ export const DataWorkspace = ({ selectionHasNoFormMessage }) => { cancelRefetch: false, } ) + + // reset the completionAttempted store for new form + setCompleteAttempted(false) } - }, [validFormKey, queryClient]) + }, [validFormKey, queryClient, setCompleteAttempted]) if (selectionHasNoFormMessage) { const title = i18n.t('The current selection does not have a form') diff --git a/src/shared/completion/use-imperative-cancel-completion-mutation.js b/src/shared/completion/use-imperative-cancel-completion-mutation.js index 552f67393..da5bb5a77 100644 --- a/src/shared/completion/use-imperative-cancel-completion-mutation.js +++ b/src/shared/completion/use-imperative-cancel-completion-mutation.js @@ -8,7 +8,7 @@ export default function useImperativeCancelCompletionMutation() { const mutationKey = useSetFormCompletionMutationKey() const dataValueSetQueryKey = useDataValueSetQueryKey() - return () => { + return ({ completedBoolean } = {}) => { const foundMutation = mutationCache.find({ mutationKey }) if (!foundMutation) { @@ -30,7 +30,7 @@ export default function useImperativeCancelCompletionMutation() { ...previousDataValueSet, completeStatus: { ...previousDataValueSet.completeStatus, - complete: !completed, + complete: completedBoolean ?? !completed, }, }) ) diff --git a/src/shared/index.js b/src/shared/index.js index 97bae33ab..03439cdec 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -23,3 +23,7 @@ export * from './default-on-success.js' export * from './use-will-component-unmount.js' export * from './api-errors/index.js' export * from './use-org-unit/use-organisation-unit.js' +export { + useIsCompulsoryDataElementOperand, + useHasCompulsoryDataElementOperandsToFillOut, +} from './use-is-compulsory-data-element-operand.js' diff --git a/src/shared/metadata/selectors.js b/src/shared/metadata/selectors.js index bd56421e0..c6df21ace 100644 --- a/src/shared/metadata/selectors.js +++ b/src/shared/metadata/selectors.js @@ -29,6 +29,8 @@ export const getIndicators = (metadata) => metadata.indicators export const getDataSets = (metadata) => metadata.dataSets export const getSections = (metadata) => metadata.sections export const getOptionSets = (metadata) => metadata.optionSets +export const getCompulsoryDataElementOperands = (metadata) => + metadata.compulsoryDataElementOperands // Select by id export const getCategoryById = (metadata, id) => getCategories(metadata)[id] @@ -116,6 +118,24 @@ export const getCategoryOptionsByCategoryOptionComboId = createCachedSelector( } )((_, categoryOptionComboId) => categoryOptionComboId) +/** + * @param {*} metadata + */ +export const getCompulsoryDataElementOperandsSet = createCachedSelector( + getDataSetById, + (dataSet) => { + if (!dataSet || !dataSet.compulsoryDataElementOperands) { + return new Set() + } + return new Set( + dataSet.compulsoryDataElementOperands?.map( + (operand) => + `${operand?.dataElement?.id}.${operand?.categoryOptionCombo?.id}` + ) + ) + } +)((_, dataSetId) => dataSetId) + /** * @param {*} metadata * @param {string} dataSetId diff --git a/src/shared/stores/entry-form-store.js b/src/shared/stores/entry-form-store.js index 1a51e480d..e3ac81f2f 100644 --- a/src/shared/stores/entry-form-store.js +++ b/src/shared/stores/entry-form-store.js @@ -4,6 +4,7 @@ import create from 'zustand' const inititalState = { errors: {}, warnings: {}, + completeAttempted: false, } export const useEntryFormStore = create((set, get) => ({ @@ -19,6 +20,8 @@ export const useEntryFormStore = create((set, get) => ({ const newWarnings = setIn(warnings, fieldname, warning) ?? {} set({ warnings: newWarnings }) }, + getCompleteAttempted: () => get().completeAttempted, + setCompleteAttempted: (bool) => set({ completeAttempted: bool }), // could add getNumberOfWarnings if needed })) diff --git a/src/shared/use-is-compulsory-data-element-operand.js b/src/shared/use-is-compulsory-data-element-operand.js new file mode 100644 index 000000000..164de9165 --- /dev/null +++ b/src/shared/use-is-compulsory-data-element-operand.js @@ -0,0 +1,57 @@ +import { useMemo } from 'react' +import { useMetadata, selectors } from './metadata/index.js' +import { useDataSetId } from './use-context-selection/use-context-selection.js' +import { useDataValueSet } from './use-data-value-set/use-data-value-set.js' + +export const useIsCompulsoryDataElementOperand = ({ + dataElementId, + categoryOptionComboId, +}) => { + const { data: metadata } = useMetadata() + const [dataSetId] = useDataSetId() + if (!dataSetId) { + return false + } + const compulsoryDataElementOperandsSet = + selectors.getCompulsoryDataElementOperandsSet(metadata, dataSetId) + return compulsoryDataElementOperandsSet.has( + `${dataElementId}.${categoryOptionComboId}` + ) +} + +export const useHasCompulsoryDataElementOperandsToFillOut = () => { + const { data } = useDataValueSet() + + const { data: metadata } = useMetadata() + const [dataSetId] = useDataSetId() + const hasCompulsoryDataElementOperandsToFillOut = useMemo(() => { + if (!dataSetId) { + return false + } + const dataSet = selectors.getDataSetById(metadata, dataSetId) + + const { compulsoryFieldsCompleteOnly } = dataSet || {} + + if (!compulsoryFieldsCompleteOnly) { + return false + } + + const compulsoryDataElementOperandsSet = + selectors.getCompulsoryDataElementOperandsSet(metadata, dataSetId) + + let hasEmptyCompulsoryDataElementOperands = false + for (const operand of compulsoryDataElementOperandsSet) { + const [dataElementId, categoryOptionComboId] = operand.split('.') + if ( + !data?.dataValues?.[dataElementId]?.[categoryOptionComboId] + ?.value + ) { + hasEmptyCompulsoryDataElementOperands = true + break + } + } + return hasEmptyCompulsoryDataElementOperands + }, [data?.dataValues, dataSetId, metadata]) + + return hasCompulsoryDataElementOperandsToFillOut +} diff --git a/src/shared/validation/index.js b/src/shared/validation/index.js index 5bb9ed80d..c43dc1897 100644 --- a/src/shared/validation/index.js +++ b/src/shared/validation/index.js @@ -1,5 +1,5 @@ export { default as buildValidationResult } from './build-validation-result.js' export { isInteger } from './is-integer.js' export * from './query-key-factory.js' -export { default as useImperativeValidate } from './use-imperative-validate.js' +export { useImperativeValidate } from './use-imperative-validate.js' export { default as useValidationResult } from './use-validation-result.js' diff --git a/src/shared/validation/use-imperative-validate.js b/src/shared/validation/use-imperative-validate.js index 518833eaf..f4cd92562 100644 --- a/src/shared/validation/use-imperative-validate.js +++ b/src/shared/validation/use-imperative-validate.js @@ -12,7 +12,7 @@ import { * @params {Object} options * @params {Boolean} options.enabled - Defaults to `true` **/ -export default function useImperativeValidate() { +export const useImperativeValidate = () => { const client = useQueryClient() const [{ dataSetId, orgUnitId, periodId }] = useContextSelection() const { diff --git a/src/shared/validation/use-imperative-validate.test.js b/src/shared/validation/use-imperative-validate.test.js index 54bcc3876..bb2af9317 100644 --- a/src/shared/validation/use-imperative-validate.test.js +++ b/src/shared/validation/use-imperative-validate.test.js @@ -2,7 +2,7 @@ import { useQueryClient } from '@tanstack/react-query' import { renderHook } from '@testing-library/react-hooks' import React from 'react' import { Wrapper } from '../../test-utils/index.js' -import useImperativeValidate from './use-imperative-validate.js' +import { useImperativeValidate } from './use-imperative-validate.js' jest.mock('@tanstack/react-query', () => { const originalModule = jest.requireActual('@tanstack/react-query')