From cc4ec25a14565aff31545fa659db39093e436055 Mon Sep 17 00:00:00 2001 From: Jamil Date: Fri, 6 Sep 2024 16:28:10 +0600 Subject: [PATCH] fix: fetch custom validators before deserializing (#7580) * fix: fetch custom validators before deserializing * chore: update changelog * chore: remove unused import * test: fix unit test that calls `initValidators()` * test: fix unit tests * test: call `crateStore()` directly to avoid `flushPromises()` --- CHANGELOG.md | 4 + .../declarations/submissionMiddleware.test.ts | 9 +- packages/client/src/forms/register/reducer.ts | 17 ++- .../src/forms/register/reviewReducer.ts | 17 ++- packages/client/src/offline/actions.ts | 15 +++ packages/client/src/tests/util.tsx | 6 +- .../CorrectionForm/CorrectionSummary.test.tsx | 3 +- .../ReviewCertificateAction.test.tsx | 7 +- .../views/RegisterForm/ReviewForm.test.tsx | 121 ++++++++++-------- .../review/ReviewSection.test.tsx | 1 + .../SysAdmin/Config/Systems/Systems.test.tsx | 4 + .../StatusWiseDeclarationCountView.test.tsx | 2 + .../SysAdmin/Team/user/UserList.test.tsx | 6 +- .../user/userCreation/CreateNewUser.test.tsx | 5 +- .../user/userCreation/SignatureForm.test.tsx | 3 +- .../Team/user/userCreation/UserForm.test.tsx | 4 +- 16 files changed, 150 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c93c316d56..62cbfc9b9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,10 @@ - Internally we were storing the `family` name field as a required property which was limiting what how you could capture the name of a person in the forms. Now we are storing it as an optional property which would make more flexible. +## Bug fixes + +- Custom form field validators from country config will work offline. [#7478](https://github.com/opencrvs/opencrvs-core/issues/7478) + ### Breaking changes - **Gateways searchEvents API updated** `operationHistories` only returns `operationType` & `operatedOn` due to the other fields being unused in OpenCRVS diff --git a/packages/client/src/declarations/submissionMiddleware.test.ts b/packages/client/src/declarations/submissionMiddleware.test.ts index fa330e8d70..a74b063b0a 100644 --- a/packages/client/src/declarations/submissionMiddleware.test.ts +++ b/packages/client/src/declarations/submissionMiddleware.test.ts @@ -12,8 +12,8 @@ import { ApolloError } from '@apollo/client' import { SubmissionAction } from '@client/forms' import { ACTION_STATUS_MAP, - createTestStore, - mockDeclarationData + mockDeclarationData, + mockOfflineDataDispatch } from '@client/tests/util' import { createClient } from '@client/utils/apolloClient' import { Event } from '@client/utils/gateway' @@ -24,6 +24,8 @@ import { declarationReadyForStatusChange, submissionMiddleware } from './submissionMiddleware' +import { createStore } from '@client/store' +import { offlineDataReady } from '@client/offline/actions' describe('Submission middleware', () => { const dispatch = vi.fn() @@ -37,7 +39,8 @@ describe('Submission middleware', () => { })(next) beforeEach(async () => { - const { store } = await createTestStore() + const { store } = createStore() + store.dispatch(offlineDataReady(mockOfflineDataDispatch)) const client = createClient(store) getState.mockImplementation(() => store.getState()) mutateSpy = vi diff --git a/packages/client/src/forms/register/reducer.ts b/packages/client/src/forms/register/reducer.ts index 286e9a1a0c..667a6cfd0d 100644 --- a/packages/client/src/forms/register/reducer.ts +++ b/packages/client/src/forms/register/reducer.ts @@ -8,11 +8,11 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { LoopReducer, Loop } from 'redux-loop' +import { LoopReducer, Loop, Cmd, loop } from 'redux-loop' import { IForm } from '@client/forms' import * as offlineActions from '@client/offline/actions' import { deserializeForm } from '@client/forms/deserializer/deserializer' -import { validators } from '@client/forms/validators' +import { initValidators, validators } from '@client/forms/validators' export type IRegisterFormState = | { @@ -46,6 +46,19 @@ export const registerFormReducer: LoopReducer = ( switch (action.type) { case offlineActions.READY: case offlineActions.FORMS_LOADED: + return loop( + state, + Cmd.run( + async () => { + await initValidators() + return action.payload + }, + { + successActionCreator: offlineActions.CustomValidatorsSuccess + } + ) + ) + case offlineActions.CUSTOM_VALIDATORS_LOADED: const { forms } = action.payload const birth = deserializeForm(forms.birth, validators) diff --git a/packages/client/src/forms/register/reviewReducer.ts b/packages/client/src/forms/register/reviewReducer.ts index e6f7d835c1..3cf7efcd5e 100644 --- a/packages/client/src/forms/register/reviewReducer.ts +++ b/packages/client/src/forms/register/reviewReducer.ts @@ -8,11 +8,11 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { LoopReducer, Loop } from 'redux-loop' +import { LoopReducer, Loop, loop, Cmd } from 'redux-loop' import { IForm } from '@client/forms' import * as offlineActions from '@client/offline/actions' import { deserializeForm } from '@client/forms/deserializer/deserializer' -import { validators } from '@client/forms/validators' +import { initValidators, validators } from '@client/forms/validators' export type IReviewFormState = | { @@ -46,6 +46,19 @@ export const reviewReducer: LoopReducer = ( switch (action.type) { case offlineActions.READY: case offlineActions.FORMS_LOADED: + return loop( + state, + Cmd.run( + async () => { + await initValidators() + return action.payload + }, + { + successActionCreator: offlineActions.CustomValidatorsSuccess + } + ) + ) + case offlineActions.CUSTOM_VALIDATORS_LOADED: const { forms } = action.payload const birth = deserializeForm(forms.birth, validators) diff --git a/packages/client/src/offline/actions.ts b/packages/client/src/offline/actions.ts index c9ac57a36e..a6d2398d83 100644 --- a/packages/client/src/offline/actions.ts +++ b/packages/client/src/offline/actions.ts @@ -68,6 +68,12 @@ export type FormsLoadedAction = { payload: LoadFormsResponse } +export const CUSTOM_VALIDATORS_LOADED = 'OFFLINE/CUSTOM_VALIDATORS_LOADED' +export type CustomValidatorsLoadedLoadedAction = { + type: typeof CUSTOM_VALIDATORS_LOADED + payload: LoadFormsResponse +} + export const FORMS_FAILED = 'OFFLINE/FORMS_FAILED' export type FormsFailedAction = { type: typeof FORMS_FAILED @@ -188,6 +194,14 @@ export const formsLoaded = (payload: LoadFormsResponse): FormsLoadedAction => ({ payload: payload }) +export const CustomValidatorsSuccess = ( + forms: LoadFormsResponse +): CustomValidatorsLoadedLoadedAction => { + return { + type: CUSTOM_VALIDATORS_LOADED, + payload: forms + } +} export const formsFailed = (error: Error): FormsFailedAction => ({ type: FORMS_FAILED, payload: error @@ -333,6 +347,7 @@ export type Action = | LocationsLoadedAction | FormsFailedAction | FormsLoadedAction + | CustomValidatorsLoadedLoadedAction | SetOfflineData | IGetOfflineDataSuccessAction | IGetOfflineDataFailedAction diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index dc25980603..c95670ce67 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -848,7 +848,8 @@ export const mockOfflineDataDispatch = { export async function createTestStore() { const { store, history } = createStore() - await store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() // This is to resolve the `referenceApi.importValidators()` promise return { store, history } } @@ -867,7 +868,8 @@ export async function createTestComponent( }, options?: MountRendererProps ) { - await store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() // This is to resolve the `referenceApi.importValidators()` promise const withGraphQL = (node: JSX.Element) => { if (apolloClient) { diff --git a/packages/client/src/views/CorrectionForm/CorrectionSummary.test.tsx b/packages/client/src/views/CorrectionForm/CorrectionSummary.test.tsx index 3d4023bc0d..be360c69fe 100644 --- a/packages/client/src/views/CorrectionForm/CorrectionSummary.test.tsx +++ b/packages/client/src/views/CorrectionForm/CorrectionSummary.test.tsx @@ -170,8 +170,9 @@ const { store, history } = createStore() describe('Correction summary', () => { describe('for a birth declaration', () => { beforeEach(async () => { - store.dispatch(storeDeclaration(birthDeclaration)) store.dispatch(getOfflineDataSuccess(JSON.stringify(mockOfflineData))) + await flushPromises() + store.dispatch(storeDeclaration(birthDeclaration)) const form = await getRegisterFormFromStore(store, Event.Birth) wrapper = await createTestComponent( { loginAsFieldAgent(store) - // @ts-ignore - store.dispatch(storeDeclaration(deathDeclaration)) - const component = await createTestComponent(, { store, history }) + // @ts-ignore + store.dispatch(storeDeclaration(deathDeclaration)) + component.update() + const confirmBtn = component.find('#confirm-print') const confirmBtnExist = !!confirmBtn.hostNodes().length expect(confirmBtnExist).toBe(true) diff --git a/packages/client/src/views/RegisterForm/ReviewForm.test.tsx b/packages/client/src/views/RegisterForm/ReviewForm.test.tsx index 640c3f149e..69c3a20ef4 100644 --- a/packages/client/src/views/RegisterForm/ReviewForm.test.tsx +++ b/packages/client/src/views/RegisterForm/ReviewForm.test.tsx @@ -271,7 +271,6 @@ describe('ReviewForm tests', () => { }) it('Shared contact phone number should be set properly', async () => { - store.dispatch(storeDeclaration(birthDeclaration)) const testComponent = await createTestComponent( { { store, history } ) + store.dispatch(storeDeclaration(birthDeclaration)) testComponent.update() const data = testComponent .find(RegisterForm) @@ -308,7 +308,6 @@ describe('ReviewForm tests', () => { ).toBe('+8801711111111') }) it('when registration has attachment', async () => { - store.dispatch(storeDeclaration(birthDeclaration)) const testComponent = await createTestComponent( { { store, history } ) + store.dispatch(storeDeclaration(birthDeclaration)) testComponent.update() const data = testComponent @@ -350,7 +350,6 @@ describe('ReviewForm tests', () => { ]) }) it('check registration', async () => { - store.dispatch(storeDeclaration(birthDeclaration)) const testComponent = await createTestComponent( { { store, history } ) + store.dispatch(storeDeclaration(birthDeclaration)) testComponent.update() const data = testComponent @@ -412,16 +412,6 @@ describe('ReviewForm tests', () => { Event.Birth, 'IN_PROGRESS' ) - store.dispatch( - getStorageDeclarationsSuccess( - JSON.stringify({ - userID: 'currentUser', // mock - drafts: [declaration], - declarations: [] - }) - ) - ) - store.dispatch(storeDeclaration(declaration)) const testComponent = await createTestComponent( { />, { store, history } ) + store.dispatch( + getStorageDeclarationsSuccess( + JSON.stringify({ + userID: 'currentUser', // mock + drafts: [declaration], + declarations: [] + }) + ) + ) + store.dispatch(storeDeclaration(declaration)) + testComponent.update() testComponent.find('#exit-btn').hostNodes().simulate('click') testComponent.update() expect(window.location.href).toContain('/progress') @@ -458,16 +459,6 @@ describe('ReviewForm tests', () => { Event.Birth, 'DECLARED' ) - store.dispatch( - getStorageDeclarationsSuccess( - JSON.stringify({ - userID: 'currentUser', // mock - drafts: [declaration], - declarations: [] - }) - ) - ) - store.dispatch(storeDeclaration(declaration)) const testComponent = await createTestComponent( { />, { store, history } ) + + store.dispatch( + getStorageDeclarationsSuccess( + JSON.stringify({ + userID: 'currentUser', // mock + drafts: [declaration], + declarations: [] + }) + ) + ) + store.dispatch(storeDeclaration(declaration)) + testComponent.update() testComponent.find('#exit-btn').hostNodes().simulate('click') testComponent.update() expect(window.location.href).toContain(WORKQUEUE_TABS.readyForReview) @@ -504,16 +507,6 @@ describe('ReviewForm tests', () => { Event.Birth, 'VALIDATED' ) - store.dispatch( - getStorageDeclarationsSuccess( - JSON.stringify({ - userID: 'currentUser', // mock - drafts: [declaration], - declarations: [] - }) - ) - ) - store.dispatch(storeDeclaration(declaration)) const testComponent = await createTestComponent( { />, { store, history } ) + + store.dispatch( + getStorageDeclarationsSuccess( + JSON.stringify({ + userID: 'currentUser', // mock + drafts: [declaration], + declarations: [] + }) + ) + ) + store.dispatch(storeDeclaration(declaration)) + testComponent.update() testComponent.find('#exit-btn').hostNodes().simulate('click') testComponent.update() expect(window.location.href).toContain(WORKQUEUE_TABS.readyForReview) @@ -550,16 +555,6 @@ describe('ReviewForm tests', () => { Event.Birth, 'REJECTED' ) - store.dispatch( - getStorageDeclarationsSuccess( - JSON.stringify({ - userID: 'currentUser', // mock - drafts: [declaration], - declarations: [] - }) - ) - ) - store.dispatch(storeDeclaration(declaration)) const testComponent = await createTestComponent( { />, { store, history } ) - testComponent.find('#exit-btn').hostNodes().simulate('click') - testComponent.update() - expect(window.location.href).toContain(WORKQUEUE_TABS.requiresUpdate) - }) - it('redirect to progress tab when exit button is clicked', async () => { - const declaration = createReviewDeclaration( - uuid(), - birthDraftData, - Event.Birth - ) store.dispatch( getStorageDeclarationsSuccess( JSON.stringify({ @@ -605,6 +590,18 @@ describe('ReviewForm tests', () => { ) ) store.dispatch(storeDeclaration(declaration)) + testComponent.update() + testComponent.find('#exit-btn').hostNodes().simulate('click') + testComponent.update() + expect(window.location.href).toContain(WORKQUEUE_TABS.requiresUpdate) + }) + + it('redirect to progress tab when exit button is clicked', async () => { + const declaration = createReviewDeclaration( + uuid(), + birthDraftData, + Event.Birth + ) const testComponent = await createTestComponent( { />, { store, history } ) + + store.dispatch( + getStorageDeclarationsSuccess( + JSON.stringify({ + userID: 'currentUser', // mock + drafts: [declaration], + declarations: [] + }) + ) + ) + store.dispatch(storeDeclaration(declaration)) + testComponent.update() testComponent.find('#exit-btn').hostNodes().simulate('click') testComponent.update() expect(window.location.href).toContain('/progress') @@ -649,7 +658,6 @@ describe('ReviewForm tests', () => { }) ) ) - store.dispatch(storeDeclaration(declaration)) const testComponent = await createTestComponent( { { store, history } ) + store.dispatch(storeDeclaration(declaration)) testComponent.update() const data = testComponent .find(RegisterForm) @@ -684,7 +693,6 @@ describe('ReviewForm tests', () => { describe('Death review flow', () => { it('it returns death registration', async () => { - store.dispatch(storeDeclaration(deathDeclaration)) const testComponent = await createTestComponent( { { store, history } ) + store.dispatch(storeDeclaration(deathDeclaration)) testComponent.update() const data = testComponent .find(RegisterForm) @@ -730,7 +739,6 @@ describe('ReviewForm tests', () => { ) }) it('populates proper death event section', async () => { - store.dispatch(storeDeclaration(deathDeclaration)) const form = await getReviewFormFromStore(store, Event.Death) const testComponent = await createTestComponent( { { store, history } ) + store.dispatch(storeDeclaration(deathDeclaration)) testComponent.update() const data = testComponent .find(RegisterForm) diff --git a/packages/client/src/views/RegisterForm/review/ReviewSection.test.tsx b/packages/client/src/views/RegisterForm/review/ReviewSection.test.tsx index b48989d554..a8c6725420 100644 --- a/packages/client/src/views/RegisterForm/review/ReviewSection.test.tsx +++ b/packages/client/src/views/RegisterForm/review/ReviewSection.test.tsx @@ -100,6 +100,7 @@ describe('when in device of large viewport', () => { beforeEach(async () => { store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() form = await getRegisterFormFromStore(store, DeclarationEvent.Birth) userAgentMock = vi.spyOn(window.navigator, 'userAgent', 'get') Object.assign(window, { outerWidth: 1034 }) diff --git a/packages/client/src/views/SysAdmin/Config/Systems/Systems.test.tsx b/packages/client/src/views/SysAdmin/Config/Systems/Systems.test.tsx index 64554712f1..fb8831a122 100644 --- a/packages/client/src/views/SysAdmin/Config/Systems/Systems.test.tsx +++ b/packages/client/src/views/SysAdmin/Config/Systems/Systems.test.tsx @@ -14,6 +14,7 @@ import { ReactWrapper } from 'enzyme' import { createStore } from '@client/store' import { createTestComponent, + flushPromises, mockOfflineDataDispatch, selectOption } from '@client/tests/util' @@ -235,6 +236,7 @@ describe('render toggle settings', () => { beforeEach(async () => { const { store, history } = createStore() store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() const mocks = [ { @@ -282,6 +284,7 @@ describe('render toggle settings', () => { beforeEach(async () => { const { store, history } = createStore() store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() const mocks = [ { @@ -332,6 +335,7 @@ describe('render toggle settings', () => { beforeEach(async () => { const { store, history } = createStore() store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() const mocks = [ { diff --git a/packages/client/src/views/SysAdmin/Performance/reports/operational/StatusWiseDeclarationCountView.test.tsx b/packages/client/src/views/SysAdmin/Performance/reports/operational/StatusWiseDeclarationCountView.test.tsx index c3a224c637..ec9fbf29c3 100644 --- a/packages/client/src/views/SysAdmin/Performance/reports/operational/StatusWiseDeclarationCountView.test.tsx +++ b/packages/client/src/views/SysAdmin/Performance/reports/operational/StatusWiseDeclarationCountView.test.tsx @@ -11,6 +11,7 @@ import { createTestComponent, createTestStore, + flushPromises, mockOfflineDataDispatch, mockRegistrarUserResponse } from '@client/tests/util' @@ -49,6 +50,7 @@ describe('Status wise registration count', () => { getItem.mockReturnValue(registerScopeToken) await store.dispatch(checkAuth()) store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() vi.spyOn(locationUtils, 'getJurisidictionType').mockReturnValue('UNION') vi.spyOn(performanceUtils, 'isUnderJurisdictionOfUser').mockReturnValue( true diff --git a/packages/client/src/views/SysAdmin/Team/user/UserList.test.tsx b/packages/client/src/views/SysAdmin/Team/user/UserList.test.tsx index 082c3c8acc..beadadb8ed 100644 --- a/packages/client/src/views/SysAdmin/Team/user/UserList.test.tsx +++ b/packages/client/src/views/SysAdmin/Team/user/UserList.test.tsx @@ -40,7 +40,8 @@ describe('user list without admin scope', () => { payload: mockUserResponse } await store.dispatch(action) - await store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() const userListMock = [ { @@ -91,7 +92,8 @@ describe('User list tests', () => { payload: mockLocalSysAdminUserResponse } await store.dispatch(action) - await store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() }) describe('Header test', () => { diff --git a/packages/client/src/views/SysAdmin/Team/user/userCreation/CreateNewUser.test.tsx b/packages/client/src/views/SysAdmin/Team/user/userCreation/CreateNewUser.test.tsx index 312c093c63..e4e0b8988f 100644 --- a/packages/client/src/views/SysAdmin/Team/user/userCreation/CreateNewUser.test.tsx +++ b/packages/client/src/views/SysAdmin/Team/user/userCreation/CreateNewUser.test.tsx @@ -196,6 +196,7 @@ describe('create new user tests', () => { store = s.store history = s.history store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() }) describe('when user is in create new user form', () => { @@ -260,6 +261,7 @@ describe('create new user tests', () => { describe('when user in review page', () => { beforeEach(async () => { store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() store.dispatch(modifyUserFormData(mockCompleteFormData)) testComponent = await createTestComponent( // @ts-ignore @@ -365,10 +367,11 @@ describe('edit user tests', () => { } ] - beforeEach(() => { + beforeEach(async () => { ;(roleQueries.fetchRoles as Mock).mockReturnValue(mockRoles) ;(userQueries.searchUsers as Mock).mockReturnValue(mockUsers) store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() }) it('check user role update', async () => { diff --git a/packages/client/src/views/SysAdmin/Team/user/userCreation/SignatureForm.test.tsx b/packages/client/src/views/SysAdmin/Team/user/userCreation/SignatureForm.test.tsx index 9fc1136cbc..326f979a0a 100644 --- a/packages/client/src/views/SysAdmin/Team/user/userCreation/SignatureForm.test.tsx +++ b/packages/client/src/views/SysAdmin/Team/user/userCreation/SignatureForm.test.tsx @@ -39,7 +39,8 @@ describe('signature upload tests', () => { beforeEach(async () => { ;(roleQueries.fetchRoles as Mock).mockReturnValue(mockRoles) - await store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() }) describe('when user is in signature upload form page', () => { diff --git a/packages/client/src/views/SysAdmin/Team/user/userCreation/UserForm.test.tsx b/packages/client/src/views/SysAdmin/Team/user/userCreation/UserForm.test.tsx index d2828cf663..964c009653 100644 --- a/packages/client/src/views/SysAdmin/Team/user/userCreation/UserForm.test.tsx +++ b/packages/client/src/views/SysAdmin/Team/user/userCreation/UserForm.test.tsx @@ -13,6 +13,7 @@ import { offlineDataReady } from '@client/offline/actions' import { createStore } from '@client/store' import { createTestComponent, + flushPromises, mockOfflineData, mockOfflineDataDispatch } from '@client/tests/util' @@ -28,7 +29,8 @@ const { store, history } = createStore() describe('Create new user page tests', () => { let component: ReactWrapper beforeEach(async () => { - await store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + store.dispatch(offlineDataReady(mockOfflineDataDispatch)) + await flushPromises() const testComponent = await createTestComponent( // @ts-ignore