From 54d0ef39976c216fbf9538955602f866465d1ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Genevi=C3=A8ve=20Bastien?= Date: Fri, 20 Mar 2026 08:55:04 -0400 Subject: [PATCH 1/3] dep: upgrade prettier to 3.8.1 Upgrade the version of `prettier` package from 3.3.3 to 3.8.1. This gives access to `objectWrap` rule, introduced in 3.5, with the `collapse` option that fits objects to a single line when possible. Also, introduced in 3.6, there is the `@noprettier` or `@noformat` that can be added at the top of a file to ignore a specific file when formatting, which can be useful for mocks or test files. --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index f54af9fb6..58efcf436 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12141,9 +12141,9 @@ prettier-eslint-cli@^8.0.1: yargs "^17.7.2" prettier@^3.0.1: - version "3.3.3" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" - integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + version "3.8.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" + integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== pretty-error@^4.0.0: version "4.0.0" From 6eca899f23d97e19965e3ddef6d8d7a44cddd143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Genevi=C3=A8ve=20Bastien?= Date: Fri, 20 Mar 2026 08:58:01 -0400 Subject: [PATCH 2/3] eslint: add rules specific for tests ** To be documented when we agree ** --- packages/evolution-backend/.eslintignore | 3 +-- packages/evolution-backend/.eslintrc.json | 12 +++++++++++- packages/evolution-backend/.prettierrc.js | 13 ++++++++++++- packages/evolution-common/.eslintignore | 3 +-- packages/evolution-common/.eslintrc.json | 12 +++++++++++- packages/evolution-common/.prettierrc.js | 13 ++++++++++++- packages/evolution-frontend/.eslintignore | 1 - packages/evolution-frontend/.eslintrc.json | 12 +++++++++++- packages/evolution-frontend/.prettierrc.js | 13 ++++++++++++- 9 files changed, 71 insertions(+), 11 deletions(-) diff --git a/packages/evolution-backend/.eslintignore b/packages/evolution-backend/.eslintignore index 7c1192c28..46f10721d 100644 --- a/packages/evolution-backend/.eslintignore +++ b/packages/evolution-backend/.eslintignore @@ -1,3 +1,2 @@ lib/ -node_modules/ -**/__tests__ \ No newline at end of file +node_modules/ \ No newline at end of file diff --git a/packages/evolution-backend/.eslintrc.json b/packages/evolution-backend/.eslintrc.json index 94b05bb83..57c967191 100644 --- a/packages/evolution-backend/.eslintrc.json +++ b/packages/evolution-backend/.eslintrc.json @@ -1,3 +1,13 @@ { - "extends": "../../configs/base.eslintrc.json" + "extends": "../../configs/base.eslintrc.json", + "overrides": [ + { + "files": ["**/__tests__/**/*.{ts,tsx}"], + "rules": { + "@typescript-eslint/no-unused-vars": ["warn", { "vars": "all", "args": "after-used", "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "ignoreRestSiblings": false }], + // Some imports for testing purposes are devDependencies and not published + "n/no-unpublished-import": "off" + } + } + ] } diff --git a/packages/evolution-backend/.prettierrc.js b/packages/evolution-backend/.prettierrc.js index a436a1cd2..eb3f51fbc 100644 --- a/packages/evolution-backend/.prettierrc.js +++ b/packages/evolution-backend/.prettierrc.js @@ -5,5 +5,16 @@ * License text available at https://opensource.org/licenses/MIT */ module.exports = { - ...require('../../configs/base.prettierrc') + ...require('../../configs/base.prettierrc'), + // FIXME Move to the base config when we agree on it. + overrides: [ + { + files: ['**/__tests__/*.{ts,tsx}', '**/*.{test,spec}.{ts,tsx}'], + options: { + // For test files, we allow longer lines and do not break objects (e.g. expected values) into multiple lines + printWidth: 150, + objectWrap: 'collapse' + } + } + ] } diff --git a/packages/evolution-common/.eslintignore b/packages/evolution-common/.eslintignore index 7c1192c28..46f10721d 100644 --- a/packages/evolution-common/.eslintignore +++ b/packages/evolution-common/.eslintignore @@ -1,3 +1,2 @@ lib/ -node_modules/ -**/__tests__ \ No newline at end of file +node_modules/ \ No newline at end of file diff --git a/packages/evolution-common/.eslintrc.json b/packages/evolution-common/.eslintrc.json index 94b05bb83..1abf0fb15 100644 --- a/packages/evolution-common/.eslintrc.json +++ b/packages/evolution-common/.eslintrc.json @@ -1,3 +1,13 @@ { - "extends": "../../configs/base.eslintrc.json" + "extends": "../../configs/base.eslintrc.json", + "overrides": [ + { + "files": ["**/__tests__/**/*.{js,jsx,ts,tsx}"], + "rules": { + "@typescript-eslint/no-unused-vars": ["warn", { "vars": "all", "args": "after-used", "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "ignoreRestSiblings": false }], + // Some imports for testing purposes are devDependencies and not published + "n/no-unpublished-import": "off" + } + } + ] } diff --git a/packages/evolution-common/.prettierrc.js b/packages/evolution-common/.prettierrc.js index a436a1cd2..eb3f51fbc 100644 --- a/packages/evolution-common/.prettierrc.js +++ b/packages/evolution-common/.prettierrc.js @@ -5,5 +5,16 @@ * License text available at https://opensource.org/licenses/MIT */ module.exports = { - ...require('../../configs/base.prettierrc') + ...require('../../configs/base.prettierrc'), + // FIXME Move to the base config when we agree on it. + overrides: [ + { + files: ['**/__tests__/*.{ts,tsx}', '**/*.{test,spec}.{ts,tsx}'], + options: { + // For test files, we allow longer lines and do not break objects (e.g. expected values) into multiple lines + printWidth: 150, + objectWrap: 'collapse' + } + } + ] } diff --git a/packages/evolution-frontend/.eslintignore b/packages/evolution-frontend/.eslintignore index c5f48bedd..7856bb51c 100644 --- a/packages/evolution-frontend/.eslintignore +++ b/packages/evolution-frontend/.eslintignore @@ -1,6 +1,5 @@ lib/ node_modules/ -**/__tests__ jestSetup.ts // The webpack file is not used yet, so it's ignored for now, but it should not be and it should be typescript webpack.config.js diff --git a/packages/evolution-frontend/.eslintrc.json b/packages/evolution-frontend/.eslintrc.json index c28084674..b4ba59145 100644 --- a/packages/evolution-frontend/.eslintrc.json +++ b/packages/evolution-frontend/.eslintrc.json @@ -1,3 +1,13 @@ { - "extends": "../../configs/react.eslintrc.json" + "extends": "../../configs/react.eslintrc.json", + "overrides": [ + { + "files": ["**/__tests__/**/*.{ts,tsx}"], + "rules": { + "@typescript-eslint/no-unused-vars": ["warn", { "vars": "all", "args": "after-used", "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "ignoreRestSiblings": false }], + // Some imports for testing purposes are devDependencies and not published + "n/no-unpublished-import": "off" + } + } + ] } \ No newline at end of file diff --git a/packages/evolution-frontend/.prettierrc.js b/packages/evolution-frontend/.prettierrc.js index ddd28132b..0c9251461 100644 --- a/packages/evolution-frontend/.prettierrc.js +++ b/packages/evolution-frontend/.prettierrc.js @@ -5,6 +5,17 @@ * License text available at https://opensource.org/licenses/MIT */ module.exports = { - ...require('../../configs/base.prettierrc') + ...require('../../configs/base.prettierrc'), + // FIXME Move to the base config when we agree on it. + overrides: [ + { + files: ['**/__tests__/*.{ts,tsx}', '**/*.{test,spec}.{ts,tsx}'], + options: { + // For test files, we allow longer lines and do not break objects (e.g. expected values) into multiple lines + printWidth: 150, + objectWrap: 'collapse' + } + } + ] } \ No newline at end of file From 6f100cfc8d790635cab93cbe32c182a63b02ea66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Genevi=C3=A8ve=20Bastien?= Date: Fri, 20 Mar 2026 14:18:47 -0400 Subject: [PATCH 3/3] Formating changes to test files --- .../src/api/__tests__/auth.routes.test.ts | 69 +- .../survey.participant.routes.test.ts | 60 +- .../api/__tests__/survey.user.routes.test.ts | 34 +- .../admin/__tests__/exports.routes.test.ts | 53 +- .../__tests__/validateUuidMiddleware.test.ts | 9 +- .../participant/__tests__/serverApp.test.ts | 93 +- .../config/__tests__/projectConfig.test.ts | 176 ++- .../models/__tests__/adminViews.db.test.ts | 225 ++-- .../src/models/__tests__/audits.db.test.ts | 204 ++-- .../models/__tests__/interviews.db.test.ts | 680 +++++------ .../__tests__/interviewsAccesses.db.test.ts | 120 +- .../__tests__/interviewsPreFill.db.test.ts | 5 +- .../models/__tests__/monitoring.db.test.ts | 44 +- .../__tests__/paradataEvents.db.test.ts | 643 +++++------ .../models/__tests__/participants.db.test.ts | 80 +- .../src/services/__tests__/accessCode.test.ts | 8 +- .../__tests__/exportAllToCsvByObject.test.ts | 202 ++-- .../__tests__/exportInterviewLogs.test.ts | 761 ++++++++----- .../audits/__tests__/AuditService.test.ts | 99 +- .../audits/__tests__/AuditUtils.test.ts | 49 +- .../services/audits/__tests__/Auditor.test.ts | 38 +- .../services/audits/__tests__/Audits.test.ts | 47 +- .../__tests__/BatchAuditService.test.ts | 448 ++------ .../__tests__/SurveyObjectAuditor.test.ts | 158 +-- .../__tests__/SurveyObjectParsers.test.ts | 253 +---- .../SurveyObjectsAndAuditsFactory.test.ts | 59 +- .../__tests__/AuditCheckUtils.test.ts | 39 +- .../checks/__tests__/AuditLocales.test.ts | 19 +- .../__tests__/HouseholdAuditChecks.test.ts | 1 - .../checks/__tests__/TripAuditChecks.test.ts | 1 - .../__tests__/VisitedPlaceAuditChecks.test.ts | 4 +- .../__tests__/home/HM_I_Geography.test.ts | 36 +- ...HM_I_GeographyNotInSurveyTerritory.test.ts | 43 +- ...graphyTooFarApartError.integration.test.ts | 15 +- ...hyAndHomeGeographyTooFarApartError.test.ts | 54 +- ...aphyTooFarApartWarning.integration.test.ts | 27 +- ...AndHomeGeographyTooFarApartWarning.test.ts | 64 +- .../__tests__/home/HM_M_Geography.test.ts | 1 - .../checks/__tests__/home/testHelper.ts | 5 +- .../__tests__/household/HH_I_Size.test.ts | 2 - .../__tests__/household/HH_M_Size.test.ts | 1 - .../checks/__tests__/household/testHelper.ts | 5 +- .../interview/I_I_InvalidAccessCode.test.ts | 61 +- .../interview/I_M_AccessCode.test.ts | 66 +- .../__tests__/interview/I_M_Languages.test.ts | 21 +- .../__tests__/interview/I_M_StartedAt.test.ts | 87 +- .../checks/__tests__/interview/testHelper.ts | 9 +- .../__tests__/journey/J_M_StartDate.test.ts | 1 - .../checks/__tests__/journey/testHelper.ts | 5 +- .../checks/__tests__/person/P_M_Age.test.ts | 2 - .../checks/__tests__/person/testHelper.ts | 5 +- .../checks/__tests__/segment/S_M_Mode.test.ts | 2 - .../checks/__tests__/segment/testHelper.ts | 5 +- .../__tests__/trip/T_M_Segments.test.ts | 1 - .../checks/__tests__/trip/testHelper.ts | 5 +- .../visitedPlace/VP_I_Geography.test.ts | 36 +- .../visitedPlace/VP_M_Geography.test.ts | 1 - .../__tests__/visitedPlace/testHelper.ts | 5 +- .../auth/__tests__/authByFieldLogin.test.ts | 199 ++-- .../__tests__/participantAuthModel.test.ts | 154 +-- .../participantAuthorization.test.ts | 19 +- .../auth/__tests__/roleDefinition.test.ts | 45 +- .../auth/__tests__/userAuthorization.test.ts | 20 +- .../__tests__/InterviewUtils.test.ts | 96 +- .../interviews/__tests__/interview.test.ts | 260 ++--- .../interviews/__tests__/interviews.test.ts | 358 +++--- .../__tests__/serverFieldUpdate.test.ts | 262 +++-- .../logging/__tests__/messageLogging.test.ts | 61 +- .../logging/__tests__/paradataLogging.test.ts | 233 +--- .../__tests__/queryLoggingMiddleware.test.ts | 32 +- .../logging/__tests__/supportRequest.test.ts | 145 +-- .../__tests__/respondentBehavior.test.ts | 16 +- .../__tests__/RouteCalculation.test.ts | 146 ++- .../RouteCalculationFromTransition.test.ts | 476 +++----- .../__tests__/JourneyFactory.test.ts | 104 +- .../__tests__/PersonFactory.test.ts | 97 +- .../__tests__/SegmentFactory.test.ts | 134 +-- .../__tests__/SurveyObjectsFactory.test.ts | 52 +- .../__tests__/TripFactory.test.ts | 211 ++-- .../__tests__/VisitedPlaceFactory.test.ts | 211 ++-- .../__tests__/surveyStatus.test.ts | 58 +- .../__tests__/serverValidation.test.ts | 12 +- .../project.config.initialized.test.ts | 38 +- .../config/__tests__/project.config.test.ts | 44 +- .../audits/__tests__/AuditUtils.test.ts | 30 +- .../baseObjects/__tests__/Address.test.ts | 27 +- .../baseObjects/__tests__/BaseAddress.test.ts | 2 - .../__tests__/BaseHousehold.test.ts | 23 +- .../__tests__/BaseInterview.test.ts | 31 +- .../baseObjects/__tests__/BaseJourney.test.ts | 13 +- .../__tests__/BaseJunction.test.ts | 44 +- .../__tests__/BaseOrganization.test.ts | 26 +- .../baseObjects/__tests__/BasePerson.test.ts | 45 +- .../baseObjects/__tests__/BasePlace.test.ts | 45 +- .../baseObjects/__tests__/BaseSegment.test.ts | 20 +- .../baseObjects/__tests__/BaseTrip.test.ts | 27 +- .../__tests__/BaseTripChain.test.ts | 22 +- .../baseObjects/__tests__/BaseVehicle.test.ts | 39 +- .../__tests__/BaseVisitedPlace.test.ts | 45 +- .../baseObjects/__tests__/Home.test.ts | 58 +- .../baseObjects/__tests__/Household.test.ts | 117 +- .../baseObjects/__tests__/Journey.test.ts | 108 +- .../baseObjects/__tests__/Junction.test.ts | 57 +- .../__tests__/Organization.test.ts | 84 +- .../baseObjects/__tests__/Person.test.ts | 289 ++--- .../baseObjects/__tests__/Place.test.ts | 133 +-- .../baseObjects/__tests__/Routing.test.ts | 16 +- .../baseObjects/__tests__/Sample.test.ts | 9 +- .../baseObjects/__tests__/SchoolPlace.test.ts | 72 +- .../baseObjects/__tests__/Segment.test.ts | 118 +- .../__tests__/StartEndable.test.ts | 265 ++--- .../baseObjects/__tests__/Survey.test.ts | 58 +- .../__tests__/SurveyObjectRegistry.test.ts | 30 +- .../baseObjects/__tests__/Trip.test.ts | 344 +++--- .../baseObjects/__tests__/TripChain.test.ts | 109 +- .../baseObjects/__tests__/Uuidable.test.ts | 5 +- .../baseObjects/__tests__/Vehicle.test.ts | 33 +- .../__tests__/VisitedPlace.test.ts | 59 +- .../baseObjects/__tests__/Weight.test.ts | 2 +- .../__tests__/WeightMethod.test.ts | 23 +- .../baseObjects/__tests__/WorkPlace.test.ts | 70 +- .../__tests__/Interview.getSet.test.ts | 12 +- .../interview/__tests__/Interview.test.ts | 115 +- .../Interview.validateParams.test.ts | 10 +- .../__tests__/InterviewAudited.test.ts | 24 +- .../InterviewParadata.getSet.test.ts | 30 +- .../__tests__/InterviewParadata.test.ts | 53 +- .../InterviewParadata.validateParams.test.ts | 92 +- .../__tests__/SurveyGeographyUtils.test.ts | 11 +- .../odSurvey/__tests__/helpers.test.ts | 586 ++-------- .../__tests__/NavigationService.test.ts | 1005 +++++------------ .../__tests__/navigationHelpers.test.ts | 177 +-- .../buttonValidateAndGotoNextSection.test.ts | 17 +- .../widgetPersonVisitedPlacesMap.test.ts | 381 +++++-- .../__tests__/widgetsSwitchPerson.test.ts | 26 +- .../__tests__/buttonSaveTripSegments.test.ts | 29 +- .../__tests__/groupPersonTrips.test.ts | 96 +- .../segments/__tests__/groupSegments.test.ts | 109 +- .../segments/__tests__/helpers.test.ts | 208 ++-- .../segments/__tests__/sectionSegment.test.ts | 230 ++-- .../__tests__/widgetPersonTripsTitle.test.ts | 20 +- .../__tests__/widgetSameAsReverseTrip.test.ts | 70 +- .../widgetSegmentHasNextMode.test.ts | 59 +- .../__tests__/widgetSegmentMode.test.ts | 202 ++-- .../__tests__/widgetSegmentModePre.test.ts | 174 ++- .../__tests__/widgetTripSegmentsIntro.test.ts | 20 +- .../__tests__/activityIconMapping.test.ts | 1 - .../types/__tests__/NavigationTypes.test.ts | 10 +- .../types/__tests__/SectionConfig.test.ts | 439 ++++--- .../types/__tests__/WidgetConfig.test.ts | 34 +- .../__tests__/checkConditionals.test.ts | 361 +----- .../validations/__tests__/validations.test.ts | 58 +- .../src/utils/__tests__/ColorUtils.test.ts | 6 +- .../utils/__tests__/ConstructorUtils.test.ts | 56 +- .../src/utils/__tests__/DateTimeUtils.test.ts | 91 +- .../src/utils/__tests__/DateUtils.test.ts | 36 +- .../__tests__/ParamsValidatorUtils.test.ts | 94 +- .../src/utils/__tests__/PhysicsUtils.test.ts | 112 +- .../src/utils/__tests__/TypeUtils.test.ts | 13 +- .../src/utils/__tests__/formatters.test.ts | 108 +- .../src/utils/__tests__/helpers.test.ts | 506 +++++---- .../src/actions/__tests__/Survey.test.ts | 915 ++++++++------- .../utils/__tests__/Conditional.test.ts | 135 ++- .../utils/__tests__/Validation.test.ts | 65 +- .../utils/__tests__/WidgetOperation.test.ts | 479 ++++---- .../HorizontalBarMonitoringChart.test.tsx | 26 +- .../SingleValueMonitoringChart.test.tsx | 40 +- .../__tests__/AdminSurveyRouter.test.ts | 102 +- .../admin/validations/InterviewStats.tsx | 2 +- .../admin/validations/react-table-config.d.ts | 28 +- .../__tests__/WithSurveyContextHoc.test.tsx | 16 +- .../__tests__/useSectionTemplate.test.ts | 44 +- .../inputs/__tests__/InputButton.test.tsx | 112 +- .../inputs/__tests__/InputCheckbox.test.tsx | 133 +-- .../__tests__/InputChoiceSorting.test.ts | 153 ++- .../inputs/__tests__/InputDatePicker.test.tsx | 57 +- .../inputs/__tests__/InputLoading.test.tsx | 2 +- .../__tests__/InputMapFindPlace.test.tsx | 337 +++--- .../inputs/__tests__/InputMapPoint.test.tsx | 170 ++- .../__tests__/InputMultiselect.test.tsx | 99 +- .../inputs/__tests__/InputRadio.test.tsx | 135 +-- .../__tests__/InputRadioNumber.test.tsx | 105 +- .../inputs/__tests__/InputRange.test.tsx | 101 +- .../inputs/__tests__/InputSelect.test.tsx | 147 +-- .../inputs/__tests__/InputString.test.tsx | 87 +- .../inputs/__tests__/InputText.test.tsx | 44 +- .../inputs/__tests__/InputTime.test.tsx | 79 +- .../inputs/__tests__/interviewData.ts | 22 +- .../modal/__tests__/SimpleModal.test.tsx | 28 +- .../__tests__/ConsentAndStartForm.test.tsx | 98 +- .../__tests__/ParticipantSurveyRouter.test.ts | 99 +- .../survey/__tests__/Button.test.tsx | 253 ++--- .../survey/__tests__/ErrorBoundary.test.tsx | 16 +- .../survey/__tests__/GroupWidgets.test.tsx | 312 +++-- .../survey/__tests__/InfoMap.test.tsx | 37 +- .../survey/__tests__/Question.test.tsx | 200 ++-- .../components/survey/__tests__/Text.test.tsx | 19 +- .../survey/__tests__/Widget.test.tsx | 78 +- .../TripsAndSegmentsSection.test.tsx | 155 +-- .../DeleteGroupedObjectButton.test.tsx | 165 ++- .../src/config/__tests__/i18n.config.test.ts | 61 +- .../src/services/__tests__/url.test.ts | 20 +- .../display/__tests__/frontendHelper.test.ts | 71 +- .../__tests__/errorHandling.test.ts | 156 ++- .../store/__tests__/configureStore.test.ts | 58 +- .../loadingState/__tests__/reducer.test.ts | 26 +- .../store/survey/__tests__/reducer.test.ts | 130 +-- .../SurveyObjectsUnserializer.test.ts | 133 +-- 208 files changed, 8965 insertions(+), 13693 deletions(-) diff --git a/packages/evolution-backend/src/api/__tests__/auth.routes.test.ts b/packages/evolution-backend/src/api/__tests__/auth.routes.test.ts index 87949296a..ac7f2c07e 100644 --- a/packages/evolution-backend/src/api/__tests__/auth.routes.test.ts +++ b/packages/evolution-backend/src/api/__tests__/auth.routes.test.ts @@ -18,14 +18,7 @@ jest.mock('chaire-lib-backend/lib/api/auth.routes', () => { return jest.fn(); }); -let authResponse: { - error: any, - user: any, - statusCode?: number -} = { - error: null, - user: false -} +let authResponse: { error: any; user: any; statusCode?: number } = { error: null, user: false }; const authMockImplementation = (req, res, next) => { const response = authResponse; @@ -38,9 +31,7 @@ const authMockImplementation = (req, res, next) => { next(response.error, response.user); }; -const authMockFunctions = { - 'auth-by-field': jest.fn().mockImplementation(authMockImplementation) -} +const authMockFunctions = { 'auth-by-field': jest.fn().mockImplementation(authMockImplementation) }; jest.mock('passport', () => { return { @@ -51,7 +42,7 @@ jest.mock('passport', () => { mockImplementation(req, res, (error, user) => { callback(error, user); }); - } + }; } return mockImplementation; }), @@ -60,7 +51,7 @@ jest.mock('passport', () => { deserializeUser: jest.fn(), initialize: jest.fn().mockReturnValue((req, res, next) => next()), session: jest.fn().mockReturnValue((req, res, next) => next()) - } + }; }); // Mock the captcha validation @@ -72,22 +63,12 @@ const mockedValidateCaptchaToken = jest.fn().mockImplementation((req, res, next) }); const validFieldValue = 'test-value'; -const validUser = { - id: 5, - uuid: 'arbitrary', - username: 'test', - email: 'test@test.org', - is_confirmed: true, - is_valid: true -}; +const validUser = { id: 5, uuid: 'arbitrary', username: 'test', email: 'test@test.org', is_confirmed: true, is_valid: true }; beforeEach(() => { - authResponse = { - error: null, - user: false - } - Object.keys(authMockFunctions).forEach(authType => authMockFunctions[authType].mockClear()); -}) + authResponse = { error: null, user: false }; + Object.keys(authMockFunctions).forEach((authType) => authMockFunctions[authType].mockClear()); +}); describe('Auth by field route', () => { // Setup the app with byField enabled @@ -108,20 +89,17 @@ describe('Auth by field route', () => { callback(null); }); next(); - }) + }); authRoutes(router, userAuthModel, passport); app.use(router); beforeEach(() => { failedAttempts = undefined; jest.clearAllMocks(); - }) + }); test('Auth by field, valid credentials', async () => { - authResponse = { - error: null, - user: validUser - }; + authResponse = { error: null, user: validUser }; const res = await session(app) .post('/auth-by-field') .send({ field: validFieldValue }) @@ -136,10 +114,7 @@ describe('Auth by field route', () => { test('Auth by field, valid credentials after first attempt, valid catpcha', async () => { failedAttempts = 1; // Simulate one failed attempt, should validate captcha - authResponse = { - error: null, - user: validUser - }; + authResponse = { error: null, user: validUser }; const res = await session(app) .post('/auth-by-field') .send({ field: validFieldValue }) @@ -156,11 +131,8 @@ describe('Auth by field route', () => { failedAttempts = 1; // Simulate one failed attempt, should validate captcha mockedValidateCaptchaToken.mockImplementationOnce((req, res, next) => { return res.status(403).json({ status: 'InvalidCaptcha' }); - }) - authResponse = { - error: null, - user: validUser - }; + }); + authResponse = { error: null, user: validUser }; const res = await session(app) .post('/auth-by-field') .send({ field: validFieldValue }) @@ -174,10 +146,7 @@ describe('Auth by field route', () => { }); test('Auth by field, invalid credentials', async () => { - authResponse = { - error: 'InvalidData', - user: false - }; + authResponse = { error: 'InvalidData', user: false }; const res = await session(app) .post('/auth-by-field') .send({ field: 'invalid-value' }) @@ -191,11 +160,7 @@ describe('Auth by field route', () => { }); test('Auth by field, unauthorized access', async () => { - authResponse = { - error: 'Unauthorized', - user: false, - statusCode: 401 - }; + authResponse = { error: 'Unauthorized', user: false, statusCode: 401 }; const res = await session(app) .post('/auth-by-field') .send({ field: validFieldValue }) @@ -229,4 +194,4 @@ describe('Auth by field route when disabled', () => { .expect('Content-Type', 'text/html; charset=utf-8') .expect(404); }); -}); \ No newline at end of file +}); diff --git a/packages/evolution-backend/src/api/__tests__/survey.participant.routes.test.ts b/packages/evolution-backend/src/api/__tests__/survey.participant.routes.test.ts index 260383258..f442836ca 100644 --- a/packages/evolution-backend/src/api/__tests__/survey.participant.routes.test.ts +++ b/packages/evolution-backend/src/api/__tests__/survey.participant.routes.test.ts @@ -15,9 +15,7 @@ import { sendSupportRequestEmail } from '../../services/logging/supportRequest'; jest.mock('../../services/interviews/interviews'); jest.mock('../../services/logging/queryLoggingMiddleware'); jest.mock('../../services/logging/supportRequest'); -jest.mock('evolution-common/lib/config/project.config', () => ({ - surveySupportForm: true -})); +jest.mock('evolution-common/lib/config/project.config', () => ({ surveySupportForm: true })); const mockUserId = 3; const mockAuthorizationMiddleware = jest.fn(() => (req, res, next) => next()); @@ -53,14 +51,7 @@ describe('GET /survey/activeInterview', () => { }); test('should return active interview for user', async () => { - const mockInterview = { - id: 1, - uuid: 'mockUuid', - is_valid: true, - is_completed: false, - response: {}, - participant_id: 1 - }; + const mockInterview = { id: 1, uuid: 'mockUuid', is_valid: true, is_completed: false, response: {}, participant_id: 1 }; (Interviews.getUserInterview as jest.Mock).mockResolvedValue(mockInterview); const response = await request(app).get('/survey/activeInterview'); @@ -71,14 +62,7 @@ describe('GET /survey/activeInterview', () => { }); test('should create interview if none exists', async () => { - const mockCreatedInterview = { - id: 1, - uuid: 'mockUuid', - is_valid: true, - is_completed: false, - response: {}, - participant_id: 1 - }; + const mockCreatedInterview = { id: 1, uuid: 'mockUuid', is_valid: true, is_completed: false, response: {}, participant_id: 1 }; (Interviews.getUserInterview as jest.Mock).mockResolvedValue(undefined); (Interviews.createInterviewForUser as jest.Mock).mockResolvedValue(mockCreatedInterview); @@ -116,11 +100,7 @@ describe('GET /survey/activeInterview', () => { const response = await request(app).get('/survey/activeInterview'); expect(response.status).toBe(500); - expect(response.body).toEqual({ - status: 'failed', - interview: null, - error: 'cannot fetch interview' - }); + expect(response.body).toEqual({ status: 'failed', interview: null, error: 'cannot fetch interview' }); expect(Interviews.getUserInterview).toHaveBeenCalledWith(mockUserId); }); @@ -155,11 +135,7 @@ describe('POST /supportRequest', () => { }); test('should handle support request successfully when user is not logged in', async () => { - const requestData = { - email: 'test@example.com', - message: 'Help me please', - currentUrl: 'http://test.com/page' - }; + const requestData = { email: 'test@example.com', message: 'Help me please', currentUrl: 'http://test.com/page' }; (sendSupportRequestEmail as jest.Mock).mockResolvedValue(undefined); @@ -191,11 +167,7 @@ describe('POST /supportRequest', () => { (Interviews.getUserInterview as jest.Mock).mockResolvedValue(mockInterview); (sendSupportRequestEmail as jest.Mock).mockResolvedValue(undefined); - const requestData = { - email: 'test@example.com', - message: 'Help me please', - currentUrl: 'http://test.com/page' - }; + const requestData = { email: 'test@example.com', message: 'Help me please', currentUrl: 'http://test.com/page' }; const response = await request(loggedInApp).post('/supportRequest/').send(requestData); @@ -212,10 +184,7 @@ describe('POST /supportRequest', () => { }); test('should handle missing message in request', async () => { - const requestData = { - email: 'test@example.com', - currentUrl: 'http://test.com/page' - }; + const requestData = { email: 'test@example.com', currentUrl: 'http://test.com/page' }; (sendSupportRequestEmail as jest.Mock).mockResolvedValue(undefined); @@ -235,10 +204,7 @@ describe('POST /supportRequest', () => { test('should handle errors in support request processing', async () => { (sendSupportRequestEmail as jest.Mock).mockRejectedValue(new Error('Email sending failed')); - const requestData = { - email: 'test@example.com', - message: 'Help me please' - }; + const requestData = { email: 'test@example.com', message: 'Help me please' }; const response = await request(publicApp).post('/supportRequest/').send(requestData); @@ -253,11 +219,7 @@ describe('POST /supportRequest', () => { return res.status(403).json({ status: 'InvalidCaptcha' }); }); - const requestData = { - email: 'test@example.com', - message: 'Help me please', - currentUrl: 'http://test.com/page' - }; + const requestData = { email: 'test@example.com', message: 'Help me please', currentUrl: 'http://test.com/page' }; (sendSupportRequestEmail as jest.Mock).mockResolvedValue(undefined); @@ -272,9 +234,7 @@ describe('POST /supportRequest', () => { test('should not register route when supportForm is disabled', async () => { // Override the project config mock to disable support form jest.resetModules(); - jest.mock('evolution-common/lib/config/project.config', () => ({ - surveySupportForm: false - })); + jest.mock('evolution-common/lib/config/project.config', () => ({ surveySupportForm: false })); // Re-import to get updated config, otherwise it still uses the previous config mock and the result is a 500 error const { getPublicParticipantRoutes: getUpdatedRoutes } = require('../survey.participant.routes'); diff --git a/packages/evolution-backend/src/api/__tests__/survey.user.routes.test.ts b/packages/evolution-backend/src/api/__tests__/survey.user.routes.test.ts index 8bb156efb..a1ec4750a 100644 --- a/packages/evolution-backend/src/api/__tests__/survey.user.routes.test.ts +++ b/packages/evolution-backend/src/api/__tests__/survey.user.routes.test.ts @@ -13,13 +13,9 @@ import Interviews from '../../services/interviews/interviews'; import { addRolesToInterview } from '../../services/interviews/interview'; import { isLoggedIn } from 'chaire-lib-backend/lib/services/auth/authorization'; -jest.mock('../../services/interviews/interviews', () => ({ - getInterviewByUuid: jest.fn() -})); +jest.mock('../../services/interviews/interviews', () => ({ getInterviewByUuid: jest.fn() })); const mockGetInterviewByUuid = Interviews.getInterviewByUuid as jest.MockedFunction; -jest.mock('../../services/interviews/interview', () => ({ - addRolesToInterview: jest.fn() -})); +jest.mock('../../services/interviews/interview', () => ({ addRolesToInterview: jest.fn() })); const mockAddRolesToInterview = addRolesToInterview as jest.MockedFunction; jest.mock('../../services/logging/queryLoggingMiddleware'); @@ -36,18 +32,15 @@ jest.mock('chaire-lib-backend/lib/services/auth/authorization', () => ({ isLoggedIn: jest.fn((req, res, next) => { req.user = { id: mockUserId }; // Mock user object next(); - }), + }) })); const mockIsLoggedIn = isLoggedIn as jest.MockedFunction; const app = express(); app.use(express.json()); -app.use( - surveyUserRoutes(mockAuthorizationMiddleware, mockLoggingMiddleware) -); +app.use(surveyUserRoutes(mockAuthorizationMiddleware, mockLoggingMiddleware)); describe('GET /survey/activeInterview/:interviewId', () => { - const interviewUuid = uuidV4(); beforeEach(() => { @@ -68,8 +61,7 @@ describe('GET /survey/activeInterview/:interviewId', () => { it('should return 404 if interview is not found', async () => { mockGetInterviewByUuid.mockResolvedValueOnce(undefined); - const response = await request(app) - .get('/survey/activeInterview/' + interviewUuid); + const response = await request(app).get('/survey/activeInterview/' + interviewUuid); expect(response.status).toBe(404); expect(response.body).toEqual({ status: 'notFound', interview: null }); @@ -80,8 +72,7 @@ describe('GET /survey/activeInterview/:interviewId', () => { const mockInterview = { id: 1, uuid: interviewUuid }; mockGetInterviewByUuid.mockResolvedValueOnce(mockInterview as any); - const response = await request(app) - .get('/survey/activeInterview/' + interviewUuid); + const response = await request(app).get('/survey/activeInterview/' + interviewUuid); expect(response.status).toBe(200); expect(response.body).toEqual({ status: 'success', interview: mockInterview }); @@ -92,20 +83,15 @@ describe('GET /survey/activeInterview/:interviewId', () => { it('should return 500 if an error occurs', async () => { mockGetInterviewByUuid.mockRejectedValueOnce(new Error('Database error')); - const response = await request(app) - .get('/survey/activeInterview/' + interviewUuid); + const response = await request(app).get('/survey/activeInterview/' + interviewUuid); expect(response.status).toBe(500); - expect(response.body).toEqual({ - status: 'failed', - interview: null, - error: 'cannot fetch interview' - }); + expect(response.body).toEqual({ status: 'failed', interview: null, error: 'cannot fetch interview' }); }); it('should return failed status if uuid is not valid', async () => { const response = await request(app).get('/survey/activeInterview/notAUuid'); expect(response.status).toBe(400); - expect(response.body).toEqual({ status: 'failed', error: "Invalid interview ID" }); + expect(response.body).toEqual({ status: 'failed', error: 'Invalid interview ID' }); }); -}); \ No newline at end of file +}); diff --git a/packages/evolution-backend/src/api/admin/__tests__/exports.routes.test.ts b/packages/evolution-backend/src/api/admin/__tests__/exports.routes.test.ts index a6254d967..a94ddacd8 100644 --- a/packages/evolution-backend/src/api/admin/__tests__/exports.routes.test.ts +++ b/packages/evolution-backend/src/api/admin/__tests__/exports.routes.test.ts @@ -27,26 +27,14 @@ jest.mock('../../../services/adminExport/exportAllToCsvBySurveyObject', () => ({ })); // Mock the file path on server -jest.mock('chaire-lib-backend/lib/utils/filesystem/directoryManager', () => ({ - directoryManager: { - getAbsolutePath: jest.fn() - } -})); +jest.mock('chaire-lib-backend/lib/utils/filesystem/directoryManager', () => ({ directoryManager: { getAbsolutePath: jest.fn() } })); // Mock the file manager -jest.mock('chaire-lib-backend/lib/utils/filesystem/fileManager', () => ({ - fileManager: { - fileExistsAbsolute: jest.fn() - } -})); +jest.mock('chaire-lib-backend/lib/utils/filesystem/fileManager', () => ({ fileManager: { fileExistsAbsolute: jest.fn() } })); const exportAllToCsvBySurveyObjectMock = exportAllToCsvBySurveyObject as jest.MockedFunction; -const fileExistsAbsoluteMock = fileManager.fileExistsAbsolute as jest.MockedFunction< - typeof fileManager.fileExistsAbsolute ->; -const getAbsolutePathMock = directoryManager.getAbsolutePath as jest.MockedFunction< - typeof directoryManager.getAbsolutePath ->; +const fileExistsAbsoluteMock = fileManager.fileExistsAbsolute as jest.MockedFunction; +const getAbsolutePathMock = directoryManager.getAbsolutePath as jest.MockedFunction; let app: express.Application; beforeAll(() => { @@ -60,42 +48,28 @@ describe('prepareCsvFileForExportBySurveyObject route', () => { it('Should prepare corrected response by default', async () => { const response = await request(app).get('/api/admin/data/prepareCsvFileForExportBySurveyObject'); expect(response.status).toBe(200); - expect(response.body).toEqual({ - status: 'exportStarted' - }); + expect(response.body).toEqual({ status: 'exportStarted' }); expect(exportAllToCsvBySurveyObjectMock).toHaveBeenCalledWith({ responseType: 'correctedIfAvailable' }); }); it('Should prepare corrected response, if specified', async () => { - const response = await request(app).get( - '/api/admin/data/prepareCsvFileForExportBySurveyObject?responseType=correctedIfAvailable' - ); + const response = await request(app).get('/api/admin/data/prepareCsvFileForExportBySurveyObject?responseType=correctedIfAvailable'); expect(response.status).toBe(200); - expect(response.body).toEqual({ - status: 'exportStarted' - }); + expect(response.body).toEqual({ status: 'exportStarted' }); expect(exportAllToCsvBySurveyObjectMock).toHaveBeenCalledWith({ responseType: 'correctedIfAvailable' }); }); it('Should prepare corrected response, if invalid value is specified', async () => { - const response = await request(app).get( - '/api/admin/data/prepareCsvFileForExportBySurveyObject?responseType=unknownType' - ); + const response = await request(app).get('/api/admin/data/prepareCsvFileForExportBySurveyObject?responseType=unknownType'); expect(response.status).toBe(200); - expect(response.body).toEqual({ - status: 'exportStarted' - }); + expect(response.body).toEqual({ status: 'exportStarted' }); expect(exportAllToCsvBySurveyObjectMock).toHaveBeenCalledWith({ responseType: 'correctedIfAvailable' }); }); it('Should prepare participant data, if specified', async () => { - const response = await request(app).get( - '/api/admin/data/prepareCsvFileForExportBySurveyObject?responseType=participant' - ); + const response = await request(app).get('/api/admin/data/prepareCsvFileForExportBySurveyObject?responseType=participant'); expect(response.status).toBe(200); - expect(response.body).toEqual({ - status: 'exportStarted' - }); + expect(response.body).toEqual({ status: 'exportStarted' }); expect(exportAllToCsvBySurveyObjectMock).toHaveBeenCalledWith({ responseType: 'participant' }); }); }); @@ -143,9 +117,6 @@ describe('downloadSurveyQuestionnaireListTxt route', () => { const response = await request(app).get('/api/admin/data/downloadSurveyQuestionnaireListTxt?lang=en'); expect(response.status).toBe(404); - expect(response.body).toEqual({ - status: 'notFound', - message: 'file does not exist for language: en' - }); + expect(response.body).toEqual({ status: 'notFound', message: 'file does not exist for language: en' }); }); }); diff --git a/packages/evolution-backend/src/api/helpers/__tests__/validateUuidMiddleware.test.ts b/packages/evolution-backend/src/api/helpers/__tests__/validateUuidMiddleware.test.ts index 7fe826f45..438bb33c3 100644 --- a/packages/evolution-backend/src/api/helpers/__tests__/validateUuidMiddleware.test.ts +++ b/packages/evolution-backend/src/api/helpers/__tests__/validateUuidMiddleware.test.ts @@ -18,14 +18,14 @@ const router = express.Router(); // Add the middleware to test before each route router.get('/testWithUuid/:interviewUuid', validateUuidMiddleware, requestHandler); router.get('/testWithId/:interviewId', validateUuidMiddleware, requestHandler); -app.use('/test', router) +app.use('/test', router); beforeEach(() => { jest.clearAllMocks(); -}) +}); test('Valid uuid in interviewUuid', async () => { - const uuid = uuidV4() + const uuid = uuidV4(); const response = await request(app).get(`/test/testWithUuid/${uuid}`); expect(response.status).toBe(200); expect(requestHandler).toHaveBeenCalled(); @@ -38,7 +38,7 @@ test('Invalid uuid in interviewUuid', async () => { }); test('Valid uuid in interviewId', async () => { - const uuid = uuidV4() + const uuid = uuidV4(); const response = await request(app).get(`/test/testWithId/${uuid}`); expect(response.status).toBe(200); expect(requestHandler).toHaveBeenCalled(); @@ -49,4 +49,3 @@ test('Invalid uuid in interviewId', async () => { expect(response.status).toBe(400); expect(requestHandler).not.toHaveBeenCalled(); }); - diff --git a/packages/evolution-backend/src/apps/participant/__tests__/serverApp.test.ts b/packages/evolution-backend/src/apps/participant/__tests__/serverApp.test.ts index a5ec2962e..2ca15215f 100644 --- a/packages/evolution-backend/src/apps/participant/__tests__/serverApp.test.ts +++ b/packages/evolution-backend/src/apps/participant/__tests__/serverApp.test.ts @@ -11,63 +11,37 @@ import * as surveyStatus from '../../../services/surveyStatus/surveyStatus'; import { fileManager } from 'chaire-lib-backend/lib/utils/filesystem/fileManager'; // Mock the dependencies -jest.mock('chaire-lib-backend/lib/config/server.config', () => ({ - __esModule: true, - default: { - projectShortname: 'test-project' - } -})); +jest.mock('chaire-lib-backend/lib/config/server.config', () => ({ __esModule: true, default: { projectShortname: 'test-project' } })); -jest.mock('chaire-lib-backend/lib/config/shared/db.config', () => ({ - __esModule: true, - default: {} -})); +jest.mock('chaire-lib-backend/lib/config/shared/db.config', () => ({ __esModule: true, default: {} })); -jest.mock('../../../services/auth/participantAuthModel', () => ({ - participantAuthModel: {} -})); +jest.mock('../../../services/auth/participantAuthModel', () => ({ participantAuthModel: {} })); jest.mock('../../../services/auth/auth.config', () => ({ __esModule: true, - default: jest.fn().mockReturnValue({ - initialize: jest.fn().mockReturnValue((req, res, next) => next()), - session: jest.fn().mockReturnValue((req, res, next) => next()) - }) + default: jest + .fn() + .mockReturnValue({ + initialize: jest.fn().mockReturnValue((req, res, next) => next()), + session: jest.fn().mockReturnValue((req, res, next) => next()) + }) })); jest.mock('chaire-lib-backend/lib/utils/filesystem/directoryManager', () => ({ - directoryManager: { - createDirectoryIfNotExistsAbsolute: jest.fn(), - createDirectoryIfNotExists: jest.fn() - } + directoryManager: { createDirectoryIfNotExistsAbsolute: jest.fn(), createDirectoryIfNotExists: jest.fn() } })); -jest.mock('chaire-lib-backend/lib/utils/filesystem/fileManager', () => ({ - fileManager: { - fileExistsAbsolute: jest.fn().mockReturnValue(true) - } -})); +jest.mock('chaire-lib-backend/lib/utils/filesystem/fileManager', () => ({ fileManager: { fileExistsAbsolute: jest.fn().mockReturnValue(true) } })); const mockFileExistsAbsolute = fileManager.fileExistsAbsolute as jest.MockedFunction; -jest.mock('../../../api/auth.routes', () => ({ - __esModule: true, - default: jest.fn() -})); +jest.mock('../../../api/auth.routes', () => ({ __esModule: true, default: jest.fn() })); -jest.mock('../../participant/routes', () => ({ - __esModule: true, - default: jest.fn() -})); +jest.mock('../../participant/routes', () => ({ __esModule: true, default: jest.fn() })); -jest.mock('chaire-lib-backend/lib/api/trRouting.routes', () => ({ - __esModule: true, - default: jest.fn() -})); +jest.mock('chaire-lib-backend/lib/api/trRouting.routes', () => ({ __esModule: true, default: jest.fn() })); jest.mock('connect-session-knex', () => { - const MockStore = jest.fn().mockImplementation(() => ({ - on: jest.fn() - })); + const MockStore = jest.fn().mockImplementation(() => ({ on: jest.fn() })); return jest.fn().mockReturnValue(MockStore); }); @@ -139,9 +113,7 @@ describe('serverApp index path routing', () => { expect(response.status).toBe(200); expect(response.text).toBe('Survey Page'); expect(surveyStatus.isSurveyEnded).toHaveBeenCalled(); - expect(sendFileMock).toHaveBeenCalledWith( - expect.stringContaining('index-survey-test-project_test.html') - ); + expect(sendFileMock).toHaveBeenCalledWith(expect.stringContaining('index-survey-test-project_test.html')); }); test('should return the regular index page for unmatched routes', async () => { @@ -150,18 +122,14 @@ describe('serverApp index path routing', () => { expect(response.status).toBe(200); expect(response.text).toBe('Survey Page'); expect(surveyStatus.isSurveyEnded).toHaveBeenCalled(); - expect(sendFileMock).toHaveBeenCalledWith( - expect.stringContaining('index-survey-test-project_test.html') - ); + expect(sendFileMock).toHaveBeenCalledWith(expect.stringContaining('index-survey-test-project_test.html')); }); test('should return 404 for file extensions that do not exist', async () => { const response = await request(app).get('/nonexistent.php'); expect(response.status).toBe(404); - expect(sendFileMock).toHaveBeenCalledWith( - expect.stringContaining('notFound404.html') - ); + expect(sendFileMock).toHaveBeenCalledWith(expect.stringContaining('notFound404.html')); }); }); @@ -177,9 +145,7 @@ describe('serverApp index path routing', () => { expect(response.status).toBe(200); expect(response.text).toBe('Survey Ended Page'); expect(surveyStatus.isSurveyEnded).toHaveBeenCalled(); - expect(sendFileMock).toHaveBeenCalledWith( - expect.stringContaining('index-survey-ended-test-project_test.html') - ); + expect(sendFileMock).toHaveBeenCalledWith(expect.stringContaining('index-survey-ended-test-project_test.html')); }); test('should return the survey ended index page for unmatched routes', async () => { @@ -188,18 +154,14 @@ describe('serverApp index path routing', () => { expect(response.status).toBe(200); expect(response.text).toBe('Survey Ended Page'); expect(surveyStatus.isSurveyEnded).toHaveBeenCalled(); - expect(sendFileMock).toHaveBeenCalledWith( - expect.stringContaining('index-survey-ended-test-project_test.html') - ); + expect(sendFileMock).toHaveBeenCalledWith(expect.stringContaining('index-survey-ended-test-project_test.html')); }); test('should return 404 for file extensions that do not exist', async () => { const response = await request(app).get('/nonexistent.php'); expect(response.status).toBe(404); - expect(sendFileMock).toHaveBeenCalledWith( - expect.stringContaining('notFound404.html') - ); + expect(sendFileMock).toHaveBeenCalledWith(expect.stringContaining('notFound404.html')); }); test('should serve regular index if survey ended page does not exist', async () => { @@ -239,12 +201,8 @@ describe('serverApp index path routing', () => { expect(response.status).toBe(200); expect(response.text).toBe('Survey Page'); expect(surveyStatus.isSurveyEnded).toHaveBeenCalled(); - expect(sendFileMock).toHaveBeenCalledWith( - expect.stringContaining('index-survey-test-project_test.html') - ); - expect(mockFileExistsAbsolute).toHaveBeenCalledWith( - expect.stringContaining('index-survey-ended-test-project_test.html') - ); + expect(sendFileMock).toHaveBeenCalledWith(expect.stringContaining('index-survey-test-project_test.html')); + expect(mockFileExistsAbsolute).toHaveBeenCalledWith(expect.stringContaining('index-survey-ended-test-project_test.html')); }); }); @@ -309,10 +267,7 @@ describe('serverApp index path routing', () => { const testApp = express(); expect(() => setupServerApp(testApp, mockSetupFct)).not.toThrow(); - expect(consoleSpy).toHaveBeenCalledWith( - 'Error running project specific server setup function: ', - expect.any(Error) - ); + expect(consoleSpy).toHaveBeenCalledWith('Error running project specific server setup function: ', expect.any(Error)); consoleSpy.mockRestore(); }); diff --git a/packages/evolution-backend/src/config/__tests__/projectConfig.test.ts b/packages/evolution-backend/src/config/__tests__/projectConfig.test.ts index 8fc2f8e2c..073d02254 100644 --- a/packages/evolution-backend/src/config/__tests__/projectConfig.test.ts +++ b/packages/evolution-backend/src/config/__tests__/projectConfig.test.ts @@ -19,7 +19,7 @@ const interview: InterviewListAttributes = { username: 'test', facebook: false, google: false, - audits: { 'errorMsg': 2, 'errorMsg2': 5 } + audits: { errorMsg: 2, errorMsg2: 5 } }; const nullResponseInterview = { @@ -48,18 +48,20 @@ describe('Validation List Filter', () => { commonProjectConfig.hasAccessCode = false; const interviewStatus = projectConfig.validationListFilter(interview); - expect(interviewStatus).toEqual(expect.objectContaining({ - id: interview.id, - uuid: interview.uuid, - is_valid: interview.is_valid, - is_validated: undefined, - is_completed: interview.is_completed, - username: interview.username, - facebook: interview.facebook, - google: interview.google, - response: { household: { size: 0 }, _isCompleted: undefined, _validationComment: undefined }, - audits: interview.audits - })); + expect(interviewStatus).toEqual( + expect.objectContaining({ + id: interview.id, + uuid: interview.uuid, + is_valid: interview.is_valid, + is_validated: undefined, + is_completed: interview.is_completed, + username: interview.username, + facebook: interview.facebook, + google: interview.google, + response: { household: { size: 0 }, _isCompleted: undefined, _validationComment: undefined }, + audits: interview.audits + }) + ); expect((interviewStatus.response as InterviewResponse).accessCode).toBeUndefined(); }); }); @@ -72,17 +74,19 @@ describe('Validation List Filter', () => { commonProjectConfig.hasAccessCode = false; const interviewStatus = projectConfig.validationListFilter(nullResponseInterview as unknown as InterviewListAttributes); - expect(interviewStatus).toEqual(expect.objectContaining({ - id: interview.id, - uuid: interview.uuid, - is_valid: interview.is_valid, - is_validated: undefined, - is_completed: interview.is_completed, - username: interview.username, - facebook: interview.facebook, - google: interview.google, - response: { household: { size: undefined }, _isCompleted: undefined, _validationComment: undefined } - })); + expect(interviewStatus).toEqual( + expect.objectContaining({ + id: interview.id, + uuid: interview.uuid, + is_valid: interview.is_valid, + is_validated: undefined, + is_completed: interview.is_completed, + username: interview.username, + facebook: interview.facebook, + google: interview.google, + response: { household: { size: undefined }, _isCompleted: undefined, _validationComment: undefined } + }) + ); expect((interviewStatus.response as InterviewResponse).accessCode).toBeUndefined(); }); }); @@ -97,18 +101,20 @@ describe('Validation List Filter', () => { commonProjectConfig.hasAccessCode = true; const interviewStatus = projectConfig.validationListFilter(interview); - expect(interviewStatus).toEqual(expect.objectContaining({ - id: interview.id, - uuid: interview.uuid, - is_valid: interview.is_valid, - is_validated: undefined, - is_completed: interview.is_completed, - username: interview.username, - facebook: interview.facebook, - google: interview.google, - response: { accessCode: 'notsure', household: { size: 0 }, _isCompleted: undefined, _validationComment: undefined }, - audits: interview.audits - })); + expect(interviewStatus).toEqual( + expect.objectContaining({ + id: interview.id, + uuid: interview.uuid, + is_valid: interview.is_valid, + is_validated: undefined, + is_completed: interview.is_completed, + username: interview.username, + facebook: interview.facebook, + google: interview.google, + response: { accessCode: 'notsure', household: { size: 0 }, _isCompleted: undefined, _validationComment: undefined }, + audits: interview.audits + }) + ); }); }); @@ -120,17 +126,19 @@ describe('Validation List Filter', () => { commonProjectConfig.hasAccessCode = true; const interviewStatus = projectConfig.validationListFilter(nullResponseInterview as unknown as InterviewListAttributes); - expect(interviewStatus).toEqual(expect.objectContaining({ - id: interview.id, - uuid: interview.uuid, - is_valid: interview.is_valid, - is_validated: undefined, - is_completed: interview.is_completed, - username: interview.username, - facebook: interview.facebook, - google: interview.google, - response: { accessCode: undefined, household: { size: undefined }, _isCompleted: undefined, _validationComment: undefined } - })); + expect(interviewStatus).toEqual( + expect.objectContaining({ + id: interview.id, + uuid: interview.uuid, + is_valid: interview.is_valid, + is_validated: undefined, + is_completed: interview.is_completed, + username: interview.username, + facebook: interview.facebook, + google: interview.google, + response: { accessCode: undefined, household: { size: undefined }, _isCompleted: undefined, _validationComment: undefined } + }) + ); }); }); }); @@ -142,26 +150,30 @@ describe('Validation List Filter', () => { commonProjectConfig.hasAccessCode = true; - setProjectConfig({ validationListFilter: (interview: InterviewListAttributes) => { - const status = defaultConfig.validationListFilter(interview) as InterviewStatusAttributesBase; - // Add custom field to response - (status.response as unknown as { foo: string }).foo = (interview.response as unknown as { foo: string }).foo; - return status; - } }); + setProjectConfig({ + validationListFilter: (interview: InterviewListAttributes) => { + const status = defaultConfig.validationListFilter(interview) as InterviewStatusAttributesBase; + // Add custom field to response + (status.response as unknown as { foo: string }).foo = (interview.response as unknown as { foo: string }).foo; + return status; + } + }); const interviewStatus = projectConfig.validationListFilter(interview); - expect(interviewStatus).toEqual(expect.objectContaining({ - id: interview.id, - uuid: interview.uuid, - is_valid: interview.is_valid, - is_validated: undefined, - is_completed: interview.is_completed, - username: interview.username, - facebook: interview.facebook, - google: interview.google, - response: { accessCode: 'notsure', foo: 'bar', household: { size: 0 }, _isCompleted: undefined, _validationComment: undefined }, - audits: interview.audits - })); + expect(interviewStatus).toEqual( + expect.objectContaining({ + id: interview.id, + uuid: interview.uuid, + is_valid: interview.is_valid, + is_validated: undefined, + is_completed: interview.is_completed, + username: interview.username, + facebook: interview.facebook, + google: interview.google, + response: { accessCode: 'notsure', foo: 'bar', household: { size: 0 }, _isCompleted: undefined, _validationComment: undefined }, + audits: interview.audits + }) + ); }); }); }); @@ -194,11 +206,7 @@ describe('Transition API configuration', () => { setProjectConfig({ // Nothing to set }); - expect(projectConfig.transitionApi).toEqual({ - url: 'https://transition.from.env', - username: 'username.env', - password: 'password.env' - }); + expect(projectConfig.transitionApi).toEqual({ url: 'https://transition.from.env', username: 'username.env', password: 'password.env' }); // Cleanup delete process.env.TRANSITION_API_URL; @@ -233,18 +241,8 @@ describe('Transition API configuration', () => { const { default: projectConfig, setProjectConfig } = await import('../projectConfig'); - setProjectConfig({ - transitionApi: { - url: 'http://transition', - username: 'user', - password: 'password' - } - }); - expect(projectConfig.transitionApi).toEqual({ - url: 'http://transition', - username: 'user', - password: 'password' - }); + setProjectConfig({ transitionApi: { url: 'http://transition', username: 'user', password: 'password' } }); + expect(projectConfig.transitionApi).toEqual({ url: 'http://transition', username: 'user', password: 'password' }); }); }); @@ -256,18 +254,8 @@ describe('Transition API configuration', () => { const { default: projectConfig, setProjectConfig } = await import('../projectConfig'); - setProjectConfig({ - transitionApi: { - url: 'http://transition', - username: 'user', - password: 'password' - } - }); - expect(projectConfig.transitionApi).toEqual({ - url: 'http://transition', - username: 'user', - password: 'password' - }); + setProjectConfig({ transitionApi: { url: 'http://transition', username: 'user', password: 'password' } }); + expect(projectConfig.transitionApi).toEqual({ url: 'http://transition', username: 'user', password: 'password' }); // Cleanup delete process.env.TRANSITION_API_URL; diff --git a/packages/evolution-backend/src/models/__tests__/adminViews.db.test.ts b/packages/evolution-backend/src/models/__tests__/adminViews.db.test.ts index d8243c97c..28d3307ef 100644 --- a/packages/evolution-backend/src/models/__tests__/adminViews.db.test.ts +++ b/packages/evolution-backend/src/models/__tests__/adminViews.db.test.ts @@ -14,23 +14,11 @@ import interviewsDbQueries from '../interviews.db.queries'; const viewName = 'test_view'; -const localParticipant = { - id: 1, - email: 'test@transition.city', - is_valid: true -}; - -const otherParticipant = { - id: 2, - is_valid: true, - google_id: '1234' -}; - -const participant3 = { - id: 3, - is_valid: true, - email: 'test@example.org', -}; +const localParticipant = { id: 1, email: 'test@transition.city', is_valid: true }; + +const otherParticipant = { id: 2, is_valid: true, google_id: '1234' }; + +const participant3 = { id: 3, is_valid: true, email: 'test@example.org' }; const localUserInterviewAttributes = { uuid: uuidV4(), @@ -38,10 +26,7 @@ const localUserInterviewAttributes = { is_valid: false, is_active: true, is_completed: undefined, - response: { - accessCode: '11111', - booleanField: true, - }, + response: { accessCode: '11111', booleanField: true }, validations: {}, audits: { errorOne: 3, errorThree: 1 } } as any; @@ -59,7 +44,7 @@ beforeAll(async () => { await interviewsDbQueries.create(localUserInterviewAttributes); }); -afterAll(async() => { +afterAll(async () => { await truncate(knex, 'sv_materialized_views'); await truncate(knex, 'sv_interviews'); await truncate(knex, 'users'); @@ -69,7 +54,6 @@ afterAll(async() => { }); describe('registerView', () => { - const defaultViewQuery = `select i.id, i.uuid, i.participant_id, @@ -83,31 +67,31 @@ describe('registerView', () => { from sv_interviews i inner join sv_participants part on i.participant_id = part.id`; - test('Register a new view', async() => { + test('Register a new view', async () => { expect(await dbQueries.registerView(viewName, defaultViewQuery)).toEqual(true); const data = await knex(viewName).select('*'); - expect(data).toEqual([{ - uuid: localUserInterviewAttributes.uuid, - id: expect.anything(), - participant_id: localUserInterviewAttributes.participant_id, - is_valid: localUserInterviewAttributes.is_valid, - is_completed: null, - auth_method: 'email' - }]); + expect(data).toEqual([ + { + uuid: localUserInterviewAttributes.uuid, + id: expect.anything(), + participant_id: localUserInterviewAttributes.participant_id, + is_valid: localUserInterviewAttributes.is_valid, + is_completed: null, + auth_method: 'email' + } + ]); expect(await dbQueries.viewExists(viewName)).toEqual(true); }); - test('Register a new view, with SQL error', async() => { + test('Register a new view, with SQL error', async () => { const errorViewName = 'errorView'; // Add a comma at the end of the view sql - await expect(dbQueries.registerView(errorViewName, `${defaultViewQuery},`)) - .rejects - .toThrow(expect.anything()); + await expect(dbQueries.registerView(errorViewName, `${defaultViewQuery},`)).rejects.toThrow(expect.anything()); // The view should not exist in the database expect(await dbQueries.viewExists(errorViewName)).toEqual(false); }); - test('Register an existing view, with same query', async() => { + test('Register an existing view, with same query', async () => { const currentView = await knex('sv_materialized_views').where('view_name', viewName); expect(await dbQueries.registerView(viewName, defaultViewQuery)).toEqual(true); @@ -115,36 +99,38 @@ describe('registerView', () => { const viewAfterRegister = await knex('sv_materialized_views').where('view_name', viewName); expect(viewAfterRegister[0].updated_at).toEqual(currentView[0].updated_at); - expect(await knex(viewName).select('*')).toEqual([{ - uuid: localUserInterviewAttributes.uuid, - id: expect.anything(), - participant_id: localUserInterviewAttributes.participant_id, - is_valid: localUserInterviewAttributes.is_valid, - is_completed: null, - auth_method: 'email' - }]); + expect(await knex(viewName).select('*')).toEqual([ + { + uuid: localUserInterviewAttributes.uuid, + id: expect.anything(), + participant_id: localUserInterviewAttributes.participant_id, + is_valid: localUserInterviewAttributes.is_valid, + is_completed: null, + auth_method: 'email' + } + ]); expect(await dbQueries.viewExists(viewName)).toEqual(true); }); - test('Register an existing view, with a SQL error', async() => { + test('Register an existing view, with a SQL error', async () => { // Add a comma at the end of the view sql - await expect(dbQueries.registerView(viewName, `${defaultViewQuery},`)) - .rejects - .toThrow(expect.anything()); + await expect(dbQueries.registerView(viewName, `${defaultViewQuery},`)).rejects.toThrow(expect.anything()); // The view should still exist in the database and be equal to the previous expect(await dbQueries.viewExists(viewName)).toEqual(true); - expect(await knex(viewName).select('*')).toEqual([{ - uuid: localUserInterviewAttributes.uuid, - id: expect.anything(), - participant_id: localUserInterviewAttributes.participant_id, - is_valid: localUserInterviewAttributes.is_valid, - is_completed: null, - auth_method: 'email' - }]); + expect(await knex(viewName).select('*')).toEqual([ + { + uuid: localUserInterviewAttributes.uuid, + id: expect.anything(), + participant_id: localUserInterviewAttributes.participant_id, + is_valid: localUserInterviewAttributes.is_valid, + is_completed: null, + auth_method: 'email' + } + ]); }); - test('Register an existing view, with a different query', async() => { + test('Register an existing view, with a different query', async () => { // Use the same query, but rename the fields const defaultViewQuery = `select i.id, i.uuid, @@ -160,18 +146,20 @@ describe('registerView', () => { inner join sv_participants part on i.participant_id = part.id`; expect(await dbQueries.registerView(viewName, defaultViewQuery)).toEqual(true); - expect(await knex(viewName).select('*')).toEqual([{ - uuid: localUserInterviewAttributes.uuid, - id: expect.anything(), - part_id: localUserInterviewAttributes.participant_id, - valid: localUserInterviewAttributes.is_valid, - completed: null, - auth: 'email' - }]); + expect(await knex(viewName).select('*')).toEqual([ + { + uuid: localUserInterviewAttributes.uuid, + id: expect.anything(), + part_id: localUserInterviewAttributes.participant_id, + valid: localUserInterviewAttributes.is_valid, + completed: null, + auth: 'email' + } + ]); expect(await dbQueries.viewExists(viewName)).toEqual(true); }); - test('Register an existing view, with a single unique field', async() => { + test('Register an existing view, with a single unique field', async () => { // Use the same query, but rename the fields const defaultViewQuery = `select i.id, i.uuid, @@ -187,23 +175,22 @@ describe('registerView', () => { inner join sv_participants part on i.participant_id = part.id`; expect(await dbQueries.registerView(viewName, defaultViewQuery, 'id')).toEqual(true); - expect(await knex(viewName).select('*')).toEqual([{ - uuid: localUserInterviewAttributes.uuid, - id: expect.anything(), - part_id: localUserInterviewAttributes.participant_id, - valid: localUserInterviewAttributes.is_valid, - completed: null, - auth: 'email' - }]); - expect(await knex('sv_materialized_views').select('*').where('view_name', viewName)).toEqual([expect.objectContaining({ - view_name: viewName, - view_query: defaultViewQuery, - unique_field: 'id', - })]); - + expect(await knex(viewName).select('*')).toEqual([ + { + uuid: localUserInterviewAttributes.uuid, + id: expect.anything(), + part_id: localUserInterviewAttributes.participant_id, + valid: localUserInterviewAttributes.is_valid, + completed: null, + auth: 'email' + } + ]); + expect(await knex('sv_materialized_views').select('*').where('view_name', viewName)).toEqual([ + expect.objectContaining({ view_name: viewName, view_query: defaultViewQuery, unique_field: 'id' }) + ]); }); - test('Register an existing view, with multiple unique fields', async() => { + test('Register an existing view, with multiple unique fields', async () => { // Use the same query, but rename the fields const defaultViewQuery = `select i.id, i.uuid, @@ -219,22 +206,20 @@ describe('registerView', () => { inner join sv_participants part on i.participant_id = part.id`; expect(await dbQueries.registerView(viewName, defaultViewQuery, ['id', 'uuid'])).toEqual(true); - expect(await knex(viewName).select('*')).toEqual([{ - uuid: localUserInterviewAttributes.uuid, - id: expect.anything(), - part_id: localUserInterviewAttributes.participant_id, - valid: localUserInterviewAttributes.is_valid, - completed: null, - auth: 'email' - }]); - expect(await knex('sv_materialized_views').select('*').where('view_name', viewName)).toEqual([expect.objectContaining({ - view_name: viewName, - view_query: defaultViewQuery, - unique_field: 'id, uuid' - })]); - + expect(await knex(viewName).select('*')).toEqual([ + { + uuid: localUserInterviewAttributes.uuid, + id: expect.anything(), + part_id: localUserInterviewAttributes.participant_id, + valid: localUserInterviewAttributes.is_valid, + completed: null, + auth: 'email' + } + ]); + expect(await knex('sv_materialized_views').select('*').where('view_name', viewName)).toEqual([ + expect.objectContaining({ view_name: viewName, view_query: defaultViewQuery, unique_field: 'id, uuid' }) + ]); }); - }); test('Update database view', async () => { @@ -272,13 +257,12 @@ test('Update database view', async () => { await dbQueries.refreshAllViews(); const data4 = await knex(viewName).select('*'); expect(data4.length).toEqual(originalCount + 2); - }); describe('Query view', () => { const dataCount = 3; - test('Query all fields', async() => { + test('Query all fields', async () => { const data = await dbQueries.queryView(viewName); expect(data).not.toEqual(false); expect((data as any[]).length).toEqual(dataCount); @@ -292,58 +276,44 @@ describe('Query view', () => { }); }); - test('Query only a subset of the fields', async() => { + test('Query only a subset of the fields', async () => { const data = await dbQueries.queryView(viewName, ['id', 'auth']); expect(data).not.toEqual(false); expect((data as any[]).length).toEqual(dataCount); - expect(data[0]).toEqual({ - id: expect.anything(), - auth: 'email' - }); + expect(data[0]).toEqual({ id: expect.anything(), auth: 'email' }); }); - test('Query an unexisting view', async() => { + test('Query an unexisting view', async () => { const data = await dbQueries.queryView('unexistingView'); expect(data).toEqual(false); }); - test('Query unexisting fields', async() => { + test('Query unexisting fields', async () => { const data = await dbQueries.queryView(viewName, ['id', 'unexisting']); expect(data).toEqual(false); }); - }); describe('Count by', () => { - const dataCount = 3; - test('Count by one field', async() => { + test('Count by one field', async () => { const data = await dbQueries.countByView(viewName, ['valid']); expect(data).not.toEqual(false); - expect(data).toEqual([{ - valid: localUserInterviewAttributes.is_valid, - count: dataCount - }]); + expect(data).toEqual([{ valid: localUserInterviewAttributes.is_valid, count: dataCount }]); }); - test('Count by 2 fields', async() => { + test('Count by 2 fields', async () => { const data = await dbQueries.countByView(viewName, ['id', 'auth']); expect(data).not.toEqual(false); expect((data as any[]).length).toEqual(dataCount); for (let i = 0; i < (data as any[]).length; i++) { - switch(data[i].auth) { - case 'email': expect(data[i]).toEqual({ - id: expect.anything(), - auth: 'email', - count: 1 - }); + switch (data[i].auth) { + case 'email': + expect(data[i]).toEqual({ id: expect.anything(), auth: 'email', count: 1 }); break; - case 'google': expect(data[i]).toEqual({ - id: expect.anything(), - auth: 'google', - count: 1 - }); + case 'google': + expect(data[i]).toEqual({ id: expect.anything(), auth: 'google', count: 1 }); break; default: // Unexpected data @@ -352,14 +322,13 @@ describe('Count by', () => { } }); - test('Count on an unexisting view', async() => { + test('Count on an unexisting view', async () => { const data = await dbQueries.countByView('unexistingView', ['valid']); expect(data).toEqual(false); }); - test('Count on unexisting fields', async() => { + test('Count on unexisting fields', async () => { const data = await dbQueries.countByView(viewName, ['id', 'unexisting']); expect(data).toEqual(false); }); - }); diff --git a/packages/evolution-backend/src/models/__tests__/audits.db.test.ts b/packages/evolution-backend/src/models/__tests__/audits.db.test.ts index 62d313b3f..bb3e4889d 100644 --- a/packages/evolution-backend/src/models/__tests__/audits.db.test.ts +++ b/packages/evolution-backend/src/models/__tests__/audits.db.test.ts @@ -13,43 +13,21 @@ import dbQueries from '../audits.db.queries'; import interviewsDbQueries from '../interviews.db.queries'; import { AuditForObject } from 'evolution-common/lib/services/audits/types'; -const localParticipant = { - id: 1, - email: 'test@transition.city', - is_valid: true -}; +const localParticipant = { id: 1, email: 'test@transition.city', is_valid: true }; -const otherParticipant = { - id: 2, - is_valid: true, - google_id: '1234' -}; +const otherParticipant = { id: 2, is_valid: true, google_id: '1234' }; const baseInterviewAttributes = { is_valid: false, is_active: true, is_completed: undefined, - response: { - accessCode: '11111', - booleanField: true, - }, - validations: {}, + response: { accessCode: '11111', booleanField: true }, + validations: {} }; -const localUserInterviewAttributes = { - id: 1, - uuid: uuidV4(), - participant_id: localParticipant.id, - ...baseInterviewAttributes -}; - -const otherUserInterviewAttributes = { - id: 2, - uuid: uuidV4(), - participant_id: otherParticipant.id, - ...baseInterviewAttributes -}; +const localUserInterviewAttributes = { id: 1, uuid: uuidV4(), participant_id: localParticipant.id, ...baseInterviewAttributes }; +const otherUserInterviewAttributes = { id: 2, uuid: uuidV4(), participant_id: otherParticipant.id, ...baseInterviewAttributes }; beforeAll(async () => { jest.setTimeout(10000); @@ -62,7 +40,7 @@ beforeAll(async () => { await interviewsDbQueries.create(otherUserInterviewAttributes as any); }); -afterAll(async() => { +afterAll(async () => { await truncate(knex, 'sv_audits'); await truncate(knex, 'sv_interviews'); await truncate(knex, 'sv_participants'); @@ -81,73 +59,69 @@ const removeUndefined = (obj) => { const person1Uuid = uuidV4(); const person2Uuid = uuidV4(); -const audits = [{ - objectType: 'interview', - objectUuid: localUserInterviewAttributes.uuid, - version: 2, - level: 'warning', - errorCode: 'InterviewErrorCode1', - message: 'ThisOneHasAMessage', - ignore: false -}, { - objectType: 'interview', - objectUuid: localUserInterviewAttributes.uuid, - version: 2, - level: 'error', - errorCode: 'InterviewErrorCodeMinimal', -}, { - objectType: 'person', - objectUuid: person1Uuid, - version: 2, - level: 'error', - errorCode: 'PersonErrorCode1', - message: 'WithAMessage', - ignore: false -}, { - objectType: 'person', - objectUuid: person2Uuid, - version: 2, - level: 'error', - errorCode: 'PersonErrorCode1', - message: 'WithAMessage', - ignore: false -}] as AuditForObject[]; - -const expected = [ - audits[0], { - ...audits[1], +const audits = [ + { + objectType: 'interview', + objectUuid: localUserInterviewAttributes.uuid, + version: 2, + level: 'warning', + errorCode: 'InterviewErrorCode1', + message: 'ThisOneHasAMessage', + ignore: false + }, + { objectType: 'interview', objectUuid: localUserInterviewAttributes.uuid, version: 2, level: 'error', errorCode: 'InterviewErrorCodeMinimal' }, + { + objectType: 'person', + objectUuid: person1Uuid, + version: 2, + level: 'error', + errorCode: 'PersonErrorCode1', + message: 'WithAMessage', + ignore: false + }, + { + objectType: 'person', + objectUuid: person2Uuid, + version: 2, + level: 'error', + errorCode: 'PersonErrorCode1', + message: 'WithAMessage', + ignore: false + } +] as AuditForObject[]; + +const expected = [audits[0], { ...audits[1], ignore: false }, audits[2], audits[3]]; + +const newAudits = [ + { + objectType: 'person', + objectUuid: person1Uuid, + version: 2, + level: 'warning' as const, + errorCode: 'NewPersonErrorCode1', + message: 'NewMessage1', ignore: false }, - audits[2], - audits[3] + { + objectType: 'person', + objectUuid: person2Uuid, + version: 2, + level: 'info' as const, + errorCode: 'NewPersonErrorCode1', + message: 'NewMEssage2', + ignore: false + } ]; -const newAudits = [{ - objectType: 'person', - objectUuid: person1Uuid, - version: 2, - level: 'warning' as const, - errorCode: 'NewPersonErrorCode1', - message: 'NewMessage1', - ignore: false -}, { - objectType: 'person', - objectUuid: person2Uuid, - version: 2, - level: 'info' as const, - errorCode: 'NewPersonErrorCode1', - message: 'NewMEssage2', - ignore: false -}]; - -const auditsMatch = (audit1, audit2) => audit1.objectType === audit2.objectType && audit1.objectUuid === audit2.objectUuid && audit1.errorCode === audit2.errorCode; - -test('Set a few audits for an interview', async() => { +const auditsMatch = (audit1, audit2) => + audit1.objectType === audit2.objectType && audit1.objectUuid === audit2.objectUuid && audit1.errorCode === audit2.errorCode; + +test('Set a few audits for an interview', async () => { const result = await dbQueries.setAuditsForInterview(localUserInterviewAttributes.id, audits); expect(result).toBeTruthy(); }); -test('Get the audits for the previous interview', async() => { +test('Get the audits for the previous interview', async () => { const dbAudits = await dbQueries.getAuditsForInterview(localUserInterviewAttributes.id); expect(dbAudits.length).toEqual(audits.length); @@ -157,12 +131,12 @@ test('Get the audits for the previous interview', async() => { } }); -test('Add same audits to other interview', async() => { +test('Add same audits to other interview', async () => { const result = await dbQueries.setAuditsForInterview(otherUserInterviewAttributes.id, audits); expect(result).toBeTruthy(); }); -test('Get the audits for both interviews', async() => { +test('Get the audits for both interviews', async () => { const dbAuditsLocal = await dbQueries.getAuditsForInterview(localUserInterviewAttributes.id); expect(dbAuditsLocal.length).toEqual(audits.length); @@ -180,7 +154,7 @@ test('Get the audits for both interviews', async() => { } }); -test('Set new audits for interview', async() => { +test('Set new audits for interview', async () => { const result = await dbQueries.setAuditsForInterview(otherUserInterviewAttributes.id, newAudits); expect(result).toBeTruthy(); @@ -194,21 +168,13 @@ test('Set new audits for interview', async() => { } }); -test('Set new audits for interview, with errors', async() => { - - const errorAudits = [{ - objectType: 'person', - objectUuid: 'notAUUID', - version: 2, - errorCode: 'NewPersonErrorCode1', - message: 'NewMessage1', - ignore: false - }]; +test('Set new audits for interview, with errors', async () => { + const errorAudits = [ + { objectType: 'person', objectUuid: 'notAUUID', version: 2, errorCode: 'NewPersonErrorCode1', message: 'NewMessage1', ignore: false } + ]; // The query should throw an error and the audits should not be modified - await expect(dbQueries.setAuditsForInterview(otherUserInterviewAttributes.id, errorAudits)) - .rejects - .toThrow(expect.anything()); + await expect(dbQueries.setAuditsForInterview(otherUserInterviewAttributes.id, errorAudits)).rejects.toThrow(expect.anything()); const dbAuditsOther = await dbQueries.getAuditsForInterview(otherUserInterviewAttributes.id); expect(dbAuditsOther.length).toEqual(newAudits.length); @@ -216,10 +182,9 @@ test('Set new audits for interview, with errors', async() => { const findAudit = newAudits.find((audit) => auditsMatch(audit, removeUndefined(dbAuditsOther[i]))); expect(dbAuditsOther[i]).toEqual(findAudit); } - }); -test('Update audit', async() => { +test('Update audit', async () => { // Change the ignore status of one audit const modifiedAudit = _cloneDeep(newAudits[0]); modifiedAudit.ignore = true; @@ -243,22 +208,23 @@ test('Update audit', async() => { } }); -test('Set new audits that will be updated', async() => { +test('Set new audits that will be updated', async () => { // For first audit, use the same as newAudits, with ignore to false (should be kept to true) // For second audit, replace with a new code // A third audit is added for an object not present the first time - const newAuditsToUpdate = [newAudits[0], { - ...newAudits[1], - errorCode: 'this-is-a-new-error' - }, { - objectType: 'newObject', - objectUuid: person1Uuid, - version: 2, - level: 'error' as const, - errorCode: 'NewObjectError', - message: 'NewMEssage2', - ignore: false - }]; + const newAuditsToUpdate = [ + newAudits[0], + { ...newAudits[1], errorCode: 'this-is-a-new-error' }, + { + objectType: 'newObject', + objectUuid: person1Uuid, + version: 2, + level: 'error' as const, + errorCode: 'NewObjectError', + message: 'NewMEssage2', + ignore: false + } + ]; // Prepare the modified audit and expected results const modifiedAudit = _cloneDeep(newAudits[0]); modifiedAudit.ignore = true; diff --git a/packages/evolution-backend/src/models/__tests__/interviews.db.test.ts b/packages/evolution-backend/src/models/__tests__/interviews.db.test.ts index 87003a3f4..7b1151de6 100644 --- a/packages/evolution-backend/src/models/__tests__/interviews.db.test.ts +++ b/packages/evolution-backend/src/models/__tests__/interviews.db.test.ts @@ -16,70 +16,25 @@ import { InterviewAttributes, InterviewListAttributes, InterviewResponse } from const permission1 = 'role1'; const permission2 = 'role2'; -const localUser = { - id: 1, - email: 'test@transition.city', - is_valid: true -}; - -const localUserWithPermission = { - ...localUser, - uuid: uuidV4(), - permissions: { - [permission1]: true - } -}; - -const facebookParticipant = { - id: 2, - facebook_id: 'facebookId', - is_valid: true -}; - -const googleParticipant = { - id: 3, - google_id: 'googleId', - is_valid: true -}; - -const googleParticipant2 = { - id: 300, - google_id: 'googleId2', - is_valid: true -}; - -const googleParticipant3 = { - id: 310, - google_id: 'googleId3', - is_valid: true -}; - -const googleParticipant4 = { - id: 320, - google_id: 'googleId4', - is_valid: true -}; - -const localUser2 = { - id: 4, - email: 'test2@transition.city', - is_valid: true -}; - -const localUser2WithPermission = { - ...localUser2, - uuid: uuidV4(), - permissions: { - [permission1]: true, - [permission2]: true - } -}; - -const interviewerParticipant = { - id: 330, - username: `${INTERVIEWER_PARTICIPANT_PREFIX}_1234`, - is_valid: true -}; +const localUser = { id: 1, email: 'test@transition.city', is_valid: true }; + +const localUserWithPermission = { ...localUser, uuid: uuidV4(), permissions: { [permission1]: true } }; + +const facebookParticipant = { id: 2, facebook_id: 'facebookId', is_valid: true }; + +const googleParticipant = { id: 3, google_id: 'googleId', is_valid: true }; + +const googleParticipant2 = { id: 300, google_id: 'googleId2', is_valid: true }; + +const googleParticipant3 = { id: 310, google_id: 'googleId3', is_valid: true }; + +const googleParticipant4 = { id: 320, google_id: 'googleId4', is_valid: true }; + +const localUser2 = { id: 4, email: 'test2@transition.city', is_valid: true }; + +const localUser2WithPermission = { ...localUser2, uuid: uuidV4(), permissions: { [permission1]: true, [permission2]: true } }; + +const interviewerParticipant = { id: 330, username: `${INTERVIEWER_PARTICIPANT_PREFIX}_1234`, is_valid: true }; const localUserInterviewAttributes = { uuid: uuidV4(), @@ -88,11 +43,8 @@ const localUserInterviewAttributes = { is_active: true, is_frozen: false, is_completed: undefined, - response: { - accessCode: '11111', - booleanField: true - }, - validations: {}, + response: { accessCode: '11111', booleanField: true }, + validations: {} } as any; const facebookUserInterviewAttributes = { @@ -101,14 +53,8 @@ const facebookUserInterviewAttributes = { is_valid: undefined, is_active: false, is_completed: false, - response: { - accessCode: '22222', - home: { - someField: 'somewhere', - otherField: '1234 Test Street West' - } - }, - validations: {}, + response: { accessCode: '22222', home: { someField: 'somewhere', otherField: '1234 Test Street West' } }, + validations: {} } as any; const googleUserInterviewAttributes = { @@ -117,23 +63,9 @@ const googleUserInterviewAttributes = { is_valid: true, is_completed: true, is_active: true, - response: { - accessCode: '33333', - home: { - someField: 'somewhere', - otherField: 'Third stop on the right', - arrayField: ['foo', 'bar'] - } - }, + response: { accessCode: '33333', home: { someField: 'somewhere', otherField: 'Third stop on the right', arrayField: ['foo', 'bar'] } }, validations: {}, - corrected_response: { - accessCode: '2222', - home: { - someField: 'corrected', - otherField: 'changed', - arrayField: ['foo', 'bar'] - } - }, + corrected_response: { accessCode: '2222', home: { someField: 'corrected', otherField: 'changed', arrayField: ['foo', 'bar'] } } } as any; beforeAll(async () => { @@ -166,27 +98,14 @@ afterAll(async () => { }); describe('create new interviews', () => { - test('create new interview, default returning fields', async () => { - const newInterviewAttributes = { - participant_id: googleParticipant4.id, - response: {}, - validations: {}, - is_active: true - }; + const newInterviewAttributes = { participant_id: googleParticipant4.id, response: {}, validations: {}, is_active: true }; const returning = await dbQueries.create(newInterviewAttributes); - expect(returning).toEqual({ - id: expect.anything() - }); + expect(returning).toEqual({ id: expect.anything() }); }); test('create new interview, returning a few fields', async () => { - const newInterviewAttributes = { - participant_id: googleParticipant2.id, - response: {}, - validations: {}, - is_active: true - }; + const newInterviewAttributes = { participant_id: googleParticipant2.id, response: {}, validations: {}, is_active: true }; const returning = await dbQueries.create(newInterviewAttributes, ['uuid', 'id', 'response', 'participant_id']); expect(returning).toEqual({ uuid: expect.anything(), @@ -197,24 +116,14 @@ describe('create new interviews', () => { }); test('create new interview, returning single field', async () => { - const newInterviewAttributes = { - participant_id: googleParticipant3.id, - response: { foo: 'bar' }, - validations: {}, - is_active: true - }; + const newInterviewAttributes = { participant_id: googleParticipant3.id, response: { foo: 'bar' }, validations: {}, is_active: true }; const returning = await dbQueries.create(newInterviewAttributes as any, 'response'); - expect(returning).toEqual({ - response: newInterviewAttributes.response - }); + expect(returning).toEqual({ response: newInterviewAttributes.response }); }); - }); describe('find by response', () => { - test('Get an existing interview by access code', async () => { - const interviews = await dbQueries.findByResponse({ accessCode: localUserInterviewAttributes.response.accessCode }); expect(interviews.length).toEqual(1); expect(interviews[0]).toEqual({ @@ -233,14 +142,11 @@ describe('find by response', () => { }); test('Unexisting access code', async () => { - const interviews = await dbQueries.findByResponse({ accessCode: 'wrong' }); expect(interviews.length).toEqual(0); - }); test('Common deep field', async () => { - const interviews = await dbQueries.findByResponse({ home: { someField: 'somewhere' } }); expect(interviews.length).toEqual(2); expect(interviews[0]).toEqual({ @@ -269,12 +175,13 @@ describe('find by response', () => { facebook: false, google: true }); - }); test('Multiple fields', async () => { - - const interviews = await dbQueries.findByResponse({ accessCode: facebookUserInterviewAttributes.response.accessCode, home: { someField: 'somewhere' } }); + const interviews = await dbQueries.findByResponse({ + accessCode: facebookUserInterviewAttributes.response.accessCode, + home: { someField: 'somewhere' } + }); expect(interviews.length).toEqual(1); expect(interviews[0]).toEqual({ id: expect.anything(), @@ -289,16 +196,12 @@ describe('find by response', () => { facebook: true, google: false }); - }); test('Unexisting fields', async () => { - const interviews = await dbQueries.findByResponse({ unexisting: { field: 'somewhere' } }); expect(interviews.length).toEqual(0); - }); - }); describe('Get interview and ID by interview uuid', () => { @@ -317,12 +220,8 @@ describe('Get interview and ID by interview uuid', () => { }); test('Not a valid uuid', async () => { - await expect(dbQueries.getInterviewByUuid('not a valid uuid')) - .rejects - .toThrow(); - await expect(dbQueries.getInterviewIdByUuid('not a valid uuid')) - .rejects - .toThrow(); + await expect(dbQueries.getInterviewByUuid('not a valid uuid')).rejects.toThrow(); + await expect(dbQueries.getInterviewIdByUuid('not a valid uuid')).rejects.toThrow(); }); }); @@ -356,7 +255,6 @@ describe('Get interview by user id', () => { }); describe('Update Interview', () => { - test('Update response and validations', async () => { const addAttributes = { response: { foo: 'test' }, validations: { accessCode: true, other: 'data' } }; const newAttributes = { @@ -367,7 +265,7 @@ describe('Update Interview', () => { expect(interview.uuid).toEqual(localUserInterviewAttributes.uuid); // Re-read the interview - const updateInterview = await dbQueries.getInterviewByUuid(localUserInterviewAttributes.uuid) as InterviewAttributes; + const updateInterview = (await dbQueries.getInterviewByUuid(localUserInterviewAttributes.uuid)) as InterviewAttributes; expect(updateInterview.response).toEqual(newAttributes.response); expect(updateInterview.validations).toEqual(newAttributes.validations); }); @@ -378,29 +276,23 @@ describe('Update Interview', () => { response: Object.assign({}, localUserInterviewAttributes.response, addAttributes.response), validations: Object.assign({}, localUserInterviewAttributes.validations, addAttributes.validations) }; - await expect(dbQueries.update('not a uuid', newAttributes, 'uuid')) - .rejects - .toThrow(expect.anything()); + await expect(dbQueries.update('not a uuid', newAttributes, 'uuid')).rejects.toThrow(expect.anything()); }); test('Invalid null unicode character in json data', async () => { const addAttributes = { response: { name: 'McDonald\u0000\u0000\u0007s' } }; - const newAttributes = { - response: Object.assign({}, localUserInterviewAttributes.response, addAttributes.response), - }; + const newAttributes = { response: Object.assign({}, localUserInterviewAttributes.response, addAttributes.response) }; const interview = await dbQueries.update(localUserInterviewAttributes.uuid, newAttributes, 'uuid'); expect(interview.uuid).toEqual(localUserInterviewAttributes.uuid); // Re-read the interview and make sure it does not contain the null, but other unicode characters are ok - const updateInterview = await dbQueries.getInterviewByUuid(localUserInterviewAttributes.uuid) as any; + const updateInterview = (await dbQueries.getInterviewByUuid(localUserInterviewAttributes.uuid)) as any; expect(updateInterview.response.name).not.toContain('\u0000'); expect(updateInterview.response.name).toContain('\u0007'); }); - }); describe('list interviews', () => { - // There are 6 interviews in the DB, but one, facebookUserInterviewAttributes, is invalid const nbActiveInterviews = 5; @@ -439,13 +331,21 @@ describe('list interviews', () => { // There is 1 valid interview, 1 invalid and 3 with no validity value // Get valid interviews - const { interviews: filterValid, totalCount: countValid } = await dbQueries.getList({ filters: { is_valid: { value: true } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterValid, totalCount: countValid } = await dbQueries.getList({ + filters: { is_valid: { value: true } }, + pageIndex: 0, + pageSize: -1 + }); expect(countValid).toEqual(1); expect(filterValid.length).toEqual(1); expect(filterValid[0].is_valid).toEqual(true); // Get not valid interviews, should return invalid and undefined ones - const { interviews: filterNotValid, totalCount: countNotValid } = await dbQueries.getList({ filters: { is_valid: { value: true, op: 'not' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterNotValid, totalCount: countNotValid } = await dbQueries.getList({ + filters: { is_valid: { value: true, op: 'not' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countNotValid).toEqual(4); expect(filterNotValid.length).toEqual(4); for (let i = 0; i < 4; i++) { @@ -453,14 +353,22 @@ describe('list interviews', () => { } // Get invalid interviews, should return only invalid ones - const { interviews: filterInvalid, totalCount: totalInvalid } = await dbQueries.getList({ filters: { is_valid: { value: false } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterInvalid, totalCount: totalInvalid } = await dbQueries.getList({ + filters: { is_valid: { value: false } }, + pageIndex: 0, + pageSize: -1 + }); expect(totalInvalid).toEqual(1); expect(filterInvalid.length).toEqual(1); expect(filterInvalid[0].is_valid).toEqual(false); expect(filterInvalid[0].is_valid).toEqual(false); // Get not invalid interviews, should return valids and undefined ones - const { interviews: filterNotInvalid, totalCount: coutNotInvalid } = await dbQueries.getList({ filters: { is_valid: { value: false, op: 'not' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterNotInvalid, totalCount: coutNotInvalid } = await dbQueries.getList({ + filters: { is_valid: { value: false, op: 'not' } }, + pageIndex: 0, + pageSize: -1 + }); expect(coutNotInvalid).toEqual(4); expect(filterNotInvalid.length).toEqual(4); for (let i = 0; i < 4; i++) { @@ -468,19 +376,31 @@ describe('list interviews', () => { } // Get valid interviews, using booleish string value - const { interviews: filterValidBooleish1, totalCount: countValidBooleish } = await dbQueries.getList({ filters: { is_valid: { value: 'y' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterValidBooleish1, totalCount: countValidBooleish } = await dbQueries.getList({ + filters: { is_valid: { value: 'y' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countValidBooleish).toEqual(1); expect(filterValidBooleish1.length).toEqual(1); expect(filterValidBooleish1[0].is_valid).toEqual(true); // Get invalid interviews, using booleish string value - const { interviews: filterInvalidBooleish2, totalCount: countInvalidBooleish } = await dbQueries.getList({ filters: { is_valid: { value: 'n' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterInvalidBooleish2, totalCount: countInvalidBooleish } = await dbQueries.getList({ + filters: { is_valid: { value: 'n' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countInvalidBooleish).toEqual(1); expect(filterInvalidBooleish2.length).toEqual(1); expect(filterInvalidBooleish2[0].is_valid).toEqual(false); // Get neither valid nor invalid - const { interviews: filterNullBooleish, totalCount: countNullBooleish } = await dbQueries.getList({ filters: { is_valid: { value: null } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterNullBooleish, totalCount: countNullBooleish } = await dbQueries.getList({ + filters: { is_valid: { value: null } }, + pageIndex: 0, + pageSize: -1 + }); expect(countNullBooleish).toEqual(3); expect(filterNullBooleish.length).toEqual(3); expect(filterNullBooleish[0].is_valid).toBeUndefined(); @@ -489,50 +409,75 @@ describe('list interviews', () => { }); test('Get lists with various filter combinations', async () => { - // Query by updated time, most are null, 1 is 0 - const { interviews: filterUpdated0, totalCount: countUpdated0 } = await dbQueries.getList({ filters: { updated_at: { value: 0, op: 'gt' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterUpdated0, totalCount: countUpdated0 } = await dbQueries.getList({ + filters: { updated_at: { value: 0, op: 'gt' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countUpdated0).toEqual(1); expect(filterUpdated0.length).toEqual(1); // Query by updated time, use now const updatedAt = moment().valueOf() / 1000; - const { interviews: filterUpdatedAfterNow, totalCount: countUpdatedAfterNow } = await dbQueries.getList({ filters: { updated_at: { value: updatedAt, op: 'gt' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterUpdatedAfterNow, totalCount: countUpdatedAfterNow } = await dbQueries.getList({ + filters: { updated_at: { value: updatedAt, op: 'gt' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countUpdatedAfterNow).toEqual(0); expect(filterUpdatedAfterNow.length).toEqual(0); // Query by creation time, use 0, sort by create data - const { interviews: filterCreate, totalCount: countCreate } = await dbQueries.getList({ filters: { created_at: { value: 0, op: 'gt' } }, pageIndex: 0, pageSize: -1, sort: ['created_at'] }); + const { interviews: filterCreate, totalCount: countCreate } = await dbQueries.getList({ + filters: { created_at: { value: 0, op: 'gt' } }, + pageIndex: 0, + pageSize: -1, + sort: ['created_at'] + }); expect(countCreate).toEqual(5); expect(filterCreate.length).toEqual(5); // Query by creation time, use second value and offset otherwise test fails sometimes const createdAt = (moment(filterCreate[2].created_at).valueOf() - 1) / 1000; - const { interviews: filterCreate2, totalCount: countCreate2 } = await dbQueries.getList({ filters: { created_at: { value: createdAt, op: 'gt' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterCreate2, totalCount: countCreate2 } = await dbQueries.getList({ + filters: { created_at: { value: createdAt, op: 'gt' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countCreate2).toEqual(3); expect(filterCreate2.length).toEqual(3); // Query by creation time range const createdAtStart = (moment(filterCreate[2].created_at).valueOf() - 1) / 1000; const createdAtEnd = (moment(filterCreate[4].created_at).valueOf() + 1) / 1000; - const { interviews: filterCreate3, totalCount: countCreate3 } = await dbQueries.getList({ filters: { created_at: { value: [createdAtStart, createdAtEnd] as any } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterCreate3, totalCount: countCreate3 } = await dbQueries.getList({ + filters: { created_at: { value: [createdAtStart, createdAtEnd] as any } }, + pageIndex: 0, + pageSize: -1 + }); expect(countCreate3).toEqual(3); expect(filterCreate3.length).toEqual(3); // Update one interview and query again by same updated time, it should return the udpated interview const addAttributes = { response: { foo: 'test' }, validations: { bar: true, other: 'data' } }; - const newAttributes = { - response: Object.assign({}, googleUserInterviewAttributes.response, addAttributes.response) - }; + const newAttributes = { response: Object.assign({}, googleUserInterviewAttributes.response, addAttributes.response) }; await dbQueries.update(googleUserInterviewAttributes.uuid, newAttributes, 'uuid'); - const { interviews: filterUpdatedAfterNow2, totalCount: countUpdatedAfterNow2 } = await dbQueries.getList({ filters: { updated_at: { value: updatedAt, op: 'gt' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterUpdatedAfterNow2, totalCount: countUpdatedAfterNow2 } = await dbQueries.getList({ + filters: { updated_at: { value: updatedAt, op: 'gt' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countUpdatedAfterNow2).toEqual(1); expect(filterUpdatedAfterNow2.length).toEqual(1); expect(filterUpdatedAfterNow2[0].uuid).toEqual(googleUserInterviewAttributes.uuid); // Query by access code not null - const { interviews: filterAccessCodeNotNull, totalCount: countAccessCodeNotNull } = - await dbQueries.getList({ filters: { 'response.accessCode': { value: null, op: 'not' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterAccessCodeNotNull, totalCount: countAccessCodeNotNull } = await dbQueries.getList({ + filters: { 'response.accessCode': { value: null, op: 'not' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countAccessCodeNotNull).toEqual(2); expect(filterAccessCodeNotNull.length).toEqual(2); for (let i = 0; i < 2; i++) { @@ -540,8 +485,11 @@ describe('list interviews', () => { } // Query by access code not null - const { interviews: filterAccessCodeNull, totalCount: countAccessCodeNull } = - await dbQueries.getList({ filters: { 'response.accessCode': { value: null } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterAccessCodeNull, totalCount: countAccessCodeNull } = await dbQueries.getList({ + filters: { 'response.accessCode': { value: null } }, + pageIndex: 0, + pageSize: -1 + }); expect(countAccessCodeNull).toEqual(3); expect(filterAccessCodeNull.length).toEqual(3); for (let i = 0; i < 2; i++) { @@ -549,43 +497,64 @@ describe('list interviews', () => { } // Query by access code not null - const { interviews: filterAccessCodeGTE, totalCount: countAccessCodeGTE } = - await dbQueries.getList({ filters: { 'response.accessCode': { value: googleUserInterviewAttributes.response.accessCode, op: 'eq' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterAccessCodeGTE, totalCount: countAccessCodeGTE } = await dbQueries.getList({ + filters: { 'response.accessCode': { value: googleUserInterviewAttributes.response.accessCode, op: 'eq' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countAccessCodeGTE).toEqual(1); expect(filterAccessCodeGTE.length).toEqual(1); expect((filterAccessCodeGTE[0].response as any).accessCode).toEqual(googleUserInterviewAttributes.response.accessCode); // Query by access code with like - const { interviews: filterAccessCodeLike, totalCount: countAccessCodeLike } = - await dbQueries.getList({ filters: { 'response.accessCode': { value: localUserInterviewAttributes.response.accessCode.substring(0, 3), op: 'like' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterAccessCodeLike, totalCount: countAccessCodeLike } = await dbQueries.getList({ + filters: { 'response.accessCode': { value: localUserInterviewAttributes.response.accessCode.substring(0, 3), op: 'like' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countAccessCodeLike).toEqual(1); expect(filterAccessCodeLike.length).toEqual(1); expect((filterAccessCodeLike[0].response as any).accessCode).toEqual(localUserInterviewAttributes.response.accessCode); // Query by response boolean - const { interviews: filterResponseBoolean, totalCount: countResponseBoolean } = - await dbQueries.getList({ filters: { 'response.booleanField': { value: 'true' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterResponseBoolean, totalCount: countResponseBoolean } = await dbQueries.getList({ + filters: { 'response.booleanField': { value: 'true' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countResponseBoolean).toEqual(1); expect(filterResponseBoolean.length).toEqual(1); expect((filterResponseBoolean[0].response as any).booleanField).toBeTruthy(); // Query with array of values without results - const { interviews: filterAccessCodeArray, totalCount: countAccessCodeArray } = - await dbQueries.getList({ filters: { 'response.accessCode': { value: [localUserInterviewAttributes.response.accessCode.substring(0, 3), '222'], op: 'like' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterAccessCodeArray, totalCount: countAccessCodeArray } = await dbQueries.getList({ + filters: { 'response.accessCode': { value: [localUserInterviewAttributes.response.accessCode.substring(0, 3), '222'], op: 'like' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countAccessCodeArray).toEqual(0); expect(filterAccessCodeArray.length).toEqual(0); // Query with array of values with results - const { interviews: filterAccessCodeArrayRes, totalCount: countAccessCodeArrayRes } = - await dbQueries.getList({ filters: { 'response.accessCode': { value: [localUserInterviewAttributes.response.accessCode.substring(0, 3), localUserInterviewAttributes.response.accessCode.substring(0, 3)], op: 'like' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterAccessCodeArrayRes, totalCount: countAccessCodeArrayRes } = await dbQueries.getList({ + filters: { + 'response.accessCode': { + value: [ + localUserInterviewAttributes.response.accessCode.substring(0, 3), + localUserInterviewAttributes.response.accessCode.substring(0, 3) + ], + op: 'like' + } + }, + pageIndex: 0, + pageSize: -1 + }); expect(countAccessCodeArrayRes).toEqual(1); expect(filterAccessCodeArrayRes.length).toEqual(1); expect((filterAccessCodeArrayRes[0].response as any).accessCode).toEqual(localUserInterviewAttributes.response.accessCode); - }); test('Combine filter and paging', async () => { - // Get not invalid interviews, with paging const filters = { is_valid: { value: false, op: 'not' as const } }; const pageSize = 2; @@ -604,7 +573,6 @@ describe('list interviews', () => { const { interviews: filterPage3, totalCount: totalCount3 } = await dbQueries.getList({ filters, pageIndex: 2, pageSize }); expect(totalCount3).toEqual(4); expect(filterPage3.length).toEqual(0); - }); test('Page index, but page size is -1, should return all', async () => { @@ -616,12 +584,7 @@ describe('list interviews', () => { test('Sort data', async () => { // Sort by is_valid - const { interviews: page, totalCount: totalCount } = await dbQueries.getList({ - filters: {}, - pageIndex: 0, - pageSize: -1, - sort: ['is_valid'] - }); + const { interviews: page, totalCount: totalCount } = await dbQueries.getList({ filters: {}, pageIndex: 0, pageSize: -1, sort: ['is_valid'] }); expect(totalCount).toEqual(nbActiveInterviews); expect(page.length).toEqual(nbActiveInterviews); @@ -662,46 +625,54 @@ describe('list interviews', () => { }); expect(totalCount3).toEqual(nbActiveInterviews); expect(page3.length).toEqual(nbActiveInterviews); - }); // Parameters for list come from external, we cannot guarantee the types test('inject bad data', async () => { // Add invalid order by, should throw an error - await expect(dbQueries.getList({ - filters: {}, - pageIndex: 0, - pageSize: -1, - sort: [{ field: 'response.accessCode', order: 'desc; select * from sv_interviews' as any }] - })) - .rejects - .toThrow('Cannot get interview list in table sv_interviews database (knex error: Invalid sort order for interview query: desc; select * from sv_interviews (DBINTO0001))'); + await expect( + dbQueries.getList({ + filters: {}, + pageIndex: 0, + pageSize: -1, + sort: [{ field: 'response.accessCode', order: 'desc; select * from sv_interviews' as any }] + }) + ).rejects.toThrow( + 'Cannot get interview list in table sv_interviews database (knex error: Invalid sort order for interview query: desc; select * from sv_interviews (DBINTO0001))' + ); // Inject bad where value - await expect(dbQueries.getList({ - filters: { 'audits': { value: 'accessCode\'; delete from sv_interviews;' } }, - pageIndex: 0, pageSize: -1 - })) - .rejects - .toThrow('Cannot get interview list in table sv_interviews database (knex error: Invalid value for where clause in sv_interviews database (DBQCR0006))'); + await expect( + dbQueries.getList({ filters: { audits: { value: 'accessCode\'; delete from sv_interviews;' } }, pageIndex: 0, pageSize: -1 }) + ).rejects.toThrow( + 'Cannot get interview list in table sv_interviews database (knex error: Invalid value for where clause in sv_interviews database (DBQCR0006))' + ); // Inject bad where operator, should use = const { interviews: page2, totalCount: totalCount2 } = await dbQueries.getList({ - filters: { 'response.accessCode': { value: googleUserInterviewAttributes.response.accessCode, op: 'eq \'something\'; select * from sv_interviews;' as any } }, - pageIndex: 0, pageSize: -1 + filters: { + 'response.accessCode': { + value: googleUserInterviewAttributes.response.accessCode, + op: 'eq \'something\'; select * from sv_interviews;' as any + } + }, + pageIndex: 0, + pageSize: -1 }); expect(totalCount2).toEqual(1); expect(page2.length).toEqual(1); expect((page2[0].response as any).accessCode).toEqual(googleUserInterviewAttributes.response.accessCode); // Inject bad where field, should throw an error - await expect(dbQueries.getList({ - filters: { 'is_valid is true; delete from sv_interviews;': { value: 'accessCode\'; delete from sv_interviews;' } }, - pageIndex: 0, pageSize: -1 - })) - .rejects - .toThrow('Cannot get interview list in table sv_interviews database (knex error: Invalid field for where clause in sv_interviews database (DBQCR0005))'); - + await expect( + dbQueries.getList({ + filters: { 'is_valid is true; delete from sv_interviews;': { value: 'accessCode\'; delete from sv_interviews;' } }, + pageIndex: 0, + pageSize: -1 + }) + ).rejects.toThrow( + 'Cannot get interview list in table sv_interviews database (knex error: Invalid field for where clause in sv_interviews database (DBQCR0005))' + ); }); test('Get list by geographic filter', async () => { @@ -726,7 +697,10 @@ describe('list interviews', () => { // Add home location in polygon for 2 interviews, and outside the polygon for 1 interview, ignore the others const { interviews, totalCount } = await dbQueries.getList({ filters: {}, pageIndex: 0, pageSize: -1 }); expect(totalCount).toBeGreaterThan(3); - const addHomeGeography = async (interview: InterviewListAttributes, homeGeography: Exclude['geography']) => { + const addHomeGeography = async ( + interview: InterviewListAttributes, + homeGeography: Exclude['geography'] + ) => { const response = { ...interview.response, home: { ...interview.response.home, geography: homeGeography } }; await dbQueries.update(interview.uuid, { response }, 'uuid'); }; @@ -748,24 +722,30 @@ describe('list interviews', () => { const inPolygonUuids = [interviews[0].uuid, interviews[1].uuid]; // Test filter on a valid geography field - const filter = { 'response.home.geography': { value: polygon } }; - const { interviews: inPolygonInterviews, totalCount: countInPolygon } = await dbQueries.getList({ filters: filter, pageIndex: 0, pageSize: -1 }); + const filter = { 'response.home.geography': { value: polygon } }; + const { interviews: inPolygonInterviews, totalCount: countInPolygon } = await dbQueries.getList({ + filters: filter, + pageIndex: 0, + pageSize: -1 + }); expect(countInPolygon).toEqual(2); expect(inPolygonInterviews.length).toEqual(2); expect(inPolygonUuids.includes(inPolygonInterviews[0].uuid)).toBeTruthy(); expect(inPolygonUuids.includes(inPolygonInterviews[1].uuid)).toBeTruthy(); // Test filter on a non-geography field - const filterInvalid = { 'response.home': { value: polygon } }; - const { interviews: invalidGeoInterview, totalCount: invalidGeoCount } = await dbQueries.getList({ filters: filterInvalid, pageIndex: 0, pageSize: -1 }); + const filterInvalid = { 'response.home': { value: polygon } }; + const { interviews: invalidGeoInterview, totalCount: invalidGeoCount } = await dbQueries.getList({ + filters: filterInvalid, + pageIndex: 0, + pageSize: -1 + }); expect(invalidGeoCount).toEqual(0); expect(invalidGeoInterview.length).toEqual(0); }); - }); describe('Queries with audits', () => { - const errorOneCode = 'errorOne'; const errorTwoCode = 'errorTwo'; const errorThreeCode = 'errorThree'; @@ -776,21 +756,74 @@ describe('Queries with audits', () => { if (firstInterview === undefined) { throw 'error getting interview 1 for audits'; } - await create(knex, 'sv_audits', undefined, { interview_id: firstInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, { returning: 'interview_id' }); - await create(knex, 'sv_audits', undefined, { interview_id: firstInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, { returning: 'interview_id' }); - await create(knex, 'sv_audits', undefined, { interview_id: firstInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, { returning: 'interview_id' }); - await create(knex, 'sv_audits', undefined, { interview_id: firstInterview.id, error_code: errorThreeCode, object_type: 'interview', object_uuid: firstInterview.uuid, version: 2 } as any, { returning: 'interview_id' }); + await create( + knex, + 'sv_audits', + undefined, + { interview_id: firstInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, + { returning: 'interview_id' } + ); + await create( + knex, + 'sv_audits', + undefined, + { interview_id: firstInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, + { returning: 'interview_id' } + ); + await create( + knex, + 'sv_audits', + undefined, + { interview_id: firstInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, + { returning: 'interview_id' } + ); + await create( + knex, + 'sv_audits', + undefined, + { + interview_id: firstInterview.id, + error_code: errorThreeCode, + object_type: 'interview', + object_uuid: firstInterview.uuid, + version: 2 + } as any, + { returning: 'interview_id' } + ); // Add 3 errors per of type one, and one of type two for another interview const secondInterview = await dbQueries.getInterviewByUuid(googleUserInterviewAttributes.uuid); if (secondInterview === undefined) { throw 'error getting interview 2 for audits'; } - await create(knex, 'sv_audits', undefined, { interview_id: secondInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, { returning: 'interview_id' }); - await create(knex, 'sv_audits', undefined, { interview_id: secondInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, { returning: 'interview_id' }); - await create(knex, 'sv_audits', undefined, { interview_id: secondInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, { returning: 'interview_id' }); - await create(knex, 'sv_audits', undefined, { interview_id: secondInterview.id, error_code: errorTwoCode, object_type: 'household', object_uuid: uuidV4(), version: 2 } as any, { returning: 'interview_id' }); - + await create( + knex, + 'sv_audits', + undefined, + { interview_id: secondInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, + { returning: 'interview_id' } + ); + await create( + knex, + 'sv_audits', + undefined, + { interview_id: secondInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, + { returning: 'interview_id' } + ); + await create( + knex, + 'sv_audits', + undefined, + { interview_id: secondInterview.id, error_code: errorOneCode, object_type: 'person', object_uuid: uuidV4(), version: 2 } as any, + { returning: 'interview_id' } + ); + await create( + knex, + 'sv_audits', + undefined, + { interview_id: secondInterview.id, error_code: errorTwoCode, object_type: 'household', object_uuid: uuidV4(), version: 2 } as any, + { returning: 'interview_id' } + ); }); afterAll(async () => { @@ -800,86 +833,63 @@ describe('Queries with audits', () => { test('Get the complete list of validation errors', async () => { const { auditStats } = await dbQueries.getValidationAuditStats({ filters: {} }); expect(auditStats.error).toEqual({ - person: [ - { - errorCode: 'errorOne', - count: 6 - } - ], - interview: [ - { - errorCode: 'errorThree', - count: 1 - } - ], - household: [ - { - errorCode: 'errorTwo', - count: 1 - } - ] + person: [{ errorCode: 'errorOne', count: 6 }], + interview: [{ errorCode: 'errorThree', count: 1 }], + household: [{ errorCode: 'errorTwo', count: 1 }] }); }); test('Get validation errors with a validity filter', async () => { const { auditStats } = await dbQueries.getValidationAuditStats({ filters: { is_valid: { value: true } } }); console.dir(auditStats, { depth: null }); - expect(auditStats.error).toEqual({ - person: [ - { - errorCode: 'errorOne', - count: 3 - } - ], - household: [ - { - errorCode: 'errorTwo', - count: 1 - } - ] - }); + expect(auditStats.error).toEqual({ person: [{ errorCode: 'errorOne', count: 3 }], household: [{ errorCode: 'errorTwo', count: 1 }] }); }); test('List interviews with audit filter', async () => { - // Query by audit - const { interviews: filterAudit, totalCount: countAudit } = - await dbQueries.getList({ filters: { 'audits': { value: 'errorThree' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterAudit, totalCount: countAudit } = await dbQueries.getList({ + filters: { audits: { value: 'errorThree' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countAudit).toEqual(1); expect(filterAudit.length).toEqual(1); expect(filterAudit[0].uuid).toEqual(localUserInterviewAttributes.uuid); - }); test('List interviews with audit filter, no result', async () => { - // Query by audit - const { interviews: filterAudit, totalCount: countAudit } = - await dbQueries.getList({ filters: { 'audits': { value: 'errorFour' } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterAudit, totalCount: countAudit } = await dbQueries.getList({ + filters: { audits: { value: 'errorFour' } }, + pageIndex: 0, + pageSize: -1 + }); expect(countAudit).toEqual(0); expect(filterAudit.length).toEqual(0); - }); test('List interviews with multiple audit filters', async () => { - // Query for errorThree and errorOne - const { interviews: filterAudit, totalCount: countAudit } = - await dbQueries.getList({ filters: { 'audits': { value: ['errorThree', 'errorOne'] } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterAudit, totalCount: countAudit } = await dbQueries.getList({ + filters: { audits: { value: ['errorThree', 'errorOne'] } }, + pageIndex: 0, + pageSize: -1 + }); expect(countAudit).toEqual(1); expect(filterAudit.length).toEqual(1); expect(filterAudit[0].uuid).toEqual(localUserInterviewAttributes.uuid); // Query for errorThree and errorTwo, no result expected - const { interviews: filterAudit2, totalCount: countAudit2 } = - await dbQueries.getList({ filters: { 'audits': { value: ['errorThree', 'errorTwo'] } }, pageIndex: 0, pageSize: -1 }); + const { interviews: filterAudit2, totalCount: countAudit2 } = await dbQueries.getList({ + filters: { audits: { value: ['errorThree', 'errorTwo'] } }, + pageIndex: 0, + pageSize: -1 + }); expect(countAudit2).toEqual(0); expect(filterAudit2.length).toEqual(0); - }); test('List interviews, validate audits', async () => { - // Query by audit const { interviews, totalCount } = await dbQueries.getList({ filters: {}, pageIndex: 0, pageSize: -1 }); expect(totalCount).toEqual(5); @@ -889,30 +899,21 @@ describe('Queries with audits', () => { const audits = interview.audits; expect(audits).toBeDefined(); expect(Object.keys(audits as any).length).toEqual(2); - expect(audits).toMatchObject({ - [errorOneCode]: 3, - [errorThreeCode]: 1 - }); + expect(audits).toMatchObject({ [errorOneCode]: 3, [errorThreeCode]: 1 }); } else if (interview.uuid === googleUserInterviewAttributes.uuid) { const audits = interview.audits; expect(audits).toBeDefined(); expect(Object.keys(audits as any).length).toEqual(2); - expect(audits).toMatchObject({ - [errorOneCode]: 3, - [errorTwoCode]: 1 - }); + expect(audits).toMatchObject({ [errorOneCode]: 3, [errorTwoCode]: 1 }); } else { const audits = interview.audits; expect(audits).toBeUndefined(); } } - }); - }); describe('stream interviews query', () => { - // There are 6 interviews in the DB const nbInterviews = 7; let i = 0; @@ -923,17 +924,19 @@ describe('stream interviews query', () => { is_valid: true, is_active: true, is_completed: undefined, - response: { - accessCode: '11111', - booleanField: true - }, - validations: {}, + response: { accessCode: '11111', booleanField: true }, + validations: {} } as any; beforeAll(async () => { // Add an interviewer from a phone interviewer, with some interview accesses const { id } = await dbQueries.create(interviewerInterviewAttributes, 'id'); - await knex('sv_interviews_accesses').insert({ interview_id: id, user_id: localUserWithPermission.id, for_validation: false, update_count: 3 }); + await knex('sv_interviews_accesses').insert({ + interview_id: id, + user_id: localUserWithPermission.id, + for_validation: false, + update_count: 3 + }); }); beforeEach(() => { @@ -942,11 +945,12 @@ describe('stream interviews query', () => { test('Get the complete list', (done) => { const queryStream = dbQueries.getInterviewsStream({ filters: {} }); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { expect(row.audits).toBeDefined(); expect(row.response).toBeDefined(); @@ -966,11 +970,12 @@ describe('stream interviews query', () => { test('Stream without audits', (done) => { const queryStream = dbQueries.getInterviewsStream({ filters: {}, select: { includeAudits: false } }); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { expect(row.audits).not.toBeDefined(); expect(row.response).toBeDefined(); @@ -990,11 +995,12 @@ describe('stream interviews query', () => { test('Stream with only response', (done) => { const queryStream = dbQueries.getInterviewsStream({ filters: {}, select: { responseType: 'participant' } }); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { expect(row.audits).toBeDefined(); expect(row.response).toBeDefined(); @@ -1009,11 +1015,12 @@ describe('stream interviews query', () => { test('Stream with only corrected_response', (done) => { const queryStream = dbQueries.getInterviewsStream({ filters: {}, select: { responseType: 'corrected' } }); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { expect(row.audits).toBeDefined(); expect(row.response).not.toBeDefined(); @@ -1028,11 +1035,12 @@ describe('stream interviews query', () => { test('Stream without response or corrected_response or audits', (done) => { const queryStream = dbQueries.getInterviewsStream({ filters: {}, select: { includeAudits: false, responseType: 'none' } }); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { expect(row.audits).not.toBeDefined(); expect(row.response).not.toBeDefined(); @@ -1047,11 +1055,12 @@ describe('stream interviews query', () => { test('Stream with both response and corrected_response', (done) => { const queryStream = dbQueries.getInterviewsStream({ filters: {}, select: { responseType: 'both' } }); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { expect(row.audits).toBeDefined(); expect(row.response).toBeDefined(); @@ -1066,11 +1075,12 @@ describe('stream interviews query', () => { test('Stream with corrected_response if available', (done) => { const queryStream = dbQueries.getInterviewsStream({ filters: {}, select: { responseType: 'correctedIfAvailable' } }); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { expect(row.audits).toBeDefined(); expect(row.response).toBeDefined(); @@ -1079,7 +1089,9 @@ describe('stream interviews query', () => { if (row.uuid === googleUserInterviewAttributes.uuid) { expect(row.response).toEqual(googleUserInterviewAttributes.corrected_response); } else { - expect('There is a unknown row with corrected_response').toEqual('Only the google participant interview should have corrected_response'); + expect('There is a unknown row with corrected_response').toEqual( + 'Only the google participant interview should have corrected_response' + ); } } else { expect(row.uuid).not.toEqual(googleUserInterviewAttributes.uuid); @@ -1098,11 +1110,12 @@ describe('stream interviews query', () => { test('Get the interviewer data', (done) => { let foundInterviewerInterview = false; const queryStream = dbQueries.getInterviewsStream({ filters: {}, select: { includeInterviewerData: true } }); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { expect(row.audits).toBeDefined(); expect(row.response).toBeDefined(); @@ -1128,5 +1141,4 @@ describe('stream interviews query', () => { done(); }); }); - }); diff --git a/packages/evolution-backend/src/models/__tests__/interviewsAccesses.db.test.ts b/packages/evolution-backend/src/models/__tests__/interviewsAccesses.db.test.ts index 31968d989..7b1b12efb 100644 --- a/packages/evolution-backend/src/models/__tests__/interviewsAccesses.db.test.ts +++ b/packages/evolution-backend/src/models/__tests__/interviewsAccesses.db.test.ts @@ -14,29 +14,11 @@ import interviewsDbQueries from '../interviews.db.queries'; const permission1 = 'role1'; const permission2 = 'role2'; -const localParticipant = { - id: 1, - email: 'test@transition.city', - is_valid: true -}; +const localParticipant = { id: 1, email: 'test@transition.city', is_valid: true }; -const localUser = { - ...localParticipant, - uuid: uuidV4(), - permissions: { - [permission1]: true - } -}; +const localUser = { ...localParticipant, uuid: uuidV4(), permissions: { [permission1]: true } }; -const anotherUser = { - id: 2, - email: 'test2@transition.city', - is_valid: true, - uuid: uuidV4(), - permissions: { - [permission2]: true - } -}; +const anotherUser = { id: 2, email: 'test2@transition.city', is_valid: true, uuid: uuidV4(), permissions: { [permission2]: true } }; const localUserInterviewAttributes = { uuid: uuidV4(), @@ -44,10 +26,7 @@ const localUserInterviewAttributes = { is_valid: false, is_active: true, is_completed: undefined, - response: { - accessCode: '11111', - booleanField: true, - }, + response: { accessCode: '11111', booleanField: true }, validations: {}, audits: { errorOne: 3, errorThree: 1 } } as any; @@ -64,7 +43,7 @@ beforeAll(async () => { await interviewsDbQueries.create(localUserInterviewAttributes); }); -afterAll(async() => { +afterAll(async () => { await truncate(knex, 'sv_interviews_accesses'); await truncate(knex, 'sv_interviews'); await truncate(knex, 'users'); @@ -78,18 +57,17 @@ const assertAccessCounts = async (cnt: number) => { }; describe('userOpenedInterview', () => { - - test('User opened existing interview for the first time', async() => { + test('User opened existing interview for the first time', async () => { expect(await dbQueries.userOpenedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: localUser.id })).toEqual(true); await assertAccessCounts(1); }); - test('User opened existing interview once more', async() => { + test('User opened existing interview once more', async () => { expect(await dbQueries.userOpenedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: localUser.id })).toEqual(true); await assertAccessCounts(1); }); - test('Interview does not exist', async() => { + test('Interview does not exist', async () => { let exception: any = undefined; try { await dbQueries.userOpenedInterview({ interviewUuid: uuidV4(), userId: localUser.id }); @@ -97,36 +75,42 @@ describe('userOpenedInterview', () => { exception = error; } expect(exception).toBeDefined(); - expect((exception as any).message).toEqual(expect.stringContaining('cannot log the user opening the interview (knex error: The requested interview does not exist:')); + expect((exception as any).message).toEqual( + expect.stringContaining('cannot log the user opening the interview (knex error: The requested interview does not exist:') + ); await assertAccessCounts(1); }); - test('User does not exist', async() => { + test('User does not exist', async () => { let exception: any = undefined; try { - await dbQueries.userOpenedInterview({ interviewUuid:localUserInterviewAttributes.uuid, userId: localUser.id - 1 }); + await dbQueries.userOpenedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: localUser.id - 1 }); } catch (error) { exception = error; } expect(exception).toBeDefined(); - expect((exception as any).message).toEqual(expect.stringContaining('violates foreign key constraint "sv_interviews_accesses_user_id_foreign")')); + expect((exception as any).message).toEqual( + expect.stringContaining('violates foreign key constraint "sv_interviews_accesses_user_id_foreign")') + ); await assertAccessCounts(1); }); - test('Second user opened existing interview', async() => { - expect(await dbQueries.userOpenedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id, validationMode: false })).toEqual(true); + test('Second user opened existing interview', async () => { + expect( + await dbQueries.userOpenedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id, validationMode: false }) + ).toEqual(true); await assertAccessCounts(2); }); - test('Second user opened existing interview, for validation', async() => { - expect(await dbQueries.userOpenedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id, validationMode: true })).toEqual(true); + test('Second user opened existing interview, for validation', async () => { + expect( + await dbQueries.userOpenedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id, validationMode: true }) + ).toEqual(true); await assertAccessCounts(3); }); - }); describe('userUpdatedInterview', () => { - const assertUpdateCounts = async (userId: number, validationMode: boolean, updateCnt: number) => { const data = await dbQueries.collection(); const record = data.find((access) => access.user_id === userId && access.for_validation === validationMode); @@ -134,43 +118,53 @@ describe('userUpdatedInterview', () => { expect(record?.update_count).toEqual(updateCnt); }; - test('User updated interview in edit mode', async() => { + test('User updated interview in edit mode', async () => { expect(await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id })).toEqual(true); await assertAccessCounts(3); await assertUpdateCounts(anotherUser.id, false, 1); }); - test('User updated interview in edit mode, again', async() => { - expect(await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id, validationMode: false })).toEqual(true); + test('User updated interview in edit mode, again', async () => { + expect( + await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id, validationMode: false }) + ).toEqual(true); await assertAccessCounts(3); await assertUpdateCounts(anotherUser.id, false, 2); }); - test('User updated interview in validation mode', async() => { - expect(await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id, validationMode: true })).toEqual(true); + test('User updated interview in validation mode', async () => { + expect( + await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id, validationMode: true }) + ).toEqual(true); await assertAccessCounts(3); await assertUpdateCounts(anotherUser.id, true, 1); }); - test('User updated interview in validation mode, again', async() => { - expect(await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id, validationMode: true })).toEqual(true); + test('User updated interview in validation mode, again', async () => { + expect( + await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: anotherUser.id, validationMode: true }) + ).toEqual(true); await assertAccessCounts(3); await assertUpdateCounts(anotherUser.id, true, 2); }); - test('New user updated interview in validation mode', async() => { - expect(await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: localUser.id, validationMode: true })).toEqual(true); + test('New user updated interview in validation mode', async () => { + expect( + await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: localUser.id, validationMode: true }) + ).toEqual(true); await assertAccessCounts(4); await assertUpdateCounts(localUser.id, true, 1); }); - test('New user updated interview in validation mode, again', async() => { - expect(await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: localUser.id, validationMode: true })).toEqual(true); + test('New user updated interview in validation mode, again', async () => { + expect( + await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: localUser.id, validationMode: true }) + ).toEqual(true); await assertAccessCounts(4); await assertUpdateCounts(localUser.id, true, 2); }); - test('Interview does not exist', async() => { + test('Interview does not exist', async () => { let exception: any = undefined; try { await dbQueries.userUpdatedInterview({ interviewUuid: uuidV4(), userId: localUser.id }); @@ -178,30 +172,31 @@ describe('userUpdatedInterview', () => { exception = error; } expect(exception).toBeDefined(); - expect((exception as any).message).toEqual(expect.stringContaining('cannot log the user updating an interview (knex error: The requested interview does not exist:')); - + expect((exception as any).message).toEqual( + expect.stringContaining('cannot log the user updating an interview (knex error: The requested interview does not exist:') + ); }); - test('User does not exist', async() => { + test('User does not exist', async () => { let exception: any = undefined; try { - await dbQueries.userUpdatedInterview({ interviewUuid:localUserInterviewAttributes.uuid, userId: localUser.id - 1 }); + await dbQueries.userUpdatedInterview({ interviewUuid: localUserInterviewAttributes.uuid, userId: localUser.id - 1 }); } catch (error) { exception = error; } expect(exception).toBeDefined(); - expect((exception as any).message).toEqual(expect.stringContaining('violates foreign key constraint "sv_interviews_accesses_user_id_foreign")')); + expect((exception as any).message).toEqual( + expect.stringContaining('violates foreign key constraint "sv_interviews_accesses_user_id_foreign")') + ); }); - }); describe('Stat editing users', () => { - test('Stat all users', async () => { const statUsers = await dbQueries.statEditingUsers({}); expect(statUsers.length).toEqual(4); statUsers.forEach((statUser) => { - switch(statUser.email) { + switch (statUser.email) { case localUser.email: expect(statUser.update_count).toEqual(statUser.for_validation ? 2 : 0); break; @@ -218,7 +213,7 @@ describe('Stat editing users', () => { const statUsers = await dbQueries.statEditingUsers({ permissions: [permission2] }); expect(statUsers.length).toEqual(2); statUsers.forEach((statUser) => { - switch(statUser.email) { + switch (statUser.email) { case anotherUser.email: expect(statUser.update_count).toEqual(2); break; @@ -232,7 +227,7 @@ describe('Stat editing users', () => { const statUsers = await dbQueries.statEditingUsers({ permissions: [permission2, permission1] }); expect(statUsers.length).toEqual(4); statUsers.forEach((statUser) => { - switch(statUser.email) { + switch (statUser.email) { case localUser.email: expect(statUser.update_count).toEqual(statUser.for_validation ? 2 : 0); break; @@ -244,5 +239,4 @@ describe('Stat editing users', () => { } }); }); - }); diff --git a/packages/evolution-backend/src/models/__tests__/interviewsPreFill.db.test.ts b/packages/evolution-backend/src/models/__tests__/interviewsPreFill.db.test.ts index 592f66f7f..8d0d10694 100644 --- a/packages/evolution-backend/src/models/__tests__/interviewsPreFill.db.test.ts +++ b/packages/evolution-backend/src/models/__tests__/interviewsPreFill.db.test.ts @@ -14,7 +14,7 @@ beforeAll(async () => { await truncate(knex, 'sv_interviews_prefill'); }); -afterAll(async() => { +afterAll(async () => { await truncate(knex, 'sv_interviews_prefill'); await knex.destroy(); }); @@ -26,7 +26,7 @@ describe('set/get pre-filled answers', () => { ['home.city']: { value: 'Montreal', actionIfPresent: 'doNothing' as const } }; - test('Get unexisting response', async() => { + test('Get unexisting response', async () => { expect(await dbQueries.getByReferenceValue('data')).toBeUndefined(); }); @@ -50,5 +50,4 @@ describe('set/get pre-filled answers', () => { const newPreFilledResponse = await dbQueries.getByReferenceValue('foo'); expect(newPreFilledResponse).toEqual(response); }); - }); diff --git a/packages/evolution-backend/src/models/__tests__/monitoring.db.test.ts b/packages/evolution-backend/src/models/__tests__/monitoring.db.test.ts index 5e5e4ea2d..76879c9ec 100644 --- a/packages/evolution-backend/src/models/__tests__/monitoring.db.test.ts +++ b/packages/evolution-backend/src/models/__tests__/monitoring.db.test.ts @@ -20,17 +20,10 @@ const surveysTable = 'sv_surveys'; // `sv_interviews.participant_id` is NOT NULL and there is a UNIQUE(survey_id, participant_id) // constraint, so each interview fixture must reference a distinct participant for a given survey. -const localParticipants = Array.from({ length: 5 }, (_v, i) => ({ - id: i + 1, - email: `test${i + 1}@transition.city`, - is_valid: true -})); +const localParticipants = Array.from({ length: 5 }, (_v, i) => ({ id: i + 1, email: `test${i + 1}@transition.city`, is_valid: true })); // `sv_interviews.survey_id` is NOT NULL in the schema, so interviews must reference an existing survey. -const localSurvey = { - id: 1, - shortname: 'test_survey' -}; +const localSurvey = { id: 1, shortname: 'test_survey' }; const mockInterviews = [ { @@ -40,10 +33,7 @@ const mockInterviews = [ participant_id: localParticipants[0].id, is_valid: true, is_completed: true, - response: { - _completedAt: '2024-01-01T10:00:00Z', - perceivedBurden: { difficultyRange: 10 } - } + response: { _completedAt: '2024-01-01T10:00:00Z', perceivedBurden: { difficultyRange: 10 } } }, { id: 2, @@ -52,10 +42,7 @@ const mockInterviews = [ participant_id: localParticipants[1].id, is_valid: true, is_completed: true, - response: { - _completedAt: '2024-01-02T10:00:00Z', - perceivedBurden: { difficultyRange: 50 } - } + response: { _completedAt: '2024-01-02T10:00:00Z', perceivedBurden: { difficultyRange: 50 } } }, { id: 3, @@ -64,9 +51,7 @@ const mockInterviews = [ participant_id: localParticipants[2].id, is_valid: true, is_completed: false, - response: { - perceivedBurden: { difficultyRange: 90 } - } + response: { perceivedBurden: { difficultyRange: 90 } } }, { id: 4, @@ -75,20 +60,9 @@ const mockInterviews = [ participant_id: localParticipants[3].id, is_valid: true, is_completed: true, - response: { - _completedAt: '2024-01-03T10:00:00Z', - perceivedBurden: { difficultyRange: null } - } + response: { _completedAt: '2024-01-03T10:00:00Z', perceivedBurden: { difficultyRange: null } } }, - { - id: 5, - uuid: uuidV4(), - survey_id: localSurvey.id, - participant_id: localParticipants[4].id, - is_valid: true, - is_completed: false, - response: {} - } + { id: 5, uuid: uuidV4(), survey_id: localSurvey.id, participant_id: localParticipants[4].id, is_valid: true, is_completed: false, response: {} } ]; beforeAll(async () => { @@ -142,9 +116,7 @@ describe('monitoring.db.queries with mock data', () => { }); // Check that bins add up to number of interviews with difficulty value - const withDifficulty = mockInterviews.filter( - (i) => typeof i.response.perceivedBurden?.difficultyRange === 'number' - ).length; + const withDifficulty = mockInterviews.filter((i) => typeof i.response.perceivedBurden?.difficultyRange === 'number').length; const totalCount = result.reduce((sum, bin) => sum + bin.count, 0); expect(totalCount).toBe(withDifficulty); diff --git a/packages/evolution-backend/src/models/__tests__/paradataEvents.db.test.ts b/packages/evolution-backend/src/models/__tests__/paradataEvents.db.test.ts index 7bb80a7a9..b91e427c7 100644 --- a/packages/evolution-backend/src/models/__tests__/paradataEvents.db.test.ts +++ b/packages/evolution-backend/src/models/__tests__/paradataEvents.db.test.ts @@ -17,23 +17,11 @@ import dbQueries from '../paradataEvents.db.queries'; import interviewsDbQueries from '../interviews.db.queries'; // Mock data -const localParticipant = { - id: 1, - email: 'test@transition.city', - is_valid: true -}; +const localParticipant = { id: 1, email: 'test@transition.city', is_valid: true }; -const secondParticipant = { - id: 11, - email: 'test1@transition.city', - is_valid: true -}; +const secondParticipant = { id: 11, email: 'test1@transition.city', is_valid: true }; -const localUser = { - ...localParticipant, - id: 2, - uuid: uuidV4() -}; +const localUser = { ...localParticipant, id: 2, uuid: uuidV4() }; const testInterviewAttributes1 = { id: 100, @@ -42,10 +30,7 @@ const testInterviewAttributes1 = { is_valid: false, is_active: true, is_completed: undefined, - response: { - accessCode: '11111', - booleanField: true, - }, + response: { accessCode: '11111', booleanField: true }, validations: {} } as any; @@ -56,11 +41,7 @@ const testInterviewAttributes2 = { is_valid: false, is_active: true, is_completed: undefined, - response: { - _isCompleted: true, - accessCode: '11111', - booleanField: true, - }, + response: { _isCompleted: true, accessCode: '11111', booleanField: true }, validations: {} } as any; @@ -78,7 +59,7 @@ beforeAll(async () => { await interviewsDbQueries.create(testInterviewAttributes2); }); -afterAll(async() => { +afterAll(async () => { await truncate(knex, 'paradata_events'); await truncate(knex, 'sv_interviews'); await truncate(knex, 'users'); @@ -89,16 +70,17 @@ afterAll(async() => { const testStart = Date.now(); describe('paradata log', () => { - beforeEach(async () => { await truncate(knex, 'paradata_events'); }); const defaultEventData = { someData: 'value', anotherData: 'value' }; - const validateLogData = async (expected: { userId: number | null, eventData: any, forCorrection?: boolean }[]) => { + const validateLogData = async (expected: { userId: number | null; eventData: any; forCorrection?: boolean }[]) => { const now = Date.now() + 1; // Add 1ms to make sure we are not equal to now at the ms precision (timestamps are at us precision) - const events = await knex('paradata_events').select(['*', knex.raw('EXTRACT(EPOCH FROM timestamp) as unix_timestamp')]).orderBy('timestamp'); + const events = await knex('paradata_events') + .select(['*', knex.raw('EXTRACT(EPOCH FROM timestamp) as unix_timestamp')]) + .orderBy('timestamp'); expect(events.length).toEqual(expected.length); let lastTimestamp = testStart; @@ -118,71 +100,59 @@ describe('paradata log', () => { for (let i = 0; i < expected.length; i++) { const expectedData = expected[i]; const event = events.find((event) => expectedData.userId === event.user_id && _isEqual(expectedData.eventData, event.event_data)); - expect(event).toEqual(expect.objectContaining({ - event_data: expectedData.eventData, - user_id: expectedData.userId, - for_correction: expectedData.forCorrection === true ? true : false - })); + expect(event).toEqual( + expect.objectContaining({ + event_data: expectedData.eventData, + user_id: expectedData.userId, + for_correction: expectedData.forCorrection === true ? true : false + }) + ); } }; - test('Log for a participant (no user id)', async() => { - expect(await dbQueries.log({ - interviewId: testInterviewAttributes1.id, - eventType: 'button_click', - eventData: defaultEventData - })).toEqual(true); + test('Log for a participant (no user id)', async () => { + expect(await dbQueries.log({ interviewId: testInterviewAttributes1.id, eventType: 'button_click', eventData: defaultEventData })).toEqual( + true + ); await validateLogData([{ userId: null, eventData: defaultEventData }]); }); test('Log with user ID', async () => { - expect(await dbQueries.log({ - interviewId: testInterviewAttributes1.id, - eventType: 'button_click', - eventData: defaultEventData, - userId: localUser.id - })).toEqual(true); + expect( + await dbQueries.log({ + interviewId: testInterviewAttributes1.id, + eventType: 'button_click', + eventData: defaultEventData, + userId: localUser.id + }) + ).toEqual(true); await validateLogData([{ userId: localUser.id, eventData: defaultEventData }]); }); test('Log undefined event data', async () => { - expect(await dbQueries.log({ - interviewId: testInterviewAttributes1.id, - eventType: 'button_click', - userId: localUser.id - })).toEqual(true); + expect(await dbQueries.log({ interviewId: testInterviewAttributes1.id, eventType: 'button_click', userId: localUser.id })).toEqual(true); await validateLogData([{ userId: localUser.id, eventData: null }]); }); - test.each([ - [true], - [false] - ])('Log with forCorrection set to `%s`', async (forCorrection) => { - expect(await dbQueries.log({ - interviewId: testInterviewAttributes1.id, - eventType: 'button_click', - eventData: defaultEventData, - userId: localUser.id, - forCorrection - })).toEqual(true); + test.each([[true], [false]])('Log with forCorrection set to `%s`', async (forCorrection) => { + expect( + await dbQueries.log({ + interviewId: testInterviewAttributes1.id, + eventType: 'button_click', + eventData: defaultEventData, + userId: localUser.id, + forCorrection + }) + ).toEqual(true); await validateLogData([{ userId: localUser.id, eventData: defaultEventData, forCorrection }]); }); - test('Log multiple data', async() => { + test('Log multiple data', async () => { const secondEventData = { someData: 'value2', anotherData: 'value2', something: 'test' }; // Save a log for user and a log for participant const promises = [ - dbQueries.log({ - interviewId: testInterviewAttributes1.id, - eventType: 'button_click', - eventData: defaultEventData, - userId: localUser.id - }), - dbQueries.log({ - interviewId: testInterviewAttributes1.id, - eventType: 'button_click', - eventData: secondEventData - }) + dbQueries.log({ interviewId: testInterviewAttributes1.id, eventType: 'button_click', eventData: defaultEventData, userId: localUser.id }), + dbQueries.log({ interviewId: testInterviewAttributes1.id, eventType: 'button_click', eventData: secondEventData }) ]; await Promise.all(promises); await validateLogData([ @@ -193,26 +163,29 @@ describe('paradata log', () => { each([ ['Invalid event type', { eventType: 'invalid_event_type' }], - ['Unknown user', { userId: localUser.id + 1 } ], - ['Unknown interview ID', { interviewId: testInterviewAttributes1.id + 1 }], + ['Unknown user', { userId: localUser.id + 1 }], + ['Unknown interview ID', { interviewId: testInterviewAttributes1.id + 1 }] ]).test('Log with errors: %s', async (_desc, changedData) => { - const invalidParadataLog = Object.assign({ - interviewId: testInterviewAttributes1.id, - eventType: 'button_click', - eventData: defaultEventData - }, changedData); + const invalidParadataLog = Object.assign( + { interviewId: testInterviewAttributes1.id, eventType: 'button_click', eventData: defaultEventData }, + changedData + ); await expect(dbQueries.log(invalidParadataLog)).rejects.toThrow(expect.anything()); const data = await knex('paradata_events').select(); expect(data.length).toEqual(0); }); - }); // Wait for a number of millisecond: in a db tests, inserts are often done too rapidly and we may want to throttle const throttle = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); // Insert a number of logs in the database, waiting for a delay between each insert -const insertSomeLogs = async (logData: (Record | undefined)[], interviewId: number, userId?: number | undefined, forCorrection?: boolean | undefined) => { +const insertSomeLogs = async ( + logData: (Record | undefined)[], + interviewId: number, + userId?: number | undefined, + forCorrection?: boolean | undefined +) => { for (let i = 0; i < logData.length; i++) { const { eventType, ...eventData } = logData[i] || { eventType: undefined }; await dbQueries.log({ @@ -227,7 +200,6 @@ const insertSomeLogs = async (logData: (Record | undefined)[], inte }; describe('Stream paradata', () => { - beforeEach(async () => { // Empty all logs await truncate(knex, 'paradata_events'); @@ -236,11 +208,12 @@ describe('Stream paradata', () => { test('Stream interview logs, no logs in database', (done) => { let nbLogs = 0; const queryStream = dbQueries.getParadataStream(); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { nbLogs++; }) @@ -253,83 +226,73 @@ describe('Stream paradata', () => { test('Stream interview logs, only one interview has logs', (done) => { // Add a few logs to one of the interview, with/without valuesByPath, with/without unsetPaths, one with undefined log data const logData = [ + { valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true } }, { - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true } - }, { - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true }, + valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true }, unsetPaths: [] - }, { - valuesByPath: { }, - unsetPaths: [ 'response.data' ] - }, undefined, { - unsetPaths: [ 'response.data.someField', 'validations.data.someField' ] - } + }, + { valuesByPath: {}, unsetPaths: ['response.data'] }, + undefined, + { unsetPaths: ['response.data.someField', 'validations.data.someField'] } ]; - insertSomeLogs(logData, testInterviewAttributes1.id).then(() => { - let nbLogs = 0; - let lastTimestamp = -1; - const queryStream = dbQueries.getParadataStream(); - queryStream.on('error', (error) => { + insertSomeLogs(logData, testInterviewAttributes1.id) + .then(() => { + let nbLogs = 0; + let lastTimestamp = -1; + const queryStream = dbQueries.getParadataStream(); + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) + .on('data', (row) => { + // Check the data + expect(row.uuid).toEqual(testInterviewAttributes1.uuid); + + // Expected sort by timestamp, timestamp should be greater than previous + const rowTimestamp = Number(row.timestamp_sec); + expect(rowTimestamp).toBeGreaterThanOrEqual(lastTimestamp); + lastTimestamp = rowTimestamp; + expect(row.event_date).toEqual(new Date(row.timestamp_sec * 1000).toISOString()); + // Expected valuesByPath and unsetPaths to match the log data + const log = logData[nbLogs]; + expect(row.values_by_path).toEqual(log?.valuesByPath ? log.valuesByPath : null); + expect(row.unset_paths).toEqual(log?.unsetPaths ? log.unsetPaths : null); + expect(row.user_id).toBeNull(); + expect(row.interview_is_completed).toBeNull(); + + nbLogs++; + }) + .on('end', () => { + expect(nbLogs).toEqual(logData.length); + done(); + }); + }) + .catch((error) => { console.error(error); expect(true).toBe(false); done(); - }) - .on('data', (row) => { - // Check the data - expect(row.uuid).toEqual(testInterviewAttributes1.uuid); - - // Expected sort by timestamp, timestamp should be greater than previous - const rowTimestamp = Number(row.timestamp_sec); - expect(rowTimestamp).toBeGreaterThanOrEqual(lastTimestamp); - lastTimestamp = rowTimestamp; - expect(row.event_date).toEqual(new Date(row.timestamp_sec * 1000).toISOString()); - // Expected valuesByPath and unsetPaths to match the log data - const log = logData[nbLogs]; - expect(row.values_by_path).toEqual(log?.valuesByPath ? log.valuesByPath : null); - expect(row.unset_paths).toEqual(log?.unsetPaths ? log.unsetPaths : null); - expect(row.user_id).toBeNull(); - expect(row.interview_is_completed).toBeNull(); - - nbLogs++; - }) - .on('end', () => { - expect(nbLogs).toEqual(logData.length); - done(); - }); - }).catch((error) => { - console.error(error); - expect(true).toBe(false); - done(); - }); - + }); }); test('Stream interview logs, many interview logs, should be sorted by interview/time', (done) => { // Add a few logs to one of the interview, with/without valuesByPath, with/without unsetPaths const logData1 = [ + { valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true } }, { - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true } - }, - { - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true }, + valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true }, unsetPaths: [] }, - { - valuesByPath: { }, - unsetPaths: [ 'response.data' ] - }, - { - unsetPaths: [ 'response.data.someField', 'validations.data.someField' ] - } + { valuesByPath: {}, unsetPaths: ['response.data'] }, + { unsetPaths: ['response.data.someField', 'validations.data.someField'] } ]; const logData2 = [ + { valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [2, 2] }, 'validations.home.geography': false } }, { - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 2, 2 ] }, 'validations.home.geography': false } - }, - { - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 3, 3 ] }, 'validations.home.geography': true }, - unsetPaths: [ 'response.data.someField', 'validations.data.someField' ] + valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [3, 3] }, 'validations.home.geography': true }, + unsetPaths: ['response.data.someField', 'validations.data.someField'] } ]; const insertLogs = async () => { @@ -342,90 +305,83 @@ describe('Stream paradata', () => { // And finally for the first again await insertSomeLogs(logData2, testInterviewAttributes1.id); }; - insertLogs().then(() => { - - let nbLogs = 0; - let lastTimestamp = -1; - let currentInterviewId: number | undefined = undefined; - let interviewLogsForFirstCompleted = false; - let currentInterviewIndex = 0; + insertLogs() + .then(() => { + let nbLogs = 0; + let lastTimestamp = -1; + let currentInterviewId: number | undefined = undefined; + let interviewLogsForFirstCompleted = false; + let currentInterviewIndex = 0; + + // This is the expected logs for the interview 1, a concatenation of the 2 log data arrays + const logsFor1 = logData1.concat(logData2 as any); + + // Test the stream and its data + const queryStream = dbQueries.getParadataStream(); + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) + .on('data', (row) => { + // Check the data + // Sorted by interview ID, all logs for first interview should be before all logs for second interview, so we should only switch once + if (nbLogs === 0) { + currentInterviewId = row.id; + } + if (currentInterviewId !== row.id) { + currentInterviewId = row.id; + lastTimestamp = -1; + expect(interviewLogsForFirstCompleted).toEqual(false); + interviewLogsForFirstCompleted = true; + currentInterviewIndex = 0; + // Should be the second interview now, with completed status true + expect(row.interview_is_completed).toEqual('true'); + } - // This is the expected logs for the interview 1, a concatenation of the 2 log data arrays - const logsFor1 = logData1.concat(logData2 as any); + // Expected sort by timestamp, timestamp should be greater than previous + const rowTimestamp = Number(row.timestamp_sec); + expect(rowTimestamp).toBeGreaterThan(lastTimestamp); + lastTimestamp = rowTimestamp; + // Expected valuesByPath and unsetPaths to match the log data + const logArrayToCheck = currentInterviewId === testInterviewAttributes1.id ? logsFor1 : logData2; + const log = logArrayToCheck[currentInterviewIndex]; + expect(log).toBeDefined(); + expect(row.values_by_path).toEqual(log?.valuesByPath ? log.valuesByPath : null); + expect(row.unset_paths).toEqual(log?.unsetPaths ? log.unsetPaths : null); - // Test the stream and its data - const queryStream = dbQueries.getParadataStream(); - queryStream.on('error', (error) => { + nbLogs++; + currentInterviewIndex++; + }) + .on('end', () => { + expect(nbLogs).toEqual(logsFor1.length + logData2.length); + done(); + }); + }) + .catch((error) => { console.error(error); expect(true).toBe(false); done(); - }) - .on('data', (row) => { - // Check the data - // Sorted by interview ID, all logs for first interview should be before all logs for second interview, so we should only switch once - if (nbLogs === 0) { - currentInterviewId = row.id; - } - if (currentInterviewId !== row.id) { - currentInterviewId = row.id; - lastTimestamp = -1; - expect(interviewLogsForFirstCompleted).toEqual(false); - interviewLogsForFirstCompleted = true; - currentInterviewIndex = 0; - // Should be the second interview now, with completed status true - expect(row.interview_is_completed).toEqual('true'); - } - - // Expected sort by timestamp, timestamp should be greater than previous - const rowTimestamp = Number(row.timestamp_sec); - expect(rowTimestamp).toBeGreaterThan(lastTimestamp); - lastTimestamp = rowTimestamp; - // Expected valuesByPath and unsetPaths to match the log data - const logArrayToCheck = currentInterviewId === testInterviewAttributes1.id ? logsFor1 : logData2; - const log = logArrayToCheck[currentInterviewIndex]; - expect(log).toBeDefined(); - expect(row.values_by_path).toEqual(log?.valuesByPath ? log.valuesByPath : null); - expect(row.unset_paths).toEqual(log?.unsetPaths ? log.unsetPaths : null); - - nbLogs++; - currentInterviewIndex++; - }) - .on('end', () => { - expect(nbLogs).toEqual(logsFor1.length + logData2.length); - done(); - }); - }).catch((error) => { - console.error(error); - expect(true).toBe(false); - done(); - }); + }); }); test('Stream interview logs for single interview, many interview logs, should be sorted by time', (done) => { // Add a few logs to the interview, with/without valuesByPath, with/without unsetPaths const logData1 = [ + { valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true } }, { - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true } - }, - { - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true }, + valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true }, unsetPaths: [] }, - { - valuesByPath: { }, - unsetPaths: [ 'response.data' ] - }, - { - unsetPaths: [ 'response.data.someField', 'validations.data.someField' ] - } + { valuesByPath: {}, unsetPaths: ['response.data'] }, + { unsetPaths: ['response.data.someField', 'validations.data.someField'] } ]; const logData2 = [ + { valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [2, 2] }, 'validations.home.geography': false } }, { - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 2, 2 ] }, 'validations.home.geography': false } - }, - { - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 3, 3 ] }, 'validations.home.geography': true }, - unsetPaths: [ 'response.data.someField', 'validations.data.someField' ] + valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [3, 3] }, 'validations.home.geography': true }, + unsetPaths: ['response.data.someField', 'validations.data.someField'] } ]; const insertLogs = async () => { @@ -438,70 +394,68 @@ describe('Stream paradata', () => { // And finally for the first again await insertSomeLogs(logData2, testInterviewAttributes1.id); }; - insertLogs().then(() => { - let nbLogs = 0; - let lastTimestamp = -1; + insertLogs() + .then(() => { + let nbLogs = 0; + let lastTimestamp = -1; - // This is the expected logs for the interview 1, a concatenation of the 2 log data arrays - const logsFor1 = logData1.concat(logData2 as any); + // This is the expected logs for the interview 1, a concatenation of the 2 log data arrays + const logsFor1 = logData1.concat(logData2 as any); - // Get logs only for one interview - const queryStream = dbQueries.getParadataStream({ interviewId: testInterviewAttributes1.id }); - queryStream.on('error', (error) => { + // Get logs only for one interview + const queryStream = dbQueries.getParadataStream({ interviewId: testInterviewAttributes1.id }); + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) + .on('data', (row) => { + // Check the data, only the logs for the first interview should be returned + expect(row.uuid).toEqual(testInterviewAttributes1.uuid); + + // Expected sort by timestamp, timestamp should be greater than previous + const rowTimestamp = Number(row.timestamp_sec); + expect(rowTimestamp).toBeGreaterThan(lastTimestamp); + lastTimestamp = rowTimestamp; + // Expected valuesByPath and unsetPaths to match the log data + const log = logsFor1[nbLogs]; + expect(log).toBeDefined(); + expect(row.values_by_path).toEqual(log?.valuesByPath ? log.valuesByPath : null); + expect(row.unset_paths).toEqual(log?.unsetPaths ? log.unsetPaths : null); + + nbLogs++; + }) + .on('end', () => { + // Only the logs for the first interview should be returned + expect(nbLogs).toEqual(logsFor1.length); + done(); + }); + }) + .catch((error) => { console.error(error); expect(true).toBe(false); done(); - }) - .on('data', (row) => { - // Check the data, only the logs for the first interview should be returned - expect(row.uuid).toEqual(testInterviewAttributes1.uuid); - - // Expected sort by timestamp, timestamp should be greater than previous - const rowTimestamp = Number(row.timestamp_sec); - expect(rowTimestamp).toBeGreaterThan(lastTimestamp); - lastTimestamp = rowTimestamp; - // Expected valuesByPath and unsetPaths to match the log data - const log = logsFor1[nbLogs]; - expect(log).toBeDefined(); - expect(row.values_by_path).toEqual(log?.valuesByPath ? log.valuesByPath : null); - expect(row.unset_paths).toEqual(log?.unsetPaths ? log.unsetPaths : null); - - nbLogs++; - }) - .on('end', () => { - // Only the logs for the first interview should be returned - expect(nbLogs).toEqual(logsFor1.length); - done(); - }); - }).catch((error) => { - console.error(error); - expect(true).toBe(false); - done(); - }); + }); }); describe('with forCorrection filter', () => { - const participantEvent = { eventType: 'section_change', - userAction: { type: 'sectionChange', targetSection: { sectionShortname: 'someSection' } }, + userAction: { type: 'sectionChange', targetSection: { sectionShortname: 'someSection' } } }; const interviewerEvent = { eventType: 'side_effect', - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true }, + valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true }, unsetPaths: [] }; const correctionEvent = { eventType: 'widget_interaction', - valuesByPath: { }, - unsetPaths: [ 'response.data' ], + valuesByPath: {}, + unsetPaths: ['response.data'], userAction: { type: 'widgetInteraction', path: 'response.home.someField', value: 'someValue', widgetType: 'radio' } }; - const unknownResponseEvent = { - eventType: 'legacy', - valuesByPath: { }, - unsetPaths: [ 'response.data' ] - }; + const unknownResponseEvent = { eventType: 'legacy', valuesByPath: {}, unsetPaths: ['response.data'] }; beforeEach(async () => { // Add a log for a participant without user @@ -512,28 +466,24 @@ describe('Stream paradata', () => { await insertSomeLogs([correctionEvent], testInterviewAttributes1.id, localUser.id, true); // Add a log for a user, then manually update the database to set the for_correction to null await insertSomeLogs([unknownResponseEvent], testInterviewAttributes1.id, localUser.id, false); - await knex.raw(`UPDATE paradata_events SET for_correction = NULL WHERE event_type = 'legacy' AND user_id IS NOT NULL`) - }) + await knex.raw('UPDATE paradata_events SET for_correction = NULL WHERE event_type = \'legacy\' AND user_id IS NOT NULL'); + }); test('Stream interview with forCorrection set to `false`, should return participant and null', (done) => { - let nbLogs = 0; let lastTimestamp = -1; // This is the expected logs for the participant data - const expectedLogs = [ - participantEvent, - interviewerEvent, - unknownResponseEvent - ]; + const expectedLogs = [participantEvent, interviewerEvent, unknownResponseEvent]; // Get logs only for one interview const queryStream = dbQueries.getParadataStream({ forCorrection: false }); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { // Check the data, only the logs for the first interview should be returned expect(row.uuid).toEqual(testInterviewAttributes1.uuid); @@ -559,23 +509,20 @@ describe('Stream paradata', () => { }); test('Stream interview with forCorrection set to `true`, should return corrected and null', (done) => { - let nbLogs = 0; let lastTimestamp = -1; // This is the expected logs for the participant data - const expectedLogs = [ - correctionEvent, - unknownResponseEvent - ]; + const expectedLogs = [correctionEvent, unknownResponseEvent]; // Get logs only for one interview const queryStream = dbQueries.getParadataStream({ forCorrection: true }); - queryStream.on('error', (error) => { - console.error(error); - expect(true).toBe(false); - done(); - }) + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) .on('data', (row) => { // Check the data, only the logs for the first interview should be returned expect(row.uuid).toEqual(testInterviewAttributes1.uuid); @@ -600,70 +547,61 @@ describe('Stream paradata', () => { }); test('Stream interview with forCorrection set to `true` and interviewId set', (done) => { - // Add a few logs for a second interview, with forCorrection = true - insertSomeLogs([correctionEvent, unknownResponseEvent], testInterviewAttributes2.id, localUser.id, true).then(() => { - let nbLogs = 0; - let lastTimestamp = -1; - - // This is the expected logs for the participant data - const expectedLogs = [ - correctionEvent, - unknownResponseEvent - ]; - - // Get logs only for one interview - const queryStream = dbQueries.getParadataStream({ interviewId: testInterviewAttributes1.id, forCorrection: true }); - queryStream.on('error', (error) => { + insertSomeLogs([correctionEvent, unknownResponseEvent], testInterviewAttributes2.id, localUser.id, true) + .then(() => { + let nbLogs = 0; + let lastTimestamp = -1; + + // This is the expected logs for the participant data + const expectedLogs = [correctionEvent, unknownResponseEvent]; + + // Get logs only for one interview + const queryStream = dbQueries.getParadataStream({ interviewId: testInterviewAttributes1.id, forCorrection: true }); + queryStream + .on('error', (error) => { + console.error(error); + expect(true).toBe(false); + done(); + }) + .on('data', (row) => { + // Check the data, only the logs for the first interview should be returned + expect(row.uuid).toEqual(testInterviewAttributes1.uuid); + + // Expected sort by timestamp, timestamp should be greater than previous + const rowTimestamp = Number(row.timestamp_sec); + expect(rowTimestamp).toBeGreaterThan(lastTimestamp); + lastTimestamp = rowTimestamp; + // Expected valuesByPath, unsetPaths and userAction to match the log data + const log = expectedLogs[nbLogs] as any; + expect(log).toBeDefined(); + expect(row.values_by_path).toEqual(log?.valuesByPath ? log.valuesByPath : null); + expect(row.unset_paths).toEqual(log?.unsetPaths ? log.unsetPaths : null); + expect(row.user_action).toEqual(log?.userAction ? log.userAction : null); + + nbLogs++; + }) + .on('end', () => { + expect(nbLogs).toEqual(expectedLogs.length); + done(); + }); + }) + .catch((error) => { console.error(error); expect(true).toBe(false); done(); - }) - .on('data', (row) => { - // Check the data, only the logs for the first interview should be returned - expect(row.uuid).toEqual(testInterviewAttributes1.uuid); - - // Expected sort by timestamp, timestamp should be greater than previous - const rowTimestamp = Number(row.timestamp_sec); - expect(rowTimestamp).toBeGreaterThan(lastTimestamp); - lastTimestamp = rowTimestamp; - // Expected valuesByPath, unsetPaths and userAction to match the log data - const log = expectedLogs[nbLogs] as any; - expect(log).toBeDefined(); - expect(row.values_by_path).toEqual(log?.valuesByPath ? log.valuesByPath : null); - expect(row.unset_paths).toEqual(log?.unsetPaths ? log.unsetPaths : null); - expect(row.user_action).toEqual(log?.userAction ? log.userAction : null); - - nbLogs++; - }) - .on('end', () => { - expect(nbLogs).toEqual(expectedLogs.length); - done(); - }); - }).catch((error) => { - console.error(error); - expect(true).toBe(false); - done(); - }); - + }); }); - }); - }); describe('Query paradata temp view', () => { - // Prepare two interviews, one complete and one incomplete let completeInterviewId: number | undefined; let incompleteInterviewId: number | undefined; const completeInterviewAttributes = _cloneDeep(testInterviewAttributes1); delete completeInterviewAttributes.id; - completeInterviewAttributes.response = { - ...completeInterviewAttributes.response, - _completedAt: new Date().toISOString(), - _isCompleted: true - } + completeInterviewAttributes.response = { ...completeInterviewAttributes.response, _completedAt: new Date().toISOString(), _isCompleted: true }; const incompleteInterviewAttributes = _cloneDeep(testInterviewAttributes2); delete incompleteInterviewAttributes.id; @@ -694,7 +632,7 @@ describe('Query paradata temp view', () => { // Create the temp view in the transaction await dbQueries.createParadataWithWidgetPathTable(trx); return trx; - } + }; describe('getIncompleteInterviewsLastEventCounts', () => { test('No events logged', async () => { @@ -713,19 +651,15 @@ describe('Query paradata temp view', () => { const logData = [ { eventType: 'widget_interaction', - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true } - }, { + valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true } + }, + { eventType: 'widget_interaction', - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true }, + valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true }, unsetPaths: [] - }, { - eventType: 'server_event', - valuesByPath: { }, - unsetPaths: [ 'response.data' ] - }, { - eventType: 'side_effect', - unsetPaths: [ 'response.data.someField', 'validations.data.someField' ] - } + }, + { eventType: 'server_event', valuesByPath: {}, unsetPaths: ['response.data'] }, + { eventType: 'side_effect', unsetPaths: ['response.data.someField', 'validations.data.someField'] } ]; await insertSomeLogs(logData, completeInterviewId!); @@ -747,19 +681,15 @@ describe('Query paradata temp view', () => { const logData = [ { eventType: 'widget_interaction', - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true } - }, { + valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true } + }, + { eventType: 'widget_interaction', - valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true }, + valuesByPath: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.home.geography': true }, unsetPaths: [] - }, { - eventType: 'server_event', - valuesByPath: { }, - unsetPaths: [ 'response.data' ] - }, { - eventType: 'side_effect', - unsetPaths: [ 'response.data.someField', 'validations.data.someField' ] - } + }, + { eventType: 'server_event', valuesByPath: {}, unsetPaths: ['response.data'] }, + { eventType: 'side_effect', unsetPaths: ['response.data.someField', 'validations.data.someField'] } ]; await insertSomeLogs(logData, completeInterviewId!, localUser.id); @@ -775,5 +705,4 @@ describe('Query paradata temp view', () => { } }); }); - }); diff --git a/packages/evolution-backend/src/models/__tests__/participants.db.test.ts b/packages/evolution-backend/src/models/__tests__/participants.db.test.ts index 29f5d4683..09bc8ac4a 100644 --- a/packages/evolution-backend/src/models/__tests__/participants.db.test.ts +++ b/packages/evolution-backend/src/models/__tests__/participants.db.test.ts @@ -12,32 +12,15 @@ import dbQueries from '../participants.db.queries'; import { truncate } from 'chaire-lib-backend/lib/models/db/default.db.queries'; import { ParticipantAttributes } from '../../services/participants/participant'; -const participant1 = { - email: 'test@test.org', - username: 'p1', - preferences: { lang: 'fr' }, - first_name: 'Toto' -}; - -const participant2 = { - email: 'test@example.org', - username: 'p2', - preferences: { lang: 'fr' }, - first_name: 'Toto', -}; - -const participantToInsert = { - email: 'newUser@test.org', - preferences: { lang: 'fr' }, - first_name: 'Foo' -}; +const participant1 = { email: 'test@test.org', username: 'p1', preferences: { lang: 'fr' }, first_name: 'Toto' }; + +const participant2 = { email: 'test@example.org', username: 'p2', preferences: { lang: 'fr' }, first_name: 'Toto' }; + +const participantToInsert = { email: 'newUser@test.org', preferences: { lang: 'fr' }, first_name: 'Foo' }; // Keep participant IDs in an object as otherwise the parameterized test use // values from before the participants are inserted in the `beforeAll` hook -const participantIds = { - participantId1: -1, - participantId2: -1 -} +const participantIds = { participantId1: -1, participantId2: -1 }; beforeAll(async () => { jest.setTimeout(10000); @@ -50,23 +33,23 @@ beforeAll(async () => { participantIds.participantId2 = part2Ret[0].id; }); -afterAll(async() => { +afterAll(async () => { await truncate(knex, 'sv_participants'); await truncate(knex, 'sv_participant_logins'); await knex.destroy(); }); each([ - [{ email: participant1.email }, participant1 ], + [{ email: participant1.email }, participant1], [{}, undefined], [{ email: 'other' }, undefined], - [{ email: participant1.email.toUpperCase() }, participant1 ], + [{ email: participant1.email.toUpperCase() }, participant1], [{ usernameOrEmail: participant1.email }, participant1], [{ usernameOrEmail: participant1.username }, participant1], [{ username: participant2.username, email: 'arbitrary' }, undefined], [{ usernameOrEmail: participant2.email.toUpperCase() }, participant2], - [{ usernameOrEmail: participant2.email.toUpperCase() }, participant2], -]).test('Find participant by %s', async(data, expected) => { + [{ usernameOrEmail: participant2.email.toUpperCase() }, participant2] +]).test('Find participant by %s', async (data, expected) => { const participant = await dbQueries.find(data); if (expected === undefined) { expect(participant).toBeUndefined(); @@ -79,7 +62,7 @@ each([ ['participantId1', participant1], ['participantId2', participant2], [50, undefined] -]).test('getById %s', async(id, expected) => { +]).test('getById %s', async (id, expected) => { const participantId = typeof id === 'string' ? participantIds[id] : id; const user = await dbQueries.getById(participantId); if (expected === undefined) { @@ -90,53 +73,47 @@ each([ }); test('Create new participant', async () => { - const participantAttributes = await dbQueries.create(participantToInsert); expect(participantAttributes.id).toBeDefined(); expect(typeof participantAttributes.id).toEqual('number'); - }); test('Create new participant with duplicate key', async () => { - - await expect(dbQueries.create(participantToInsert)) - .rejects - .toThrow(expect.anything()); - + await expect(dbQueries.create(participantToInsert)).rejects.toThrow(expect.anything()); }); test('Update participant', async () => { - // Update the first name const newName = 'Newname'; - const { id, first_name, ...origUser } = await dbQueries.getById(participantIds.participantId1) as ParticipantAttributes; + const { id, first_name, ...origUser } = (await dbQueries.getById(participantIds.participantId1)) as ParticipantAttributes; const updatedAttributes = _cloneDeep(origUser) as ParticipantAttributes; updatedAttributes.first_name = newName; expect(await dbQueries.update(participantIds.participantId1, updatedAttributes)).toEqual(true); - const updatedUser = await dbQueries.getById(participantIds.participantId1) as ParticipantAttributes; + const updatedUser = (await dbQueries.getById(participantIds.participantId1)) as ParticipantAttributes; expect(updatedUser.first_name).toEqual(newName); // Try to update id or email expect(await dbQueries.update(participantIds.participantId1, { email: 'new@test.org', id: participantIds.participantId1 + 10 })).toEqual(false); - const updatedUser2 = await dbQueries.getById(participantIds.participantId1) as ParticipantAttributes; + const updatedUser2 = (await dbQueries.getById(participantIds.participantId1)) as ParticipantAttributes; expect(updatedUser2).toEqual(updatedUser); }); describe('logLastLogin', () => { - test('Correctly log last login', async () => { // Save current timestamp const testStart = Date.now(); - + // Validate there are no logins in the database const currentLastLogin = await knex.table('sv_participant_logins').select('*'); expect(currentLastLogin.length).toEqual(0); - + // Log a first login await dbQueries.logLastLogin(participantIds.participantId1); - + // Validate that the login was logged - const newLastLogin = await knex('sv_participant_logins').select(['*', knex.raw('EXTRACT(EPOCH FROM timestamp) as unix_timestamp')]).orderBy('timestamp'); + const newLastLogin = await knex('sv_participant_logins') + .select(['*', knex.raw('EXTRACT(EPOCH FROM timestamp) as unix_timestamp')]) + .orderBy('timestamp'); const afterFirstInsert = Date.now() + 1; // Add 1ms to make sure we are not equal to now at the ms precision (timestamps are at us precision) expect(newLastLogin.length).toEqual(1); expect(newLastLogin[0].participant_id).toEqual(participantIds.participantId1); @@ -145,15 +122,16 @@ describe('logLastLogin', () => { // Validate all timestamps are greater than previous or test start and less than now (at us precision, it should not be equal) expect(newLastLogin[0].unix_timestamp * 1000).toBeGreaterThan(testStart); expect(newLastLogin[0].unix_timestamp * 1000).toBeLessThan(afterFirstInsert); - - + // Add other logs, one for same participant, one for other again await dbQueries.logLastLogin(participantIds.participantId1); await dbQueries.logLastLogin(participantIds.participantId2); - const afterSecondInsert = Date.now() + 1; + const afterSecondInsert = Date.now() + 1; // Validate that the logins were logged - const newLastLogin2 = await knex('sv_participant_logins').select(['*', knex.raw('EXTRACT(EPOCH FROM timestamp) as unix_timestamp')]).orderBy('timestamp'); + const newLastLogin2 = await knex('sv_participant_logins') + .select(['*', knex.raw('EXTRACT(EPOCH FROM timestamp) as unix_timestamp')]) + .orderBy('timestamp'); expect(newLastLogin2.length).toEqual(3); // Validate the participant ID order expect(newLastLogin2[0].participant_id).toEqual(participantIds.participantId1); @@ -169,8 +147,6 @@ describe('logLastLogin', () => { }); test('Insert for an unexisting participant', async () => { - await expect(dbQueries.logLastLogin(participantIds.participantId1 + 1000)) - .rejects - .toThrow(expect.anything()); + await expect(dbQueries.logLastLogin(participantIds.participantId1 + 1000)).rejects.toThrow(expect.anything()); }); }); diff --git a/packages/evolution-backend/src/services/__tests__/accessCode.test.ts b/packages/evolution-backend/src/services/__tests__/accessCode.test.ts index 403a6c597..2a19df130 100644 --- a/packages/evolution-backend/src/services/__tests__/accessCode.test.ts +++ b/packages/evolution-backend/src/services/__tests__/accessCode.test.ts @@ -14,10 +14,10 @@ describe('Access code validation', () => { ['Too short', '14323', false], ['Too long', '1542927564892', false], ['valid code 2', validCode, true], - ['Changed first digit', (parseInt(validCode[0]) + 1 % 10) + validCode.slice(1), false], - ['Changed last digit', validCode.slice(0, validCode.length - 1) + (parseInt(validCode[validCode.length - 1]) + 1 % 10), false], - ['Alphanumeric', '83abcd734', false], + ['Changed first digit', parseInt(validCode[0]) + (1 % 10) + validCode.slice(1), false], + ['Changed last digit', validCode.slice(0, validCode.length - 1) + (parseInt(validCode[validCode.length - 1]) + (1 % 10)), false], + ['Alphanumeric', '83abcd734', false] ]).test('%s', (_description, accessCode, result) => { expect(validateAccessCode(accessCode)).toEqual(result); }); -}); \ No newline at end of file +}); diff --git a/packages/evolution-backend/src/services/adminExport/__tests__/exportAllToCsvByObject.test.ts b/packages/evolution-backend/src/services/adminExport/__tests__/exportAllToCsvByObject.test.ts index 6d70d43ee..fe10f0304 100644 --- a/packages/evolution-backend/src/services/adminExport/__tests__/exportAllToCsvByObject.test.ts +++ b/packages/evolution-backend/src/services/adminExport/__tests__/exportAllToCsvByObject.test.ts @@ -12,13 +12,11 @@ import interviewsDbQueries from '../../../models/interviews.db.queries'; import { exportAllToCsvBySurveyObjectTask } from '../exportAllToCsvBySurveyObject'; // Mock the database log stream -jest.mock('../../../models/interviews.db.queries', () => ({ - getInterviewsStream: jest.fn().mockImplementation(() => new ObjectReadableMock([])) -})); +jest.mock('../../../models/interviews.db.queries', () => ({ getInterviewsStream: jest.fn().mockImplementation(() => new ObjectReadableMock([])) })); const mockGetInterviewsStream = interviewsDbQueries.getInterviewsStream as jest.MockedFunction; // Mock the csv file stream -let fileStreams: {[key: string]: ObjectWritableMock } = {}; +let fileStreams: { [key: string]: ObjectWritableMock } = {}; const mockCreateStream = jest.fn().mockImplementation((filename: string) => { fileStreams[filename] = new ObjectWritableMock(); return fileStreams[filename]; @@ -28,10 +26,7 @@ jest.mock('fs', () => { // Require the original module to not be mocked... const originalModule = jest.requireActual('fs'); - return { - ...originalModule, - createWriteStream: (fileName: string) => mockCreateStream(fileName) - }; + return { ...originalModule, createWriteStream: (fileName: string) => mockCreateStream(fileName) }; }); beforeEach(() => { @@ -40,7 +35,6 @@ beforeEach(() => { }); describe('exportAllToCsvBySurveyObject', () => { - const getCsvFileRows = (csvData: string[]): Promise => { const input = csvData.join(''); const rows: any[] = []; @@ -72,26 +66,20 @@ describe('exportAllToCsvBySurveyObject', () => { const interviewData = { id: 1, uuid: uuidV4(), - 'updated_at': '2024-10-11 09:02:00', + updated_at: '2024-10-11 09:02:00', is_valid: true, is_completed: true, is_validated: null, is_questionable: null, response: { - household: { - size: 1, - persons: { - [person1Uuid]: { - _uuid: person1Uuid, - age: 30 - } - }, - personsDidTrips: [person1Uuid] - }, - arrayOfObjects: [{ a: 1, b: 2 }, { a: 3, b: 4 }], - arrayOfStrings: ['a', 'b', 'c'], + household: { size: 1, persons: { [person1Uuid]: { _uuid: person1Uuid, age: 30 } }, personsDidTrips: [person1Uuid] }, + arrayOfObjects: [ + { a: 1, b: 2 }, + { a: 3, b: 4 } + ], + arrayOfStrings: ['a', 'b', 'c'] }, - corrected_response_available: true, + corrected_response_available: true }; // Add the interview to the stream twice, for the paths and the export @@ -103,7 +91,10 @@ describe('exportAllToCsvBySurveyObject', () => { // Check the file content of the exported files, there should be one file for persons, one for the interview expect(mockCreateStream).toHaveBeenCalledTimes(2); expect(mockGetInterviewsStream).toHaveBeenCalledTimes(2); - expect(mockGetInterviewsStream).toHaveBeenCalledWith({ filters: {}, select: { includeAudits: false, includeInterviewerData: true, responseType: 'correctedIfAvailable' } }); + expect(mockGetInterviewsStream).toHaveBeenCalledWith({ + filters: {}, + select: { includeAudits: false, includeInterviewerData: true, responseType: 'correctedIfAvailable' } + }); // Check the content of the interview file const interviewCsvFileName = Object.keys(fileStreams).find((filename) => filename.endsWith('corrected_interview_test.csv')); @@ -130,7 +121,7 @@ describe('exportAllToCsvBySurveyObject', () => { 'arrayOfObjects.0.b': String(interviewData.response.arrayOfObjects[0].b), 'arrayOfObjects.1.a': String(interviewData.response.arrayOfObjects[1].a), 'arrayOfObjects.1.b': String(interviewData.response.arrayOfObjects[1].b), - 'arrayOfStrings': interviewData.response.arrayOfStrings.join('|'), + arrayOfStrings: interviewData.response.arrayOfStrings.join('|'), 'household.personsDidTrips': person1Uuid }); @@ -150,7 +141,7 @@ describe('exportAllToCsvBySurveyObject', () => { _interviewUuid: interviewData.uuid, _parentUuid: interviewData.uuid, _uuid: person1Uuid, - age: String(interviewData.response.household.persons[person1Uuid].age), + age: String(interviewData.response.household.persons[person1Uuid].age) }); }); @@ -164,7 +155,7 @@ describe('exportAllToCsvBySurveyObject', () => { const interviewData = { id: 1, uuid: uuidV4(), - 'updated_at': '2024-10-11 09:02:00', + updated_at: '2024-10-11 09:02:00', is_valid: true, is_completed: true, is_validated: null, @@ -182,13 +173,8 @@ describe('exportAllToCsvBySurveyObject', () => { name: 'Place 1', geography: { type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 2] - }, - properties: { - action: 'mapClicked' - } + geometry: { type: 'Point', coordinates: [1, 2] }, + properties: { action: 'mapClicked' } } } } @@ -203,13 +189,8 @@ describe('exportAllToCsvBySurveyObject', () => { name: 'Place 1', geography: { type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 2] - }, - properties: { - action: 'mapClicked' - } + geometry: { type: 'Point', coordinates: [1, 2] }, + properties: { action: 'mapClicked' } } }, [visitedPlace2P2Uuid]: { @@ -217,13 +198,8 @@ describe('exportAllToCsvBySurveyObject', () => { name: 'Place 2', geography: { type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 2] - }, - properties: { - action: 'mapClicked' - } + geometry: { type: 'Point', coordinates: [1, 2] }, + properties: { action: 'mapClicked' } } } } @@ -232,7 +208,7 @@ describe('exportAllToCsvBySurveyObject', () => { personsDidTrips: [person1Uuid, person2Uuid] } }, - corrected_response_available: true, + corrected_response_available: true }; // Add the interview to the stream twice, for the paths and the export @@ -245,7 +221,10 @@ describe('exportAllToCsvBySurveyObject', () => { // Check the file content of the exported files, there should be one file for persons, one for the interview expect(mockCreateStream).toHaveBeenCalledTimes(3); expect(mockGetInterviewsStream).toHaveBeenCalledTimes(2); - expect(mockGetInterviewsStream).toHaveBeenCalledWith({ filters: {}, select: { includeAudits: false, includeInterviewerData: true, responseType: 'correctedIfAvailable' } }); + expect(mockGetInterviewsStream).toHaveBeenCalledWith({ + filters: {}, + select: { includeAudits: false, includeInterviewerData: true, responseType: 'correctedIfAvailable' } + }); // Check the content of the interview file const interviewCsvFileName = Object.keys(fileStreams).find((filename) => filename.endsWith('corrected_interview_test.csv')); @@ -297,7 +276,9 @@ describe('exportAllToCsvBySurveyObject', () => { }); // Check the content of the visited places file - const visitedPlacesCsvFileName = Object.keys(fileStreams).find((filename) => filename.endsWith('corrected_household_persons_visitedPlaces_test.csv')); + const visitedPlacesCsvFileName = Object.keys(fileStreams).find((filename) => + filename.endsWith('corrected_household_persons_visitedPlaces_test.csv') + ); expect(visitedPlacesCsvFileName).toBeDefined(); const visitedPlacesCsvStream = fileStreams[visitedPlacesCsvFileName as string]; @@ -312,10 +293,16 @@ describe('exportAllToCsvBySurveyObject', () => { _uuid: visitedPlace1P1Uuid, name: interviewData.response.household.persons[person1Uuid].visitedPlaces[visitedPlace1P1Uuid].name, 'geography.type': interviewData.response.household.persons[person1Uuid].visitedPlaces[visitedPlace1P1Uuid].geography.type, - 'geography.properties.action': interviewData.response.household.persons[person1Uuid].visitedPlaces[visitedPlace1P1Uuid].geography.properties.action, - 'geography.geometry.type': interviewData.response.household.persons[person1Uuid].visitedPlaces[visitedPlace1P1Uuid].geography.geometry.type, - 'geography.geometry.lat': String(interviewData.response.household.persons[person1Uuid].visitedPlaces[visitedPlace1P1Uuid].geography.geometry.coordinates[1]), - 'geography.geometry.lon': String(interviewData.response.household.persons[person1Uuid].visitedPlaces[visitedPlace1P1Uuid].geography.geometry.coordinates[0]), + 'geography.properties.action': + interviewData.response.household.persons[person1Uuid].visitedPlaces[visitedPlace1P1Uuid].geography.properties.action, + 'geography.geometry.type': + interviewData.response.household.persons[person1Uuid].visitedPlaces[visitedPlace1P1Uuid].geography.geometry.type, + 'geography.geometry.lat': String( + interviewData.response.household.persons[person1Uuid].visitedPlaces[visitedPlace1P1Uuid].geography.geometry.coordinates[1] + ), + 'geography.geometry.lon': String( + interviewData.response.household.persons[person1Uuid].visitedPlaces[visitedPlace1P1Uuid].geography.geometry.coordinates[0] + ) }); expect(visitedPlacesRows[1]).toEqual({ _interviewUuid: interviewData.uuid, @@ -323,10 +310,16 @@ describe('exportAllToCsvBySurveyObject', () => { _uuid: visitedPlace1P2Uuid, name: interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace1P2Uuid].name, 'geography.type': interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace1P2Uuid].geography.type, - 'geography.properties.action': interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace1P2Uuid].geography.properties.action, - 'geography.geometry.type': interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace1P2Uuid].geography.geometry.type, - 'geography.geometry.lat': String(interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace1P2Uuid].geography.geometry.coordinates[1]), - 'geography.geometry.lon': String(interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace1P2Uuid].geography.geometry.coordinates[0]), + 'geography.properties.action': + interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace1P2Uuid].geography.properties.action, + 'geography.geometry.type': + interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace1P2Uuid].geography.geometry.type, + 'geography.geometry.lat': String( + interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace1P2Uuid].geography.geometry.coordinates[1] + ), + 'geography.geometry.lon': String( + interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace1P2Uuid].geography.geometry.coordinates[0] + ) }); expect(visitedPlacesRows[2]).toEqual({ _interviewUuid: interviewData.uuid, @@ -334,46 +327,52 @@ describe('exportAllToCsvBySurveyObject', () => { _uuid: visitedPlace2P2Uuid, name: interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace2P2Uuid].name, 'geography.type': interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace2P2Uuid].geography.type, - 'geography.properties.action': interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace2P2Uuid].geography.properties.action, - 'geography.geometry.type': interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace2P2Uuid].geography.geometry.type, - 'geography.geometry.lat': String(interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace2P2Uuid].geography.geometry.coordinates[1]), - 'geography.geometry.lon': String(interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace2P2Uuid].geography.geometry.coordinates[0]), + 'geography.properties.action': + interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace2P2Uuid].geography.properties.action, + 'geography.geometry.type': + interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace2P2Uuid].geography.geometry.type, + 'geography.geometry.lat': String( + interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace2P2Uuid].geography.geometry.coordinates[1] + ), + 'geography.geometry.lon': String( + interviewData.response.household.persons[person2Uuid].visitedPlaces[visitedPlace2P2Uuid].geography.geometry.coordinates[0] + ) }); - }); test('Test multiple interviews with divergent response fields', async () => { // Very simple interview data - const interviewData = [{ - id: 1, - uuid: uuidV4(), - 'updated_at': '2024-10-11 09:02:00', - is_valid: true, - is_completed: true, - is_validated: null, - is_questionable: null, - response: { - arrayOfObjectsIn1Only: [{ a: 1, b: 2 }, { a: 3, b: 4 }], - arrayOfStrings: ['a', 'b', 'c'], - arrayOrString: 'string' - }, - corrected_response_available: true, - }, { - id: 2, - uuid: uuidV4(), - 'updated_at': '2024-10-11 09:02:00', - is_valid: true, - is_completed: true, - is_validated: null, - is_questionable: null, - response: { - arrayOfStrings: ['d', 'e', 'c'], - arrayOrString: ['an', 'array'], - fieldIn2: 2, - arrayIn2Only: ['x'] + const interviewData = [ + { + id: 1, + uuid: uuidV4(), + updated_at: '2024-10-11 09:02:00', + is_valid: true, + is_completed: true, + is_validated: null, + is_questionable: null, + response: { + arrayOfObjectsIn1Only: [ + { a: 1, b: 2 }, + { a: 3, b: 4 } + ], + arrayOfStrings: ['a', 'b', 'c'], + arrayOrString: 'string' + }, + corrected_response_available: true }, - corrected_response_available: true, - }]; + { + id: 2, + uuid: uuidV4(), + updated_at: '2024-10-11 09:02:00', + is_valid: true, + is_completed: true, + is_validated: null, + is_questionable: null, + response: { arrayOfStrings: ['d', 'e', 'c'], arrayOrString: ['an', 'array'], fieldIn2: 2, arrayIn2Only: ['x'] }, + corrected_response_available: true + } + ]; // Add the interview to the stream twice, for the paths and the export mockGetInterviewsStream.mockReturnValueOnce(new ObjectReadableMock(interviewData) as any); @@ -384,7 +383,10 @@ describe('exportAllToCsvBySurveyObject', () => { // Check the file content of the exported files, there should be one file for persons, one for the interview expect(mockCreateStream).toHaveBeenCalledTimes(1); expect(mockGetInterviewsStream).toHaveBeenCalledTimes(2); - expect(mockGetInterviewsStream).toHaveBeenCalledWith({ filters: {}, select: { includeAudits: false, includeInterviewerData: true, responseType: 'correctedIfAvailable' } }); + expect(mockGetInterviewsStream).toHaveBeenCalledWith({ + filters: {}, + select: { includeAudits: false, includeInterviewerData: true, responseType: 'correctedIfAvailable' } + }); // Check the content of the interview file const interviewCsvFileName = Object.keys(fileStreams).find((filename) => filename.endsWith('corrected_interview_test.csv')); @@ -409,8 +411,8 @@ describe('exportAllToCsvBySurveyObject', () => { 'arrayOfObjectsIn1Only.0.b': String(interviewData[0].response.arrayOfObjectsIn1Only![0].b), 'arrayOfObjectsIn1Only.1.a': String(interviewData[0].response.arrayOfObjectsIn1Only![1].a), 'arrayOfObjectsIn1Only.1.b': String(interviewData[0].response.arrayOfObjectsIn1Only![1].b), - 'arrayOfStrings': interviewData[0].response.arrayOfStrings.join('|'), - 'arrayOrString': 'string', + arrayOfStrings: interviewData[0].response.arrayOfStrings.join('|'), + arrayOrString: 'string', fieldIn2: '', arrayIn2Only: '' }); @@ -428,12 +430,10 @@ describe('exportAllToCsvBySurveyObject', () => { 'arrayOfObjectsIn1Only.0.b': '', 'arrayOfObjectsIn1Only.1.a': '', 'arrayOfObjectsIn1Only.1.b': '', - 'arrayOfStrings': interviewData[1].response.arrayOfStrings.join('|'), - 'arrayOrString': (interviewData[1].response.arrayOrString as string[]).join('|'), + arrayOfStrings: interviewData[1].response.arrayOfStrings.join('|'), + arrayOrString: (interviewData[1].response.arrayOrString as string[]).join('|'), fieldIn2: String(interviewData[1].response.fieldIn2), - arrayIn2Only: interviewData[1].response.arrayIn2Only!.join('|'), + arrayIn2Only: interviewData[1].response.arrayIn2Only!.join('|') }); - }); - }); diff --git a/packages/evolution-backend/src/services/adminExport/__tests__/exportInterviewLogs.test.ts b/packages/evolution-backend/src/services/adminExport/__tests__/exportInterviewLogs.test.ts index ec49f8b3d..a793055c4 100644 --- a/packages/evolution-backend/src/services/adminExport/__tests__/exportInterviewLogs.test.ts +++ b/packages/evolution-backend/src/services/adminExport/__tests__/exportInterviewLogs.test.ts @@ -12,14 +12,12 @@ import { exportInterviewLogTask } from '../exportInterviewLogs'; import { UserAction } from 'evolution-common/lib/services/questionnaire/types'; // Mock the database log stream -jest.mock('../../../models/paradataEvents.db.queries', () => ({ - getParadataStream: jest.fn().mockImplementation(() => new ObjectReadableMock([])) -})); +jest.mock('../../../models/paradataEvents.db.queries', () => ({ getParadataStream: jest.fn().mockImplementation(() => new ObjectReadableMock([])) })); // FIXME Fix this function when interview logs are back const mockGetInterviewLogsStream = paradataDbQueries.getParadataStream as jest.MockedFunction; // Mock the csv file stream -let fileStreams: {[key: string]: ObjectWritableMock } = {}; +let fileStreams: { [key: string]: ObjectWritableMock } = {}; const mockCreateStream = jest.fn().mockImplementation((filename: string) => { fileStreams[filename] = new ObjectWritableMock(); return fileStreams[filename]; @@ -29,10 +27,7 @@ jest.mock('fs', () => { // Require the original module to not be mocked... const originalModule = jest.requireActual('fs'); - return { - ...originalModule, - createWriteStream: (fileName: string) => mockCreateStream(fileName) - }; + return { ...originalModule, createWriteStream: (fileName: string) => mockCreateStream(fileName) }; }); beforeEach(() => { @@ -41,10 +36,29 @@ beforeEach(() => { }); describe('exportInterviewLogTask', () => { - // Common data for all logs of the interview - const commonInterviewData = { id: 1, uuid: 'uuid', 'updated_at': '2024-10-11 09:02:00', is_valid: true, is_completed: true, is_validated: null, is_questionable: null, interview_is_completed: true, user_id: null }; - const commonInterviewDataInRows = { id: '1', uuid: 'uuid', 'updated_at': '2024-10-11 09:02:00', is_valid: 'true', is_completed: 'true', is_validated: '', is_questionable: '', interview_is_completed: 'true', user_id: '' }; + const commonInterviewData = { + id: 1, + uuid: 'uuid', + updated_at: '2024-10-11 09:02:00', + is_valid: true, + is_completed: true, + is_validated: null, + is_questionable: null, + interview_is_completed: true, + user_id: null + }; + const commonInterviewDataInRows = { + id: '1', + uuid: 'uuid', + updated_at: '2024-10-11 09:02:00', + is_valid: 'true', + is_completed: 'true', + is_validated: '', + is_questionable: '', + interview_is_completed: 'true', + user_id: '' + }; // Various logs to test different situations, the last one is not from the participant const logs: { [key: string]: any }[] = [ @@ -53,47 +67,62 @@ describe('exportInterviewLogTask', () => { ...commonInterviewData, timestamp_sec: 1, event_date: new Date(1 * 1000), - values_by_path: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true, 'response.household.size': 3, 'response._activeTripId': null }, - unset_paths: [ 'response.home.someField', 'validations.home.someField' ], + values_by_path: { + 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, + 'validations.home.geography': true, + 'response.household.size': 3, + 'response._activeTripId': null + }, + unset_paths: ['response.home.someField', 'validations.home.someField'], for_correction: false - }, { + }, + { // No unset paths ...commonInterviewData, timestamp_sec: 2, event_date: new Date(2 * 1000), values_by_path: { 'response.home.address': 'somewhere over the rainbow', 'validations.home.address': true }, for_correction: false - }, { + }, + { // Unset paths, but empty values_by_path ...commonInterviewData, timestamp_sec: 3, event_date: new Date(3 * 1000), values_by_path: {}, - unset_paths: [ 'response.home.address', 'validations.home.address' ], + unset_paths: ['response.home.address', 'validations.home.address'], for_correction: false - }, { + }, + { // No participant response in values_by_path ...commonInterviewData, timestamp_sec: 4, event_date: new Date(4 * 1000), values_by_path: { 'validations.home.region': true, 'validations.home.country': true }, - unset_paths: [ 'response.home.region', 'response.home.country' ], + unset_paths: ['response.home.region', 'response.home.country'], for_correction: false - }, { + }, + { // No participant response in unset_paths ...commonInterviewData, timestamp_sec: 5, event_date: new Date(5 * 1000), - values_by_path: { 'response.household.carNumber': 1, 'response.household.bikeNumber': 10, 'validations.household.carNumber': false, 'validations.household.bikeNumber': true }, - unset_paths: [ 'validations.home.region', 'validations.home.country' ], + values_by_path: { + 'response.household.carNumber': 1, + 'response.household.bikeNumber': 10, + 'validations.household.carNumber': false, + 'validations.household.bikeNumber': true + }, + unset_paths: ['validations.home.region', 'validations.home.country'], for_correction: false - }, { + }, + { // No participant response in values_by_path and unset_paths ...commonInterviewData, timestamp_sec: 6, event_date: new Date(6 * 1000), values_by_path: { 'corrected_response.home.address': '6760 rue Saint-Vallier Montréal', 'corrected_response.home.city': 'Montréal' }, - unset_paths: [ 'corrected_response.home.country' ], + unset_paths: ['corrected_response.home.country'], for_correction: true } ]; @@ -146,16 +175,22 @@ describe('exportInterviewLogTask', () => { // One row per log, with modified fields and unset fields for each for (let i = 0; i < logs.length; i++) { - expect(logRows[i]).toEqual(expect.objectContaining({ - ...commonInterviewDataInRows - })); - const modifiedKeys = Object.entries(logs[i].values_by_path).filter(([key, value]) => value !== null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - const initializedKeys = Object.entries(logs[i].values_by_path).filter(([key, value]) => value === null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - expect(logRows[i].timestampMs).toEqual(String((i+1) * 1000)); - expect(logRows[i].event_date).toEqual(new Date((i+1) * 1000).toISOString()); + expect(logRows[i]).toEqual(expect.objectContaining({ ...commonInterviewDataInRows })); + const modifiedKeys = Object.entries(logs[i].values_by_path) + .filter(([key, value]) => value !== null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeys = Object.entries(logs[i].values_by_path) + .filter(([key, value]) => value === null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + expect(logRows[i].timestampMs).toEqual(String((i + 1) * 1000)); + expect(logRows[i].event_date).toEqual(new Date((i + 1) * 1000).toISOString()); expect(logRows[i].modifiedFields).toEqual(modifiedKeys); expect(logRows[i].initializedFields).toEqual(initializedKeys); - expect(logRows[i].unsetFields).toEqual(logs[i].unset_paths !== undefined ? logs[i].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') : ''); + expect(logRows[i].unsetFields).toEqual( + logs[i].unset_paths !== undefined ? logs[i].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') : '' + ); expect(logRows[i].widgetType).toEqual(''); expect(logRows[i].widgetPath).toEqual(''); } @@ -163,7 +198,7 @@ describe('exportInterviewLogTask', () => { test('Test only participant response, no values', async () => { // Add the logs to the stream, only those with for_correction not true - mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(logs.filter(log => log.for_correction !== true)) as any); + mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(logs.filter((log) => log.for_correction !== true)) as any); const fileName = await exportInterviewLogTask({ participantResponseOnly: true }); @@ -184,16 +219,24 @@ describe('exportInterviewLogTask', () => { // One row per log, with modified fields and unset fields for each for (let i = 0; i < logs.length - 1; i++) { - expect(logRows[i]).toEqual(expect.objectContaining({ - ...commonInterviewDataInRows - })); - const modifiedKeys = Object.entries(logs[i].values_by_path).filter(([key, value]) => value !== null).filter(([key, value]) => key.startsWith('response.')).map(([key, value]) => key).join('|'); - const initializedKeys = Object.entries(logs[i].values_by_path).filter(([key, value]) => value === null).filter(([key, value]) => key.startsWith('response.')).map(([key, value]) => key).join('|'); - expect(logRows[i].timestampMs).toEqual(String((i+1) * 1000)); - expect(logRows[i].event_date).toEqual(new Date((i+1) * 1000).toISOString()); + expect(logRows[i]).toEqual(expect.objectContaining({ ...commonInterviewDataInRows })); + const modifiedKeys = Object.entries(logs[i].values_by_path) + .filter(([key, value]) => value !== null) + .filter(([key, value]) => key.startsWith('response.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeys = Object.entries(logs[i].values_by_path) + .filter(([key, value]) => value === null) + .filter(([key, value]) => key.startsWith('response.')) + .map(([key, value]) => key) + .join('|'); + expect(logRows[i].timestampMs).toEqual(String((i + 1) * 1000)); + expect(logRows[i].event_date).toEqual(new Date((i + 1) * 1000).toISOString()); expect(logRows[i].modifiedFields).toEqual(modifiedKeys); expect(logRows[i].initializedFields).toEqual(initializedKeys); - expect(logRows[i].unsetFields).toEqual(logs[i].unset_paths !== undefined ? logs[i].unset_paths.filter((key) => key.startsWith('response.')).join('|') : ''); + expect(logRows[i].unsetFields).toEqual( + logs[i].unset_paths !== undefined ? logs[i].unset_paths.filter((key) => key.startsWith('response.')).join('|') : '' + ); expect(logRows[i].widgetType).toEqual(''); expect(logRows[i].widgetPath).toEqual(''); } @@ -201,7 +244,7 @@ describe('exportInterviewLogTask', () => { test('Test only participant response, no values, with `null` in `for_correction`', async () => { // Add the logs to the stream, but with `null` in `for_correction` - mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(logs.map(log => ({ ...log, for_correction: null }))) as any); + mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(logs.map((log) => ({ ...log, for_correction: null }))) as any); const fileName = await exportInterviewLogTask({ participantResponseOnly: true }); @@ -222,16 +265,24 @@ describe('exportInterviewLogTask', () => { // One row per log, with modified fields and unset fields for each for (let i = 0; i < logs.length - 1; i++) { - expect(logRows[i]).toEqual(expect.objectContaining({ - ...commonInterviewDataInRows - })); - const modifiedKeys = Object.entries(logs[i].values_by_path).filter(([key, value]) => value !== null).filter(([key, value]) => key.startsWith('response.')).map(([key, value]) => key).join('|'); - const initializedKeys = Object.entries(logs[i].values_by_path).filter(([key, value]) => value === null).filter(([key, value]) => key.startsWith('response.')).map(([key, value]) => key).join('|'); - expect(logRows[i].timestampMs).toEqual(String((i+1) * 1000)); - expect(logRows[i].event_date).toEqual(new Date((i+1) * 1000).toISOString()); + expect(logRows[i]).toEqual(expect.objectContaining({ ...commonInterviewDataInRows })); + const modifiedKeys = Object.entries(logs[i].values_by_path) + .filter(([key, value]) => value !== null) + .filter(([key, value]) => key.startsWith('response.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeys = Object.entries(logs[i].values_by_path) + .filter(([key, value]) => value === null) + .filter(([key, value]) => key.startsWith('response.')) + .map(([key, value]) => key) + .join('|'); + expect(logRows[i].timestampMs).toEqual(String((i + 1) * 1000)); + expect(logRows[i].event_date).toEqual(new Date((i + 1) * 1000).toISOString()); expect(logRows[i].modifiedFields).toEqual(modifiedKeys); expect(logRows[i].initializedFields).toEqual(initializedKeys); - expect(logRows[i].unsetFields).toEqual(logs[i].unset_paths !== undefined ? logs[i].unset_paths.filter((key) => key.startsWith('response.')).join('|') : ''); + expect(logRows[i].unsetFields).toEqual( + logs[i].unset_paths !== undefined ? logs[i].unset_paths.filter((key) => key.startsWith('response.')).join('|') : '' + ); expect(logRows[i].widgetType).toEqual(''); expect(logRows[i].widgetPath).toEqual(''); } @@ -265,12 +316,12 @@ describe('exportInterviewLogTask', () => { const currentLog = logs[i]; // Find a row for each value by path for (const [key, value] of Object.entries(currentLog.values_by_path)) { - const foundRow = logRows.find((row) => row.field === key && row.timestampMs === String((i+1) * 1000)); + const foundRow = logRows.find((row) => row.field === key && row.timestampMs === String((i + 1) * 1000)); expect(foundRow).toBeDefined(); expect(foundRow).toEqual({ ...commonInterviewDataInRows, - timestampMs: String((i+1) * 1000), - event_date: new Date((i+1) * 1000).toISOString(), + timestampMs: String((i + 1) * 1000), + event_date: new Date((i + 1) * 1000).toISOString(), field: key, value: JSON.stringify(value), for_correction: currentLog.for_correction ? 'true' : 'false' @@ -278,12 +329,12 @@ describe('exportInterviewLogTask', () => { } // Find a row for each unset path for (const path of currentLog.unset_paths || []) { - const foundRow = logRows.find((row) => row.field === path && row.timestampMs === String((i+1) * 1000)); + const foundRow = logRows.find((row) => row.field === path && row.timestampMs === String((i + 1) * 1000)); expect(foundRow).toBeDefined(); expect(foundRow).toEqual({ ...commonInterviewDataInRows, - timestampMs: String((i+1) * 1000), - event_date: new Date((i+1) * 1000).toISOString(), + timestampMs: String((i + 1) * 1000), + event_date: new Date((i + 1) * 1000).toISOString(), field: path, value: '', for_correction: currentLog.for_correction ? 'true' : 'false' @@ -294,7 +345,7 @@ describe('exportInterviewLogTask', () => { test('Test only participant response, with values', async () => { // Add the logs to the stream, only those with for_correction not true - mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(logs.filter(log => log.for_correction !== true)) as any); + mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(logs.filter((log) => log.for_correction !== true)) as any); const fileName = await exportInterviewLogTask({ withValues: true, participantResponseOnly: true }); @@ -312,7 +363,13 @@ describe('exportInterviewLogTask', () => { // Get the actual rows in the file data const logRows = await getCsvFileRows(csvStream.data); // There should be one row per values_by_path key-value pair that is for the response field, plus one row per unset path with response field - const expectedCount = logs.reduce((acc, log) => acc + Object.keys(log.values_by_path).filter((key) => key.startsWith('response.')).length + (log.unset_paths || []).filter((key) => key.startsWith('response.')).length, 0); + const expectedCount = logs.reduce( + (acc, log) => + acc + + Object.keys(log.values_by_path).filter((key) => key.startsWith('response.')).length + + (log.unset_paths || []).filter((key) => key.startsWith('response.')).length, + 0 + ); expect(logRows.length).toEqual(expectedCount); // For each element in the logs, make sure there is a corresponding row in the data @@ -320,13 +377,13 @@ describe('exportInterviewLogTask', () => { const currentLog = logs[i]; // Find a row for each value by path for (const [key, value] of Object.entries(currentLog.values_by_path)) { - const foundRow = logRows.find((row) => row.field === key && row.timestampMs === String((i+1) * 1000)); + const foundRow = logRows.find((row) => row.field === key && row.timestampMs === String((i + 1) * 1000)); if (key.startsWith('response.')) { expect(foundRow).toBeDefined(); expect(foundRow).toEqual({ ...commonInterviewDataInRows, - event_date: new Date((i+1) * 1000).toISOString(), - timestampMs: String((i+1) * 1000), + event_date: new Date((i + 1) * 1000).toISOString(), + timestampMs: String((i + 1) * 1000), field: key, value: JSON.stringify(value), for_correction: currentLog.for_correction ? 'true' : 'false' @@ -337,13 +394,13 @@ describe('exportInterviewLogTask', () => { } // Find a row for each unset path for (const path of currentLog.unset_paths || []) { - const foundRow = logRows.find((row) => row.field === path && row.timestampMs === String((i+1) * 1000)); + const foundRow = logRows.find((row) => row.field === path && row.timestampMs === String((i + 1) * 1000)); if (path.startsWith('response.')) { expect(foundRow).toBeDefined(); expect(foundRow).toEqual({ ...commonInterviewDataInRows, - event_date: new Date((i+1) * 1000).toISOString(), - timestampMs: String((i+1) * 1000), + event_date: new Date((i + 1) * 1000).toISOString(), + timestampMs: String((i + 1) * 1000), field: path, value: '', for_correction: currentLog.for_correction ? 'true' : 'false' @@ -351,7 +408,6 @@ describe('exportInterviewLogTask', () => { } else { expect(foundRow).toBeUndefined(); } - } } }); @@ -379,8 +435,13 @@ describe('exportInterviewLogTask', () => { event_type: 'widget_interaction', timestamp_sec: 1, event_date: new Date(1 * 1000), - values_by_path: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true, 'response.household.size': 3, 'response._activeTripId': null }, - unset_paths: [ 'response.home.someField', 'validations.home.someField' ], + values_by_path: { + 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, + 'validations.home.geography': true, + 'response.household.size': 3, + 'response._activeTripId': null + }, + unset_paths: ['response.home.someField', 'validations.home.someField'], user_action: userAction }; // Add the logs to the stream @@ -407,17 +468,22 @@ describe('exportInterviewLogTask', () => { // Test the row values const currentLog = logRows[0]; - expect(currentLog).toEqual(expect.objectContaining({ - ...commonInterviewDataInRows, - event_type: 'widget_interaction' - })); - const modifiedKeys = Object.entries(log.values_by_path).filter(([key, value]) => value !== null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - const initializedKeys = Object.entries(log.values_by_path).filter(([key, value]) => value === null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - expect(currentLog.timestampMs).toEqual(String((1) * 1000)); - expect(currentLog.event_date).toEqual(new Date((1) * 1000).toISOString()); + expect(currentLog).toEqual(expect.objectContaining({ ...commonInterviewDataInRows, event_type: 'widget_interaction' })); + const modifiedKeys = Object.entries(log.values_by_path) + .filter(([key, value]) => value !== null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeys = Object.entries(log.values_by_path) + .filter(([key, value]) => value === null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + expect(currentLog.timestampMs).toEqual(String(1 * 1000)); + expect(currentLog.event_date).toEqual(new Date(1 * 1000).toISOString()); expect(currentLog.modifiedFields).toEqual(modifiedKeys); expect(currentLog.initializedFields).toEqual(initializedKeys); - expect(currentLog.unsetFields).toEqual(log.unset_paths !== undefined ? log.unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') : ''); + expect(currentLog.unsetFields).toEqual( + log.unset_paths !== undefined ? log.unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') : '' + ); expect(currentLog.widgetType).toEqual(userAction.widgetType); expect(currentLog.widgetPath).toEqual(userAction.path); expect(currentLog.invalidFields).toEqual(''); @@ -432,8 +498,8 @@ describe('exportInterviewLogTask', () => { event_type: 'widget_interaction', timestamp_sec: 1, event_date: new Date(1 * 1000), - values_by_path: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] } }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] } }, + unset_paths: ['response.home.someField'], user_action: userAction }; // Add the logs to the stream @@ -461,42 +527,41 @@ describe('exportInterviewLogTask', () => { // Find a row for each value by path for (const [key, value] of Object.entries(log.values_by_path)) { - const foundRow = logRows.find((row) => row.field === key && row.timestampMs === String((1) * 1000)); + const foundRow = logRows.find((row) => row.field === key && row.timestampMs === String(1 * 1000)); expect(foundRow).toBeDefined(); expect(foundRow).toEqual({ ...commonInterviewDataInRows, event_type: 'widget_interaction', - timestampMs: String((1) * 1000), - event_date: new Date((1) * 1000).toISOString(), + timestampMs: String(1 * 1000), + event_date: new Date(1 * 1000).toISOString(), field: key, value: JSON.stringify(value) }); } // Find a row for each unset path for (const path of log.unset_paths || []) { - const foundRow = logRows.find((row) => row.field === path && row.timestampMs === String((1) * 1000)); + const foundRow = logRows.find((row) => row.field === path && row.timestampMs === String(1 * 1000)); expect(foundRow).toBeDefined(); expect(foundRow).toEqual({ ...commonInterviewDataInRows, event_type: 'widget_interaction', - timestampMs: String((1) * 1000), - event_date: new Date((1) * 1000).toISOString(), + timestampMs: String(1 * 1000), + event_date: new Date(1 * 1000).toISOString(), field: path, value: '' }); } // Find a row for the user action - const foundRow = logRows.find((row) => row.field === userAction.path && row.timestampMs === String((1) * 1000)); + const foundRow = logRows.find((row) => row.field === userAction.path && row.timestampMs === String(1 * 1000)); expect(foundRow).toBeDefined(); expect(foundRow).toEqual({ ...commonInterviewDataInRows, event_type: 'widget_interaction', - timestampMs: String((1) * 1000), - event_date: new Date((1) * 1000).toISOString(), + timestampMs: String(1 * 1000), + event_date: new Date(1 * 1000).toISOString(), field: userAction.path, value: JSON.stringify(userAction.value) }); - }); describe('Tests with button_click events', () => { @@ -505,50 +570,33 @@ describe('exportInterviewLogTask', () => { description: 'basic user action', userAction: { type: 'buttonClick', buttonId: 'response.someField' }, values_by_path: { - 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, + 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'response.household.size': 3, 'response._activeTripId': null }, - unset_paths: [ 'response.home.someField', 'validations.home.someField' ], - expectedOutput: { - hiddenWidgets: '', - invalidFields: '' - } + unset_paths: ['response.home.someField', 'validations.home.someField'], + expectedOutput: { hiddenWidgets: '', invalidFields: '' } }, { description: 'hidden widgets', - userAction: { type: 'buttonClick', buttonId: 'response.otherField', hiddenWidgets: [ 'hiddenWidget1', 'hiddenWidget2' ] }, - values_by_path: { }, + userAction: { type: 'buttonClick', buttonId: 'response.otherField', hiddenWidgets: ['hiddenWidget1', 'hiddenWidget2'] }, + values_by_path: {}, unset_paths: undefined, - expectedOutput: { - hiddenWidgets: 'hiddenWidget1|hiddenWidget2', - invalidFields: '' - } + expectedOutput: { hiddenWidgets: 'hiddenWidget1|hiddenWidget2', invalidFields: '' } }, { description: 'invalid widgets in user action', - userAction: { type: 'buttonClick', buttonId: 'response.otherField', invalidWidgets: [ 'invalidWidget1', 'invalidWidget2' ] }, - values_by_path: { - 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] } - }, - unset_paths: [ 'response.home.someField' ], - expectedOutput: { - hiddenWidgets: '', - invalidFields: 'invalidWidget1|invalidWidget2' - } + userAction: { type: 'buttonClick', buttonId: 'response.otherField', invalidWidgets: ['invalidWidget1', 'invalidWidget2'] }, + values_by_path: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] } }, + unset_paths: ['response.home.someField'], + expectedOutput: { hiddenWidgets: '', invalidFields: 'invalidWidget1|invalidWidget2' } }, { description: 'invalid widgets in user action and values_by_path', - userAction: { type: 'buttonClick', buttonId: 'response.otherField', invalidWidgets: [ 'invalidWidget1', 'invalidWidget2' ] }, - values_by_path: { - 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, - 'validations.valuesByPathInvalid': false - }, - unset_paths: [ 'response.home.someField' ], - expectedOutput: { - hiddenWidgets: '', - invalidFields: 'valuesByPathInvalid|invalidWidget1|invalidWidget2' - } + userAction: { type: 'buttonClick', buttonId: 'response.otherField', invalidWidgets: ['invalidWidget1', 'invalidWidget2'] }, + values_by_path: { 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, 'validations.valuesByPathInvalid': false }, + unset_paths: ['response.home.someField'], + expectedOutput: { hiddenWidgets: '', invalidFields: 'valuesByPathInvalid|invalidWidget1|invalidWidget2' } } ]; @@ -584,13 +632,19 @@ describe('exportInterviewLogTask', () => { expect(logRows.length).toEqual(1); // Test the row values - const modifiedKeys = Object.entries(values_by_path).filter(([key, value]) => value !== null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - const initializedKeys = Object.entries(values_by_path).filter(([key, value]) => value === null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); + const modifiedKeys = Object.entries(values_by_path) + .filter(([key, value]) => value !== null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeys = Object.entries(values_by_path) + .filter(([key, value]) => value === null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); expect(logRows[0]).toEqual({ ...commonInterviewDataInRows, event_type: 'button_click', - timestampMs : String((1) * 1000), - event_date: new Date((1) * 1000).toISOString(), + timestampMs: String(1 * 1000), + event_date: new Date(1 * 1000).toISOString(), modifiedFields: modifiedKeys, initializedFields: initializedKeys, unsetFields: unset_paths !== undefined ? unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') : '', @@ -610,23 +664,36 @@ describe('exportInterviewLogTask', () => { test('Test with an event of type section_change with user action', async () => { // Just add one log statement to test the widget_interaction event: const userAction = { type: 'sectionChange', targetSection: { sectionShortname: 'someSection' } }; - const userActionWithHidden = { type: 'sectionChange', targetSection: { sectionShortname: 'someSection', iterationContext: ['person', 'personId'] }, previousSection: { sectionShortname: 'prevSection' }, hiddenWidgets: [ 'hiddenWidget1', 'hiddenWidget2' ] }; - const sectionChangeLogs: { [key: string]: any }[] = [{ - ...commonInterviewData, - event_type: 'section_change', - timestamp_sec: 1, - event_date: new Date(1 * 1000), - values_by_path: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true, 'response.household.size': 3, 'response._activeTripId': null }, - unset_paths: [ 'response.home.someField', 'validations.home.someField' ], - user_action: userAction - }, { - ...commonInterviewData, - event_type: 'section_change', - timestamp_sec: 2, - event_date: new Date(2 * 1000), - values_by_path: { }, - user_action: userActionWithHidden - }]; + const userActionWithHidden = { + type: 'sectionChange', + targetSection: { sectionShortname: 'someSection', iterationContext: ['person', 'personId'] }, + previousSection: { sectionShortname: 'prevSection' }, + hiddenWidgets: ['hiddenWidget1', 'hiddenWidget2'] + }; + const sectionChangeLogs: { [key: string]: any }[] = [ + { + ...commonInterviewData, + event_type: 'section_change', + timestamp_sec: 1, + event_date: new Date(1 * 1000), + values_by_path: { + 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, + 'validations.home.geography': true, + 'response.household.size': 3, + 'response._activeTripId': null + }, + unset_paths: ['response.home.someField', 'validations.home.someField'], + user_action: userAction + }, + { + ...commonInterviewData, + event_type: 'section_change', + timestamp_sec: 2, + event_date: new Date(2 * 1000), + values_by_path: {}, + user_action: userActionWithHidden + } + ]; // Add the logs to the stream mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(sectionChangeLogs) as any); @@ -649,16 +716,25 @@ describe('exportInterviewLogTask', () => { expect(logRows.length).toEqual(sectionChangeLogs.length); // Test the row values - const modifiedKeys = Object.entries(sectionChangeLogs[0].values_by_path).filter(([key, value]) => value !== null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - const initializedKeys = Object.entries(sectionChangeLogs[0].values_by_path).filter(([key, value]) => value === null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); + const modifiedKeys = Object.entries(sectionChangeLogs[0].values_by_path) + .filter(([key, value]) => value !== null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeys = Object.entries(sectionChangeLogs[0].values_by_path) + .filter(([key, value]) => value === null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); expect(logRows[0]).toEqual({ ...commonInterviewDataInRows, event_type: 'section_change', - timestampMs : String((1) * 1000), - event_date: new Date((1) * 1000).toISOString(), + timestampMs: String(1 * 1000), + event_date: new Date(1 * 1000).toISOString(), modifiedFields: modifiedKeys, initializedFields: initializedKeys, - unsetFields: sectionChangeLogs[0].unset_paths !== undefined ? sectionChangeLogs[0].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') : '', + unsetFields: + sectionChangeLogs[0].unset_paths !== undefined + ? sectionChangeLogs[0].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') + : '', widgetType: '', widgetPath: userAction.targetSection.sectionShortname, hiddenWidgets: '', @@ -670,16 +746,25 @@ describe('exportInterviewLogTask', () => { language: '' }); - const modifiedKeys2 = Object.entries(sectionChangeLogs[1].values_by_path).filter(([key, value]) => value !== null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - const initializedKeys2 = Object.entries(sectionChangeLogs[1].values_by_path).filter(([key, value]) => value === null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); + const modifiedKeys2 = Object.entries(sectionChangeLogs[1].values_by_path) + .filter(([key, value]) => value !== null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeys2 = Object.entries(sectionChangeLogs[1].values_by_path) + .filter(([key, value]) => value === null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); expect(logRows[1]).toEqual({ ...commonInterviewDataInRows, event_type: 'section_change', - timestampMs : String((2) * 1000), - event_date: new Date((2) * 1000).toISOString(), + timestampMs: String(2 * 1000), + event_date: new Date(2 * 1000).toISOString(), modifiedFields: modifiedKeys2, initializedFields: initializedKeys2, - unsetFields: sectionChangeLogs[1].unset_paths !== undefined ? sectionChangeLogs[1].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') : '', + unsetFields: + sectionChangeLogs[1].unset_paths !== undefined + ? sectionChangeLogs[1].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') + : '', widgetType: '', widgetPath: userAction.targetSection.sectionShortname + '/' + (userActionWithHidden.targetSection.iterationContext || []).join('/'), hiddenWidgets: userActionWithHidden.hiddenWidgets.join('|'), @@ -690,21 +775,27 @@ describe('exportInterviewLogTask', () => { platform: '', language: '' }); - }); test('Test with an event of type language_change with user action', async () => { // Add one log statement, with/without hidden paths to test the button_click event: const languageChange: UserAction = { type: 'languageChange', language: 'fr' }; - const languageLogs: { [key: string]: any }[] = [{ - ...commonInterviewData, - event_type: 'language_change', - timestamp_sec: 1, - event_date: new Date(1 * 1000), - values_by_path: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true, 'response.household.size': 3, 'response._activeTripId': null }, - unset_paths: [ 'response.home.someField', 'validations.home.someField' ], - user_action: languageChange - }]; + const languageLogs: { [key: string]: any }[] = [ + { + ...commonInterviewData, + event_type: 'language_change', + timestamp_sec: 1, + event_date: new Date(1 * 1000), + values_by_path: { + 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, + 'validations.home.geography': true, + 'response.household.size': 3, + 'response._activeTripId': null + }, + unset_paths: ['response.home.someField', 'validations.home.someField'], + user_action: languageChange + } + ]; // Add the logs to the stream mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(languageLogs) as any); @@ -727,16 +818,25 @@ describe('exportInterviewLogTask', () => { expect(logRows.length).toEqual(languageLogs.length); // Test the row values - const modifiedKeysLog1 = Object.entries(languageLogs[0].values_by_path).filter(([key, value]) => value !== null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - const initializedKeysLog1 = Object.entries(languageLogs[0].values_by_path).filter(([key, value]) => value === null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); + const modifiedKeysLog1 = Object.entries(languageLogs[0].values_by_path) + .filter(([key, value]) => value !== null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeysLog1 = Object.entries(languageLogs[0].values_by_path) + .filter(([key, value]) => value === null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); expect(logRows[0]).toEqual({ ...commonInterviewDataInRows, event_type: 'language_change', - timestampMs : String((1) * 1000), - event_date: new Date((1) * 1000).toISOString(), + timestampMs: String(1 * 1000), + event_date: new Date(1 * 1000).toISOString(), modifiedFields: modifiedKeysLog1, initializedFields: initializedKeysLog1, - unsetFields: languageLogs[0].unset_paths !== undefined ? languageLogs[0].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') : '', + unsetFields: + languageLogs[0].unset_paths !== undefined + ? languageLogs[0].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') + : '', widgetType: '', widgetPath: '', hiddenWidgets: '', @@ -752,15 +852,22 @@ describe('exportInterviewLogTask', () => { test('Test with an event of type interview_open with user action', async () => { // Add one log statement, with/without hidden paths to test the button_click event: const userAction: UserAction = { type: 'interviewOpen', language: 'en', browser: { name: 'firefox' } }; - const interviewOpenLogs: { [key: string]: any }[] = [{ - ...commonInterviewData, - event_type: 'interview_open', - timestamp_sec: 1, - event_date: new Date(1 * 1000), - values_by_path: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true, 'response.household.size': 3, 'response._activeTripId': null }, - unset_paths: [ 'response.home.someField', 'validations.home.someField' ], - user_action: userAction - }]; + const interviewOpenLogs: { [key: string]: any }[] = [ + { + ...commonInterviewData, + event_type: 'interview_open', + timestamp_sec: 1, + event_date: new Date(1 * 1000), + values_by_path: { + 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, + 'validations.home.geography': true, + 'response.household.size': 3, + 'response._activeTripId': null + }, + unset_paths: ['response.home.someField', 'validations.home.someField'], + user_action: userAction + } + ]; // Add the logs to the stream mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(interviewOpenLogs) as any); @@ -783,13 +890,19 @@ describe('exportInterviewLogTask', () => { expect(logRows.length).toEqual(interviewOpenLogs.length); // Test the row values - const modifiedKeysLog1 = Object.entries(interviewOpenLogs[0].values_by_path).filter(([key, value]) => value !== null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - const initializedKeysLog1 = Object.entries(interviewOpenLogs[0].values_by_path).filter(([key, value]) => value === null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); + const modifiedKeysLog1 = Object.entries(interviewOpenLogs[0].values_by_path) + .filter(([key, value]) => value !== null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeysLog1 = Object.entries(interviewOpenLogs[0].values_by_path) + .filter(([key, value]) => value === null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); expect(logRows[0]).toEqual({ ...commonInterviewDataInRows, event_type: 'interview_open', - timestampMs : String((1) * 1000), - event_date: new Date((1) * 1000).toISOString(), + timestampMs: String(1 * 1000), + event_date: new Date(1 * 1000).toISOString(), modifiedFields: modifiedKeysLog1, initializedFields: initializedKeysLog1, unsetFields: interviewOpenLogs[0].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|'), @@ -807,30 +920,39 @@ describe('exportInterviewLogTask', () => { test('Test with various events, with validations `true` and `false`', async () => { // Add some log statements with validations data: first statement has only true, second has one false, third has both true and false - const interviewLogs: { [key: string]: any }[] = [{ - ...commonInterviewData, - event_type: 'button_click', - timestamp_sec: 1, - event_date: new Date(1 * 1000), - values_by_path: { 'response.home.geography': { type: 'Point', coordinates: [ 1, 1 ] }, 'validations.home.geography': true, 'response.household.size': 3, 'response._activeTripId': null }, - unset_paths: [ 'response.home.someField', 'validations.home.someField' ], - user_action: { type: 'buttonClick', buttonId: 'response.someField' } - }, { - ...commonInterviewData, - event_type: 'widget_interaction', - timestamp_sec: 2, - event_date: new Date(2 * 1000), - values_by_path: { 'validations.home.someField': false }, - user_action: { type: 'widgetInteraction', path: 'response.home.someField', value: 'someValue', widgetType: 'radio' } - },{ - ...commonInterviewData, - event_type: 'button_click', - timestamp_sec: 3, - event_date: new Date(3 * 1000), - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], - user_action: { type: 'buttonClick', buttonId: 'response.someField' } - }]; + const interviewLogs: { [key: string]: any }[] = [ + { + ...commonInterviewData, + event_type: 'button_click', + timestamp_sec: 1, + event_date: new Date(1 * 1000), + values_by_path: { + 'response.home.geography': { type: 'Point', coordinates: [1, 1] }, + 'validations.home.geography': true, + 'response.household.size': 3, + 'response._activeTripId': null + }, + unset_paths: ['response.home.someField', 'validations.home.someField'], + user_action: { type: 'buttonClick', buttonId: 'response.someField' } + }, + { + ...commonInterviewData, + event_type: 'widget_interaction', + timestamp_sec: 2, + event_date: new Date(2 * 1000), + values_by_path: { 'validations.home.someField': false }, + user_action: { type: 'widgetInteraction', path: 'response.home.someField', value: 'someValue', widgetType: 'radio' } + }, + { + ...commonInterviewData, + event_type: 'button_click', + timestamp_sec: 3, + event_date: new Date(3 * 1000), + values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, + unset_paths: ['response.home.someField'], + user_action: { type: 'buttonClick', buttonId: 'response.someField' } + } + ]; // Add the logs to the stream mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(interviewLogs) as any); @@ -853,16 +975,25 @@ describe('exportInterviewLogTask', () => { expect(logRows.length).toEqual(interviewLogs.length); // Test the row values - const modifiedKeysLog1 = Object.entries(interviewLogs[0].values_by_path).filter(([key, value]) => value !== null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - const initializedKeysLog1 = Object.entries(interviewLogs[0].values_by_path).filter(([key, value]) => value === null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); + const modifiedKeysLog1 = Object.entries(interviewLogs[0].values_by_path) + .filter(([key, value]) => value !== null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeysLog1 = Object.entries(interviewLogs[0].values_by_path) + .filter(([key, value]) => value === null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); expect(logRows[0]).toEqual({ ...commonInterviewDataInRows, event_type: 'button_click', - timestampMs : String((1) * 1000), - event_date: new Date((1) * 1000).toISOString(), + timestampMs: String(1 * 1000), + event_date: new Date(1 * 1000).toISOString(), modifiedFields: modifiedKeysLog1, initializedFields: initializedKeysLog1, - unsetFields: interviewLogs[0].unset_paths !== undefined ? interviewLogs[0].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') : '', + unsetFields: + interviewLogs[0].unset_paths !== undefined + ? interviewLogs[0].unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') + : '', widgetType: '', widgetPath: interviewLogs[0].user_action.buttonId, hiddenWidgets: '', @@ -874,13 +1005,19 @@ describe('exportInterviewLogTask', () => { language: '' }); - const modifiedKeysLog2 = Object.entries(interviewLogs[1].values_by_path).filter(([key, value]) => value !== null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - const initializedKeysLog2 = Object.entries(interviewLogs[1].values_by_path).filter(([key, value]) => value === null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); + const modifiedKeysLog2 = Object.entries(interviewLogs[1].values_by_path) + .filter(([key, value]) => value !== null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeysLog2 = Object.entries(interviewLogs[1].values_by_path) + .filter(([key, value]) => value === null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); expect(logRows[1]).toEqual({ ...commonInterviewDataInRows, event_type: 'widget_interaction', - timestampMs : String((2) * 1000), - event_date: new Date((2) * 1000).toISOString(), + timestampMs: String(2 * 1000), + event_date: new Date(2 * 1000).toISOString(), modifiedFields: modifiedKeysLog2, initializedFields: initializedKeysLog2, unsetFields: interviewLogs[1].unset_paths !== undefined ? interviewLogs[1].unset_paths.join('|') : '', @@ -895,13 +1032,19 @@ describe('exportInterviewLogTask', () => { language: '' }); - const modifiedKeysLog3 = Object.entries(interviewLogs[2].values_by_path).filter(([key, value]) => value !== null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); - const initializedKeysLog3 = Object.entries(interviewLogs[2].values_by_path).filter(([key, value]) => value === null && !key.startsWith('validations.')).map(([key, value]) => key).join('|'); + const modifiedKeysLog3 = Object.entries(interviewLogs[2].values_by_path) + .filter(([key, value]) => value !== null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); + const initializedKeysLog3 = Object.entries(interviewLogs[2].values_by_path) + .filter(([key, value]) => value === null && !key.startsWith('validations.')) + .map(([key, value]) => key) + .join('|'); expect(logRows[2]).toEqual({ ...commonInterviewDataInRows, event_type: 'button_click', - timestampMs : String((3) * 1000), - event_date: new Date((3) * 1000).toISOString(), + timestampMs: String(3 * 1000), + event_date: new Date(3 * 1000).toISOString(), modifiedFields: modifiedKeysLog3, initializedFields: initializedKeysLog3, unsetFields: interviewLogs[2].unset_paths !== undefined ? interviewLogs[2].unset_paths.join('|') : '', @@ -919,16 +1062,18 @@ describe('exportInterviewLogTask', () => { test('Test a legacy event with no event data', async () => { // Add one log statement, with/without hidden paths to test the button_click event: - const legacyEmptyLog: { [key: string]: any }[] = [{ - ...commonInterviewData, - event_type: 'legacy', - timestamp_sec: 1, - event_date: new Date(1 * 1000), - values_by_path: null, - unset_paths: null, - user_action: null, - for_correction: null - }]; + const legacyEmptyLog: { [key: string]: any }[] = [ + { + ...commonInterviewData, + event_type: 'legacy', + timestamp_sec: 1, + event_date: new Date(1 * 1000), + values_by_path: null, + unset_paths: null, + user_action: null, + for_correction: null + } + ]; // Add the logs to the stream mockGetInterviewLogsStream.mockReturnValue(new ObjectReadableMock(legacyEmptyLog) as any); @@ -954,8 +1099,8 @@ describe('exportInterviewLogTask', () => { expect(logRows[0]).toEqual({ ...commonInterviewDataInRows, event_type: 'legacy', - timestampMs : String((1) * 1000), - event_date: new Date((1) * 1000).toISOString(), + timestampMs: String(1 * 1000), + event_date: new Date(1 * 1000).toISOString(), for_correction: '', modifiedFields: '', initializedFields: '', @@ -1005,9 +1150,7 @@ describe('exportInterviewLogTask', () => { .map(([key]) => key) .join('|') : '', - unsetFields: log.unset_paths - ? log.unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') - : '', + unsetFields: log.unset_paths ? log.unset_paths.filter((path: string) => !path.startsWith('validations.')).join('|') : '', widgetType: '', widgetPath: '', hiddenWidgets: '', @@ -1030,8 +1173,12 @@ describe('exportInterviewLogTask', () => { ...overrides }); - const platformData1 = { parsedResult: { browser: { name: 'Firefox', version: '146.0' }, os: { name: 'Linux' }, platform: { type: 'desktop'} } }; - const platformData2 = { parsedResult: { browser: { name: 'Safari', version: '146.0' }, os: { name: 'MacOs' }, platform: { type: 'mobile'} } } ; + const platformData1 = { + parsedResult: { browser: { name: 'Firefox', version: '146.0' }, os: { name: 'Linux' }, platform: { type: 'desktop' } } + }; + const platformData2 = { + parsedResult: { browser: { name: 'Safari', version: '146.0' }, os: { name: 'MacOs' }, platform: { type: 'mobile' } } + }; const contextCases = [ { @@ -1056,8 +1203,12 @@ describe('exportInterviewLogTask', () => { event_type: 'button_click', timestamp_sec: 3, event_date: new Date(3 * 1000), - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { + 'validations.home.geography': true, + 'validations.home.household.size': false, + 'validations.home.someField': false + }, + unset_paths: ['response.home.someField'], user_action: { type: 'buttonClick', buttonId: 'response.someField' } }), makeLog({ @@ -1072,8 +1223,12 @@ describe('exportInterviewLogTask', () => { event_type: 'button_click', timestamp_sec: 5, event_date: new Date(5 * 1000), - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { + 'validations.home.geography': true, + 'validations.home.household.size': false, + 'validations.home.someField': false + }, + unset_paths: ['response.home.someField'], user_action: { type: 'buttonClick', buttonId: 'response.someField' } }), makeLog({ @@ -1103,7 +1258,7 @@ describe('exportInterviewLogTask', () => { os: platformData1.parsedResult.os.name, platform: platformData1.parsedResult.platform.type, language: 'fr', - widgetPath: 'response.someField', + widgetPath: 'response.someField' }, { // Browser 2, language set to French @@ -1117,7 +1272,7 @@ describe('exportInterviewLogTask', () => { os: platformData2.parsedResult.os.name, platform: platformData2.parsedResult.platform.type, language: 'fr', - widgetPath: 'response.someField', + widgetPath: 'response.someField' }, { // Browser 2, language set to Spanish with legacy response data @@ -1127,7 +1282,8 @@ describe('exportInterviewLogTask', () => { language: 'es' } ] - }, { + }, + { description: 'multiple users intricated, with/without correction for browser context', logs: [ makeLog({ @@ -1152,8 +1308,12 @@ describe('exportInterviewLogTask', () => { event_date: new Date(3 * 1000), user_id: 1, for_correction: true, - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { + 'validations.home.geography': true, + 'validations.home.household.size': false, + 'validations.home.someField': false + }, + unset_paths: ['response.home.someField'], user_action: { type: 'buttonClick', buttonId: 'response.someField' } }), makeLog({ @@ -1163,8 +1323,12 @@ describe('exportInterviewLogTask', () => { event_date: new Date(4 * 1000), user_id: 1, for_correction: false, - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { + 'validations.home.geography': true, + 'validations.home.household.size': false, + 'validations.home.someField': false + }, + unset_paths: ['response.home.someField'], user_action: { type: 'buttonClick', buttonId: 'response.someField' } }), makeLog({ @@ -1172,8 +1336,12 @@ describe('exportInterviewLogTask', () => { event_type: 'button_click', timestamp_sec: 5, event_date: new Date(5 * 1000), - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { + 'validations.home.geography': true, + 'validations.home.household.size': false, + 'validations.home.someField': false + }, + unset_paths: ['response.home.someField'], user_action: { type: 'buttonClick', buttonId: 'response.someField' } }), makeLog({ @@ -1192,10 +1360,14 @@ describe('exportInterviewLogTask', () => { event_date: new Date(7 * 1000), user_id: 1, for_correction: false, - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { + 'validations.home.geography': true, + 'validations.home.household.size': false, + 'validations.home.someField': false + }, + unset_paths: ['response.home.someField'], user_action: { type: 'buttonClick', buttonId: 'response.someField' } - }), + }) ], expectedRowOverrides: [ { @@ -1209,14 +1381,7 @@ describe('exportInterviewLogTask', () => { platform: platformData2.parsedResult.platform.type, user_id: '1' }, - { - browser: '', - os: '', - platform: '', - widgetPath: 'response.someField', - user_id: '1', - for_correction: 'true' - }, + { browser: '', os: '', platform: '', widgetPath: 'response.someField', user_id: '1', for_correction: 'true' }, { browser: platformData2.parsedResult.browser.name, os: platformData2.parsedResult.os.name, @@ -1228,7 +1393,7 @@ describe('exportInterviewLogTask', () => { browser: platformData1.parsedResult.browser.name, os: platformData1.parsedResult.os.name, platform: platformData1.parsedResult.platform.type, - widgetPath: 'response.someField', + widgetPath: 'response.someField' }, { browser: platformData1.parsedResult.browser.name, @@ -1243,9 +1408,10 @@ describe('exportInterviewLogTask', () => { platform: platformData2.parsedResult.platform.type, widgetPath: 'response.someField', user_id: '1' - }, + } ] - }, { + }, + { description: 'multiple users intricated, with/without correction for language context', logs: [ makeLog({ @@ -1270,8 +1436,12 @@ describe('exportInterviewLogTask', () => { event_date: new Date(3 * 1000), user_id: 1, for_correction: true, - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { + 'validations.home.geography': true, + 'validations.home.household.size': false, + 'validations.home.someField': false + }, + unset_paths: ['response.home.someField'], user_action: { type: 'buttonClick', buttonId: 'response.someField' } }), makeLog({ @@ -1281,8 +1451,12 @@ describe('exportInterviewLogTask', () => { event_date: new Date(4 * 1000), user_id: 1, for_correction: false, - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { + 'validations.home.geography': true, + 'validations.home.household.size': false, + 'validations.home.someField': false + }, + unset_paths: ['response.home.someField'], user_action: { type: 'buttonClick', buttonId: 'response.someField' } }), makeLog({ @@ -1290,8 +1464,12 @@ describe('exportInterviewLogTask', () => { event_type: 'button_click', timestamp_sec: 5, event_date: new Date(5 * 1000), - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { + 'validations.home.geography': true, + 'validations.home.household.size': false, + 'validations.home.someField': false + }, + unset_paths: ['response.home.someField'], user_action: { type: 'buttonClick', buttonId: 'response.someField' } }), makeLog({ @@ -1310,44 +1488,23 @@ describe('exportInterviewLogTask', () => { event_date: new Date(7 * 1000), user_id: 1, for_correction: false, - values_by_path: { 'validations.home.geography': true, 'validations.home.household.size': false, 'validations.home.someField': false }, - unset_paths: [ 'response.home.someField' ], + values_by_path: { + 'validations.home.geography': true, + 'validations.home.household.size': false, + 'validations.home.someField': false + }, + unset_paths: ['response.home.someField'], user_action: { type: 'buttonClick', buttonId: 'response.someField' } - }), + }) ], expectedRowOverrides: [ - { - language: 'fr' - }, - { - language: 'es', - user_id: '1' - }, - { - language: '', - widgetPath: 'response.someField', - user_id: '1', - for_correction: 'true' - }, - { - language: 'es', - widgetPath: 'response.someField', - user_id: '1' - }, - { - language: 'fr', - widgetPath: 'response.someField', - }, - { - language: 'fr', - user_id: '1', - for_correction: 'true' - }, - { - language: 'es', - widgetPath: 'response.someField', - user_id: '1' - }, + { language: 'fr' }, + { language: 'es', user_id: '1' }, + { language: '', widgetPath: 'response.someField', user_id: '1', for_correction: 'true' }, + { language: 'es', widgetPath: 'response.someField', user_id: '1' }, + { language: 'fr', widgetPath: 'response.someField' }, + { language: 'fr', user_id: '1', for_correction: 'true' }, + { language: 'es', widgetPath: 'response.someField', user_id: '1' } ] } ]; @@ -1369,13 +1526,9 @@ describe('exportInterviewLogTask', () => { const logRows = await getCsvFileRows(csvStream.data); expect(logRows.length).toEqual(expectedRowOverrides.length); for (let i = 0; i < expectedRowOverrides.length; i++) { - const expectedRow = expectedRowFromLog( - logs[i], - expectedRowOverrides[i] - ); + const expectedRow = expectedRowFromLog(logs[i], expectedRowOverrides[i]); expect(logRows[i]).toEqual(expectedRow); } }); }); - }); diff --git a/packages/evolution-backend/src/services/audits/__tests__/AuditService.test.ts b/packages/evolution-backend/src/services/audits/__tests__/AuditService.test.ts index ce50c3ada..8a0131c17 100644 --- a/packages/evolution-backend/src/services/audits/__tests__/AuditService.test.ts +++ b/packages/evolution-backend/src/services/audits/__tests__/AuditService.test.ts @@ -20,29 +20,18 @@ describe('AuditService', () => { const mockHouseholdUuid = uuidV4(); const mockHomeUuid = uuidV4(); - const createMockInterview = (): InterviewAttributes => ({ - id: 123, - uuid: mockInterviewUuid, - participant_id: 456, - is_valid: false, - is_completed: false, - response: { - _uuid: mockInterviewUuid, - household: { - _uuid: mockHouseholdUuid, - size: 2 - } - }, - corrected_response: { - _uuid: mockInterviewUuid, - household: { - _uuid: mockHouseholdUuid, - size: 2 - } - }, - validations: {}, - logs: [] - } as InterviewAttributes); + const createMockInterview = (): InterviewAttributes => + ({ + id: 123, + uuid: mockInterviewUuid, + participant_id: 456, + is_valid: false, + is_completed: false, + response: { _uuid: mockInterviewUuid, household: { _uuid: mockHouseholdUuid, size: 2 } }, + corrected_response: { _uuid: mockInterviewUuid, household: { _uuid: mockHouseholdUuid, size: 2 } }, + validations: {}, + logs: [] + }) as InterviewAttributes; beforeEach(() => { jest.clearAllMocks(); @@ -69,17 +58,19 @@ describe('AuditService', () => { } }); - (SurveyObjectAuditor.auditSurveyObjects as jest.Mock) = jest.fn().mockResolvedValue([ - { - objectType: 'interview', - objectUuid: mockInterviewUuid, - errorCode: 'TEST_AUDIT', - version: 1, - level: 'warning', - message: 'Test audit', - ignore: false - } - ]); + (SurveyObjectAuditor.auditSurveyObjects as jest.Mock) = jest + .fn() + .mockResolvedValue([ + { + objectType: 'interview', + objectUuid: mockInterviewUuid, + errorCode: 'TEST_AUDIT', + version: 1, + level: 'warning', + message: 'Test audit', + ignore: false + } + ]); }); describe('auditInterview', () => { @@ -89,12 +80,7 @@ describe('AuditService', () => { const result = await AuditService.auditInterview(interview); - expect(result).toEqual({ - audits: [], - interview: undefined, - household: undefined, - home: undefined - }); + expect(result).toEqual({ audits: [], interview: undefined, household: undefined, home: undefined }); expect(SurveyObjectsFactory.prototype.createAllObjectsWithErrors).not.toHaveBeenCalled(); expect(SurveyObjectAuditor.auditSurveyObjects).not.toHaveBeenCalled(); }); @@ -105,12 +91,7 @@ describe('AuditService', () => { const result = await AuditService.auditInterview(interview); - expect(result).toEqual({ - audits: [], - interview: undefined, - household: undefined, - home: undefined - }); + expect(result).toEqual({ audits: [], interview: undefined, household: undefined, home: undefined }); expect(SurveyObjectsFactory.prototype.createAllObjectsWithErrors).not.toHaveBeenCalled(); expect(SurveyObjectAuditor.auditSurveyObjects).not.toHaveBeenCalled(); }); @@ -121,11 +102,7 @@ describe('AuditService', () => { await AuditService.auditInterview(interview); expect(SurveyObjectAuditor.auditSurveyObjects).toHaveBeenCalledWith( - expect.objectContaining({ - interview: expect.any(Object), - household: expect.any(Object), - home: expect.any(Object) - }), + expect.objectContaining({ interview: expect.any(Object), household: expect.any(Object), home: expect.any(Object) }), false ); }); @@ -136,11 +113,7 @@ describe('AuditService', () => { await AuditService.auditInterview(interview, true); expect(SurveyObjectAuditor.auditSurveyObjects).toHaveBeenCalledWith( - expect.objectContaining({ - interview: expect.any(Object), - household: expect.any(Object), - home: expect.any(Object) - }), + expect.objectContaining({ interview: expect.any(Object), household: expect.any(Object), home: expect.any(Object) }), true ); }); @@ -151,11 +124,7 @@ describe('AuditService', () => { await AuditService.auditInterview(interview, false); expect(SurveyObjectAuditor.auditSurveyObjects).toHaveBeenCalledWith( - expect.objectContaining({ - interview: expect.any(Object), - household: expect.any(Object), - home: expect.any(Object) - }), + expect.objectContaining({ interview: expect.any(Object), household: expect.any(Object), home: expect.any(Object) }), false ); }); @@ -183,12 +152,7 @@ describe('AuditService', () => { interviewUuid: mockInterviewUuid, householdUuid: mockHouseholdUuid, homeUuid: mockHomeUuid, - interview: [ - { - path: 'test.path', - message: 'Parameter error' - } - ], + interview: [{ path: 'test.path', message: 'Parameter error' }], household: [], home: [], personsByUuid: {}, @@ -208,4 +172,3 @@ describe('AuditService', () => { }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/__tests__/AuditUtils.test.ts b/packages/evolution-backend/src/services/audits/__tests__/AuditUtils.test.ts index 4900c832a..ae5cbe2d4 100644 --- a/packages/evolution-backend/src/services/audits/__tests__/AuditUtils.test.ts +++ b/packages/evolution-backend/src/services/audits/__tests__/AuditUtils.test.ts @@ -42,44 +42,14 @@ describe('convertParamsErrorsToAudits', () => { describe('mergeWithExisting', () => { it('should merge new audits with existing audits', async () => { const existingAudits: Audits = { - 'test-error': { - version: 1, - errorCode: 'test-error', - message: 'Test error message', - level: 'error', - ignore: true, - }, - 'outdated-error': { - version: 1, - errorCode: 'oudated-error', - message: 'Outdated error message', - level: 'error', - ignore: true, - }, - 'version-changed-error': { - version: 1, - errorCode: 'oudated-error', - message: 'Outdated error message', - level: 'error', - ignore: true, - }, + 'test-error': { version: 1, errorCode: 'test-error', message: 'Test error message', level: 'error', ignore: true }, + 'outdated-error': { version: 1, errorCode: 'oudated-error', message: 'Outdated error message', level: 'error', ignore: true }, + 'version-changed-error': { version: 1, errorCode: 'oudated-error', message: 'Outdated error message', level: 'error', ignore: true } }; const newAudits: Audits = { - 'test-error': { - version: 1, - errorCode: 'test-error', - message: 'Test error message updated', - level: 'warning', - ignore: false, - }, - 'version-changed-error': { - version: 2, - errorCode: 'oudated-error', - message: 'Outdated error message', - level: 'error', - ignore: false, - }, + 'test-error': { version: 1, errorCode: 'test-error', message: 'Test error message updated', level: 'warning', ignore: false }, + 'version-changed-error': { version: 2, errorCode: 'oudated-error', message: 'Outdated error message', level: 'error', ignore: false } }; const mergedAudits = mergeWithExisting(existingAudits, newAudits); @@ -135,10 +105,7 @@ describe('fieldIsRequired', () => { const { fieldIsRequired } = await import('../AuditUtils'); // Set config for this test - projectConfig.requiredFieldsBySurveyObject = { - ...projectConfig.requiredFieldsBySurveyObject, - [objectType]: isRequired ? [field] : [] - }; + projectConfig.requiredFieldsBySurveyObject = { ...projectConfig.requiredFieldsBySurveyObject, [objectType]: isRequired ? [field] : [] }; const result = fieldIsRequired(objectType, field); @@ -189,9 +156,7 @@ describe('fieldIsRequired', () => { const { fieldIsRequired } = await import('../AuditUtils'); // Remove key entirely for this test - projectConfig.requiredFieldsBySurveyObject = { - ...projectConfig.requiredFieldsBySurveyObject, - }; + projectConfig.requiredFieldsBySurveyObject = { ...projectConfig.requiredFieldsBySurveyObject }; delete (projectConfig.requiredFieldsBySurveyObject as any).organization; expect(fieldIsRequired('organization', 'name')).toBe(false); diff --git a/packages/evolution-backend/src/services/audits/__tests__/Auditor.test.ts b/packages/evolution-backend/src/services/audits/__tests__/Auditor.test.ts index a40ee7e52..797cc6656 100644 --- a/packages/evolution-backend/src/services/audits/__tests__/Auditor.test.ts +++ b/packages/evolution-backend/src/services/audits/__tests__/Auditor.test.ts @@ -8,14 +8,9 @@ import { v4 as uuidV4 } from 'uuid'; import Auditor from '../Auditor'; -type ArbitraryObject = { - a: number; - b: string; - c: boolean; -}; +type ArbitraryObject = { a: number; b: string; c: boolean }; describe('auditObject', () => { - const validations = { 'error-code-ok': (_value: ArbitraryObject) => undefined, 'error-code-with-minimal-info': (_value: ArbitraryObject) => ({ version: 2 }), @@ -27,24 +22,17 @@ describe('auditObject', () => { const arbitraryUuid = uuidV4(); const results = Auditor.auditObject({ a: 2, b: 'test', c: true }, validations, { objectType: 'test', objectUuid: arbitraryUuid }); - expect(results).toEqual([{ - errorCode: 'error-code-with-minimal-info', - objectUuid: arbitraryUuid, - objectType: 'test', - version: 2, - }, { - errorCode: 'error-code-with-more-info', - objectUuid: arbitraryUuid, - objectType: 'test', - message: 'SomeMessageKey', - version: 3, - level: 'warning' - }, { - errorCode: 'error-code-without-version', - objectUuid: arbitraryUuid, - objectType: 'test', - message: 'OtherMessageKey', - version: 1 - }]); + expect(results).toEqual([ + { errorCode: 'error-code-with-minimal-info', objectUuid: arbitraryUuid, objectType: 'test', version: 2 }, + { + errorCode: 'error-code-with-more-info', + objectUuid: arbitraryUuid, + objectType: 'test', + message: 'SomeMessageKey', + version: 3, + level: 'warning' + }, + { errorCode: 'error-code-without-version', objectUuid: arbitraryUuid, objectType: 'test', message: 'OtherMessageKey', version: 1 } + ]); }); }); diff --git a/packages/evolution-backend/src/services/audits/__tests__/Audits.test.ts b/packages/evolution-backend/src/services/audits/__tests__/Audits.test.ts index ea0ff96dd..7a45b08ac 100644 --- a/packages/evolution-backend/src/services/audits/__tests__/Audits.test.ts +++ b/packages/evolution-backend/src/services/audits/__tests__/Audits.test.ts @@ -13,15 +13,8 @@ import type { Interview } from 'evolution-common/lib/services/baseObjects/interv import type { Household } from 'evolution-common/lib/services/baseObjects/Household'; import type { Home } from 'evolution-common/lib/services/baseObjects/Home'; -jest.mock('../../../models/audits.db.queries', () => ({ - setAuditsForInterview: jest.fn(), - updateAudit: jest.fn() -})); -jest.mock('../AuditService', () => ({ - AuditService: { - auditInterview: jest.fn() - } -})); +jest.mock('../../../models/audits.db.queries', () => ({ setAuditsForInterview: jest.fn(), updateAudit: jest.fn() })); +jest.mock('../AuditService', () => ({ AuditService: { auditInterview: jest.fn() } })); const mockSetAudits = auditsDbQueries.setAuditsForInterview as jest.MockedFunction; mockSetAudits.mockImplementation(async (_interviewId, audits) => audits); @@ -40,7 +33,6 @@ const makeServiceResult = (audits: AuditForObject[]) => ({ const interviewId = 3; describe('updateAudits', () => { - beforeEach(() => { mockUpdateAudit.mockClear(); }); @@ -51,34 +43,18 @@ describe('updateAudits', () => { }); test('update single audit', async () => { - const audits = [{ - version: 3, - objectType: 'person', - objectUuid: 'arbitrary', - errorCode: 'err-code' - }]; + const audits = [{ version: 3, objectType: 'person', objectUuid: 'arbitrary', errorCode: 'err-code' }]; await SurveyObjectsAndAuditsFactory.updateAudits(interviewId, audits); expect(mockUpdateAudit).toHaveBeenCalledTimes(audits.length); expect(mockUpdateAudit).toHaveBeenCalledWith(interviewId, audits[0]); }); test('update many audits', async () => { - const audits = [{ - version: 3, - objectType: 'person', - objectUuid: 'arbitrary', - errorCode: 'err-code' - }, { - version: 3, - objectType: 'interview', - objectUuid: 'arbitrary', - errorCode: 'err-code' - }, { - version: 3, - objectType: 'person', - objectUuid: 'arbitrary2', - errorCode: 'err-code' - }]; + const audits = [ + { version: 3, objectType: 'person', objectUuid: 'arbitrary', errorCode: 'err-code' }, + { version: 3, objectType: 'interview', objectUuid: 'arbitrary', errorCode: 'err-code' }, + { version: 3, objectType: 'person', objectUuid: 'arbitrary2', errorCode: 'err-code' } + ]; await SurveyObjectsAndAuditsFactory.updateAudits(interviewId, audits); expect(mockUpdateAudit).toHaveBeenCalledTimes(audits.length); expect(mockUpdateAudit).toHaveBeenCalledWith(interviewId, audits[0]); @@ -88,7 +64,6 @@ describe('updateAudits', () => { }); describe('createSurveyObjectsAndSaveAuditsToDb', () => { - const interviewAttributes = { id: interviewId, uuid: '123e4567-e89b-12d3-a456-426614174000', @@ -130,8 +105,9 @@ describe('createSurveyObjectsAndSaveAuditsToDb', () => { }); test('corrected_response not set in interview', async () => { - await expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb(_omit(interviewAttributes, 'corrected_response'))) - .rejects.toThrow('Corrected response is required to create survey objects and audits'); + await expect( + SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb(_omit(interviewAttributes, 'corrected_response')) + ).rejects.toThrow('Corrected response is required to create survey objects and audits'); expect(mockAuditInterview).not.toHaveBeenCalled(); }); @@ -198,4 +174,3 @@ describe('createSurveyObjectsAndSaveAuditsToDb', () => { expect(mockSetAudits).toHaveBeenCalledWith(interviewId, []); }); }); - diff --git a/packages/evolution-backend/src/services/audits/__tests__/BatchAuditService.test.ts b/packages/evolution-backend/src/services/audits/__tests__/BatchAuditService.test.ts index abbd86978..7eec16286 100644 --- a/packages/evolution-backend/src/services/audits/__tests__/BatchAuditService.test.ts +++ b/packages/evolution-backend/src/services/audits/__tests__/BatchAuditService.test.ts @@ -26,80 +26,50 @@ describe('BatchAuditService', () => { const mockInterviewUuid2 = uuidV4(); const mockInterviewUuid3 = uuidV4(); - const createMockInterview = (uuid: string): InterviewAttributes => ({ - id: 123, - uuid, - participant_id: 456, - is_valid: false, - is_completed: false, - response: { - _uuid: uuid, - household: { - _uuid: uuidV4(), - size: 2 - } - }, - corrected_response: { - _uuid: uuid, - household: { - _uuid: uuidV4(), - size: 2 - } - }, - validations: {}, - logs: [] - } as InterviewAttributes); - - const createMockInterviewListItem = (uuid: string): InterviewListAttributes => ({ - uuid, - id: 123, - participant_id: 456, - is_valid: false, - is_completed: false, - created_at: new Date().toISOString(), - updated_at: new Date().toISOString() - } as InterviewListAttributes); + const createMockInterview = (uuid: string): InterviewAttributes => + ({ + id: 123, + uuid, + participant_id: 456, + is_valid: false, + is_completed: false, + response: { _uuid: uuid, household: { _uuid: uuidV4(), size: 2 } }, + corrected_response: { _uuid: uuid, household: { _uuid: uuidV4(), size: 2 } }, + validations: {}, + logs: [] + }) as InterviewAttributes; + + const createMockInterviewListItem = (uuid: string): InterviewListAttributes => + ({ + uuid, + id: 123, + participant_id: 456, + is_valid: false, + is_completed: false, + created_at: new Date().toISOString(), + updated_at: new Date().toISOString() + }) as InterviewListAttributes; beforeEach(() => { jest.clearAllMocks(); // Setup default mock implementations (copyResponseToCorrectedResponse as jest.Mock).mockResolvedValue(undefined); - (SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb as jest.Mock).mockResolvedValue( - undefined - ); + (SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb as jest.Mock).mockResolvedValue(undefined); // Reset getAllMatching mock to avoid interference between tests (Interviews.getAllMatching as jest.Mock).mockReset(); }); describe('runBatchAuditsTask', () => { it('should return empty results when no interviews match filters', async () => { - (Interviews.getAllMatching as jest.Mock).mockResolvedValue({ - interviews: [], - totalCount: 0 - }); + (Interviews.getAllMatching as jest.Mock).mockResolvedValue({ interviews: [], totalCount: 0 }); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; const result = await runBatchAuditsTask(params); - expect(result).toEqual({ - totalCount: 0, - processed: 0, - succeeded: 0, - failed: 0, - results: [] - }); - expect(Interviews.getAllMatching).toHaveBeenCalledWith({ - pageIndex: 0, - pageSize: BATCH_SIZE, - filter: {}, - updatedAt: 0, - sort: undefined - }); + expect(result).toEqual({ totalCount: 0, processed: 0, succeeded: 0, failed: 0, results: [] }); + expect(Interviews.getAllMatching).toHaveBeenCalledWith({ pageIndex: 0, pageSize: BATCH_SIZE, filter: {}, updatedAt: 0, sort: undefined }); }); it('should process single interview successfully', async () => { @@ -107,20 +77,11 @@ describe('BatchAuditService', () => { const interviewListItem1 = createMockInterviewListItem(mockInterviewUuid1); (Interviews.getAllMatching as jest.Mock) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }); + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }) + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }); (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValue(interview1); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; const result = await runBatchAuditsTask(params); @@ -129,15 +90,9 @@ describe('BatchAuditService', () => { expect(result.succeeded).toBe(1); expect(result.failed).toBe(0); expect(result.results).toHaveLength(1); - expect(result.results[0]).toEqual({ - uuid: mockInterviewUuid1, - status: 'success' - }); + expect(result.results[0]).toEqual({ uuid: mockInterviewUuid1, status: 'success' }); expect(Interviews.getInterviewByUuid).toHaveBeenCalledWith(mockInterviewUuid1); - expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb).toHaveBeenCalledWith( - interview1, - false - ); + expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb).toHaveBeenCalledWith(interview1, false); }); it('should copy response to corrected_response when missing', async () => { @@ -147,20 +102,11 @@ describe('BatchAuditService', () => { const interviewListItem1 = createMockInterviewListItem(mockInterviewUuid1); (Interviews.getAllMatching as jest.Mock) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }); + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }) + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }); (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValue(interview1); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; await runBatchAuditsTask(params); @@ -172,20 +118,11 @@ describe('BatchAuditService', () => { const interviewListItem1 = createMockInterviewListItem(mockInterviewUuid1); (Interviews.getAllMatching as jest.Mock) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }); + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }) + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }); (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValue(interview1); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; await runBatchAuditsTask(params); @@ -210,10 +147,7 @@ describe('BatchAuditService', () => { .mockResolvedValueOnce(interview2) .mockResolvedValueOnce(interview3); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; const result = await runBatchAuditsTask(params); @@ -229,16 +163,10 @@ describe('BatchAuditService', () => { const interviewListItem1 = createMockInterviewListItem(mockInterviewUuid1); // First call gets first page (to get totalCount) - (Interviews.getAllMatching as jest.Mock).mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }); + (Interviews.getAllMatching as jest.Mock).mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }); (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValue(undefined); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; const result = await runBatchAuditsTask(params); @@ -246,11 +174,7 @@ describe('BatchAuditService', () => { expect(result.processed).toBe(1); expect(result.succeeded).toBe(0); expect(result.failed).toBe(1); - expect(result.results[0]).toEqual({ - uuid: mockInterviewUuid1, - status: 'failed', - error: 'Interview not found' - }); + expect(result.results[0]).toEqual({ uuid: mockInterviewUuid1, status: 'failed', error: 'Interview not found' }); }); it('should handle audit processing errors', async () => { @@ -258,23 +182,12 @@ describe('BatchAuditService', () => { const interviewListItem1 = createMockInterviewListItem(mockInterviewUuid1); (Interviews.getAllMatching as jest.Mock) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }); + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }) + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }); (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValue(interview1); - (SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb as jest.Mock).mockRejectedValue( - new Error('Audit failed') - ); + (SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb as jest.Mock).mockRejectedValue(new Error('Audit failed')); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; const result = await runBatchAuditsTask(params); @@ -282,11 +195,7 @@ describe('BatchAuditService', () => { expect(result.processed).toBe(1); expect(result.succeeded).toBe(0); expect(result.failed).toBe(1); - expect(result.results[0]).toEqual({ - uuid: mockInterviewUuid1, - status: 'failed', - error: 'Error processing interview' - }); + expect(result.results[0]).toEqual({ uuid: mockInterviewUuid1, status: 'failed', error: 'Error processing interview' }); }); it('should handle mixed success and failure', async () => { @@ -295,18 +204,10 @@ describe('BatchAuditService', () => { const interviewListItem2 = createMockInterviewListItem(mockInterviewUuid2); // First call gets first page (to get totalCount), both interviews fit in one page - (Interviews.getAllMatching as jest.Mock).mockResolvedValueOnce({ - interviews: [interviewListItem1, interviewListItem2], - totalCount: 2 - }); - (Interviews.getInterviewByUuid as jest.Mock) - .mockResolvedValueOnce(interview1) - .mockResolvedValueOnce(undefined); // Second interview not found + (Interviews.getAllMatching as jest.Mock).mockResolvedValueOnce({ interviews: [interviewListItem1, interviewListItem2], totalCount: 2 }); + (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValueOnce(interview1).mockResolvedValueOnce(undefined); // Second interview not found - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; const result = await runBatchAuditsTask(params); @@ -320,16 +221,10 @@ describe('BatchAuditService', () => { }); it('should parse string filters correctly', async () => { - (Interviews.getAllMatching as jest.Mock).mockResolvedValue({ - interviews: [], - totalCount: 0 - }); + (Interviews.getAllMatching as jest.Mock).mockResolvedValue({ interviews: [], totalCount: 0 }); const params: BatchAuditTaskParams = { - filters: { - 'response.accessCode': 'ABC123', - 'response.household.size': ['1', '2'] - }, + filters: { 'response.accessCode': 'ABC123', 'response.household.size': ['1', '2'] }, extended: false }; @@ -338,42 +233,23 @@ describe('BatchAuditService', () => { expect(Interviews.getAllMatching).toHaveBeenCalledWith({ pageIndex: 0, pageSize: BATCH_SIZE, - filter: { - 'response.accessCode': 'ABC123', - 'response.household.size': ['1', '2'] - }, + filter: { 'response.accessCode': 'ABC123', 'response.household.size': ['1', '2'] }, updatedAt: 0, sort: undefined }); }); it('should parse object filters correctly', async () => { - (Interviews.getAllMatching as jest.Mock).mockResolvedValue({ - interviews: [], - totalCount: 0 - }); + (Interviews.getAllMatching as jest.Mock).mockResolvedValue({ interviews: [], totalCount: 0 }); - const params: BatchAuditTaskParams = { - filters: { - 'response.household.size': { - value: 2, - op: 'eq' - } - }, - extended: false - }; + const params: BatchAuditTaskParams = { filters: { 'response.household.size': { value: 2, op: 'eq' } }, extended: false }; await runBatchAuditsTask(params); expect(Interviews.getAllMatching).toHaveBeenCalledWith({ pageIndex: 0, pageSize: BATCH_SIZE, - filter: { - 'response.household.size': { - value: 2, - op: 'eq' - } - }, + filter: { 'response.household.size': { value: 2, op: 'eq' } }, updatedAt: 0, sort: undefined }); @@ -390,9 +266,7 @@ describe('BatchAuditService', () => { extended: false }; - await expect(runBatchAuditsTask(params)).rejects.toThrow( - 'Invalid filter structure for keys: response.household.size' - ); + await expect(runBatchAuditsTask(params)).rejects.toThrow('Invalid filter structure for keys: response.household.size'); }); it('should throw error for invalid filter value type', async () => { @@ -406,9 +280,7 @@ describe('BatchAuditService', () => { extended: false }; - await expect(runBatchAuditsTask(params)).rejects.toThrow( - 'Invalid filter structure for keys: response.household.size' - ); + await expect(runBatchAuditsTask(params)).rejects.toThrow('Invalid filter structure for keys: response.household.size'); }); it('should throw error for invalid operator', async () => { @@ -422,17 +294,13 @@ describe('BatchAuditService', () => { extended: false }; - await expect(runBatchAuditsTask(params)).rejects.toThrow( - 'Invalid filter structure for keys: response.household.size' - ); + await expect(runBatchAuditsTask(params)).rejects.toThrow('Invalid filter structure for keys: response.household.size'); }); it('should throw error for multiple invalid filters', async () => { const params: BatchAuditTaskParams = { filters: { - 'response.household.size': { - value: { invalid: 'type' } - }, + 'response.household.size': { value: { invalid: 'type' } }, 'response.accessCode': { op: 'eq' // Missing value @@ -448,10 +316,7 @@ describe('BatchAuditService', () => { }); it('should accept valid GeoJSON Polygon feature as filter value', async () => { - (Interviews.getAllMatching as jest.Mock).mockResolvedValue({ - interviews: [], - totalCount: 0 - }); + (Interviews.getAllMatching as jest.Mock).mockResolvedValue({ interviews: [], totalCount: 0 }); const validPolygonFeature = { type: 'Feature', @@ -471,12 +336,7 @@ describe('BatchAuditService', () => { }; const params: BatchAuditTaskParams = { - filters: { - 'response.home.geography': { - value: validPolygonFeature, - op: 'eq' - } - }, + filters: { 'response.home.geography': { value: validPolygonFeature, op: 'eq' } }, extended: false }; @@ -485,58 +345,25 @@ describe('BatchAuditService', () => { expect(Interviews.getAllMatching).toHaveBeenCalledWith({ pageIndex: 0, pageSize: BATCH_SIZE, - filter: { - 'response.home.geography': { - value: validPolygonFeature, - op: 'eq' - } - }, + filter: { 'response.home.geography': { value: validPolygonFeature, op: 'eq' } }, updatedAt: 0, sort: undefined }); }); it('should reject invalid GeoJSON feature (not a Polygon)', async () => { - const invalidFeature = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0] - }, - properties: {} - }; + const invalidFeature = { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }; - const params: BatchAuditTaskParams = { - filters: { - 'response.home.geography': { - value: invalidFeature, - op: 'eq' - } - }, - extended: false - }; + const params: BatchAuditTaskParams = { filters: { 'response.home.geography': { value: invalidFeature, op: 'eq' } }, extended: false }; - await expect(runBatchAuditsTask(params)).rejects.toThrow( - 'Invalid filter structure for keys: response.home.geography' - ); + await expect(runBatchAuditsTask(params)).rejects.toThrow('Invalid filter structure for keys: response.home.geography'); }); it('should accept all valid operator types', async () => { - (Interviews.getAllMatching as jest.Mock).mockResolvedValue({ - interviews: [], - totalCount: 0 - }); + (Interviews.getAllMatching as jest.Mock).mockResolvedValue({ interviews: [], totalCount: 0 }); for (const op of VALID_OPERATORS) { - const params: BatchAuditTaskParams = { - filters: { - [`test.${op}`]: { - value: 2, - op: op as keyof OperatorSigns - } - }, - extended: false - }; + const params: BatchAuditTaskParams = { filters: { [`test.${op}`]: { value: 2, op: op as keyof OperatorSigns } }, extended: false }; await runBatchAuditsTask(params); } @@ -550,27 +377,15 @@ describe('BatchAuditService', () => { const interviewListItem1 = createMockInterviewListItem(mockInterviewUuid1); (Interviews.getAllMatching as jest.Mock) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }); + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }) + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }); (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValue(interview1); - const params: BatchAuditTaskParams = { - filters: {}, - extended: true - }; + const params: BatchAuditTaskParams = { filters: {}, extended: true }; await runBatchAuditsTask(params); - expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb).toHaveBeenCalledWith( - interview1, - true - ); + expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb).toHaveBeenCalledWith(interview1, true); }); it('should use extended audit checks when extended="true"', async () => { @@ -578,27 +393,15 @@ describe('BatchAuditService', () => { const interviewListItem1 = createMockInterviewListItem(mockInterviewUuid1); (Interviews.getAllMatching as jest.Mock) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }); + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }) + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }); (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValue(interview1); - const params: BatchAuditTaskParams = { - filters: {}, - extended: 'true' - }; + const params: BatchAuditTaskParams = { filters: {}, extended: 'true' }; await runBatchAuditsTask(params); - expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb).toHaveBeenCalledWith( - interview1, - true - ); + expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb).toHaveBeenCalledWith(interview1, true); }); it('should not use extended audit checks when extended=false', async () => { @@ -606,27 +409,15 @@ describe('BatchAuditService', () => { const interviewListItem1 = createMockInterviewListItem(mockInterviewUuid1); (Interviews.getAllMatching as jest.Mock) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }); + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }) + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }); (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValue(interview1); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; await runBatchAuditsTask(params); - expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb).toHaveBeenCalledWith( - interview1, - false - ); + expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb).toHaveBeenCalledWith(interview1, false); }); it('should log userId when provided', async () => { @@ -635,27 +426,15 @@ describe('BatchAuditService', () => { const interviewListItem1 = createMockInterviewListItem(mockInterviewUuid1); (Interviews.getAllMatching as jest.Mock) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }) - .mockResolvedValueOnce({ - interviews: [interviewListItem1], - totalCount: 1 - }); + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }) + .mockResolvedValueOnce({ interviews: [interviewListItem1], totalCount: 1 }); (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValue(interview1); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false, - userId: 123 - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false, userId: 123 }; await runBatchAuditsTask(params); - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining('userId=123') - ); + expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('userId=123')); consoleSpy.mockRestore(); }); @@ -676,10 +455,7 @@ describe('BatchAuditService', () => { totalCount: 150 // Exceeds limit of 100 }); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; await expect(reloadedTask(params)).rejects.toThrow( 'Batch audit request exceeds maximum limit of 100 interviews. Found 150 matching interviews. Please refine your filters.' @@ -714,10 +490,7 @@ describe('BatchAuditService', () => { .mockResolvedValueOnce({ interviews: [], totalCount: 50 }); // pageIndex 4 (Interviews.getInterviewByUuid as jest.Mock).mockResolvedValue(interview1); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; const result = await runBatchAuditsTask(params); @@ -737,15 +510,9 @@ describe('BatchAuditService', () => { // Re-import Interviews after module reset const InterviewsModule = await import('../../interviews/interviews'); - (InterviewsModule.default.getAllMatching as jest.Mock).mockResolvedValue({ - interviews: [], - totalCount: 0 - }); + (InterviewsModule.default.getAllMatching as jest.Mock).mockResolvedValue({ interviews: [], totalCount: 0 }); - const params: BatchAuditTaskParams = { - filters: {}, - extended: false - }; + const params: BatchAuditTaskParams = { filters: {}, extended: false }; await reloadedTask(params); @@ -772,25 +539,14 @@ describe('BatchAuditService', () => { processed: 1, succeeded: 1, failed: 0, - results: [ - { - uuid: mockInterviewUuid1, - status: 'success' as const - } - ] + results: [{ uuid: mockInterviewUuid1, status: 'success' as const }] }; (execJob as jest.Mock).mockResolvedValue(mockResult); const result = await BatchAuditService.runBatchAudits({}, false, 123); - expect(execJob).toHaveBeenCalledWith('runBatchAudits', [ - { - filters: {}, - extended: false, - userId: 123 - } - ]); + expect(execJob).toHaveBeenCalledWith('runBatchAudits', [{ filters: {}, extended: false, userId: 123 }]); expect(isOk(result)).toBe(true); if (isOk(result)) { expect(result.result).toEqual(mockResult); @@ -823,31 +579,15 @@ describe('BatchAuditService', () => { }); it('should pass filters and extended flag correctly', async () => { - const mockResult = { - totalCount: 0, - processed: 0, - succeeded: 0, - failed: 0, - results: [] - }; + const mockResult = { totalCount: 0, processed: 0, succeeded: 0, failed: 0, results: [] }; (execJob as jest.Mock).mockResolvedValue(mockResult); - const filters = { - 'response.accessCode': 'ABC123', - 'response.household.size': { value: 2, op: 'eq' } - }; + const filters = { 'response.accessCode': 'ABC123', 'response.household.size': { value: 2, op: 'eq' } }; await BatchAuditService.runBatchAudits(filters, true, 456); - expect(execJob).toHaveBeenCalledWith('runBatchAudits', [ - { - filters, - extended: true, - userId: 456 - } - ]); + expect(execJob).toHaveBeenCalledWith('runBatchAudits', [{ filters, extended: true, userId: 456 }]); }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectAuditor.test.ts b/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectAuditor.test.ts index e2b1f3008..4a5be1271 100644 --- a/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectAuditor.test.ts +++ b/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectAuditor.test.ts @@ -14,12 +14,7 @@ import { Person } from 'evolution-common/lib/services/baseObjects/Person'; import { Journey } from 'evolution-common/lib/services/baseObjects/Journey'; import { VisitedPlace } from 'evolution-common/lib/services/baseObjects/VisitedPlace'; import { Trip } from 'evolution-common/lib/services/baseObjects/Trip'; -import { - runInterviewAuditChecks, - runHouseholdAuditChecks, - runHomeAuditChecks, - runVisitedPlaceAuditChecks -} from '../auditChecks'; +import { runInterviewAuditChecks, runHouseholdAuditChecks, runHomeAuditChecks, runVisitedPlaceAuditChecks } from '../auditChecks'; import { Interview } from 'evolution-common/lib/services/baseObjects/interview/Interview'; // Mock the audit functions @@ -29,52 +24,60 @@ jest.mock('../auditChecks', () => { const mockExtendedCheck = jest.fn(); return { - runInterviewAuditChecks: jest.fn().mockResolvedValue([ - { - objectType: 'interview', - objectUuid: 'interview-uuid', - errorCode: 'I_L_InterviewAudited', - level: 'info', - message: 'Interview audited', - version: 1, - ignore: false - } - ]), - runHouseholdAuditChecks: jest.fn().mockResolvedValue([ - { - objectType: 'household', - objectUuid: 'household-uuid', - errorCode: 'HH_I_Size', - level: 'error', - message: 'Invalid household size', - version: 1, - ignore: false - } - ]), - runHomeAuditChecks: jest.fn().mockResolvedValue([ - { - objectType: 'home', - objectUuid: 'home-uuid', - errorCode: 'HM_I_Address', - level: 'warning', - message: 'Invalid home address', - version: 1, - ignore: false - } - ]), + runInterviewAuditChecks: jest + .fn() + .mockResolvedValue([ + { + objectType: 'interview', + objectUuid: 'interview-uuid', + errorCode: 'I_L_InterviewAudited', + level: 'info', + message: 'Interview audited', + version: 1, + ignore: false + } + ]), + runHouseholdAuditChecks: jest + .fn() + .mockResolvedValue([ + { + objectType: 'household', + objectUuid: 'household-uuid', + errorCode: 'HH_I_Size', + level: 'error', + message: 'Invalid household size', + version: 1, + ignore: false + } + ]), + runHomeAuditChecks: jest + .fn() + .mockResolvedValue([ + { + objectType: 'home', + objectUuid: 'home-uuid', + errorCode: 'HM_I_Address', + level: 'warning', + message: 'Invalid home address', + version: 1, + ignore: false + } + ]), runPersonAuditChecks: jest.fn().mockResolvedValue([]), runJourneyAuditChecks: jest.fn().mockResolvedValue([]), - runVisitedPlaceAuditChecks: jest.fn().mockResolvedValue([ - { - objectType: 'visitedPlace', - objectUuid: 'visitedplace-uuid', - errorCode: 'VP_M_Geography', - level: 'warning', - message: 'Missing geography', - version: 1, - ignore: false - } - ]), + runVisitedPlaceAuditChecks: jest + .fn() + .mockResolvedValue([ + { + objectType: 'visitedPlace', + objectUuid: 'visitedplace-uuid', + errorCode: 'VP_M_Geography', + level: 'warning', + message: 'Missing geography', + version: 1, + ignore: false + } + ]), runTripAuditChecks: jest.fn().mockResolvedValue([]), runSegmentAuditChecks: jest.fn().mockResolvedValue([]), interviewAuditChecks: { normalCheck: mockNormalCheck }, @@ -96,7 +99,6 @@ jest.mock('../auditChecks', () => { }; }); - describe('SurveyObjectAuditor', () => { const interviewUuid = uuidV4(); const householdUuid = uuidV4(); @@ -116,32 +118,15 @@ describe('SurveyObjectAuditor', () => { geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.5, 45.5] } } } as VisitedPlace; - const mockTrip = { - _uuid: tripUuid, - mode: 'transit', - segments: [] - } as unknown as Trip; + const mockTrip = { _uuid: tripUuid, mode: 'transit', segments: [] } as unknown as Trip; - const mockJourney = { - _uuid: journeyUuid, - visitedPlaces: [mockVisitedPlace], - trips: [mockTrip] - } as Journey; + const mockJourney = { _uuid: journeyUuid, visitedPlaces: [mockVisitedPlace], trips: [mockTrip] } as Journey; - const mockPerson = { - _uuid: personUuid, - age: 30, - journeys: [mockJourney] - } as Person; + const mockPerson = { _uuid: personUuid, age: 30, journeys: [mockJourney] } as Person; mockHousehold.members = [mockPerson]; - return { - interview: mockInterview as unknown as Interview, - home: mockHome, - household: mockHousehold, - audits: [] - }; + return { interview: mockInterview as unknown as Interview, home: mockHome, household: mockHousehold, audits: [] }; }; beforeEach(() => { @@ -154,12 +139,7 @@ describe('SurveyObjectAuditor', () => { const audits = await SurveyObjectAuditor.auditSurveyObjects(surveyObjects); - expect(runInterviewAuditChecks).toHaveBeenCalledWith( - { - interview: surveyObjects.interview - }, - expect.any(Object) - ); + expect(runInterviewAuditChecks).toHaveBeenCalledWith({ interview: surveyObjects.interview }, expect.any(Object)); expect(audits).toContainEqual({ objectType: 'interview', @@ -178,10 +158,7 @@ describe('SurveyObjectAuditor', () => { const audits = await SurveyObjectAuditor.auditSurveyObjects(surveyObjects); expect(runHouseholdAuditChecks).toHaveBeenCalledWith( - { - household: surveyObjects.household, - interview: surveyObjects.interview - }, + { household: surveyObjects.household, interview: surveyObjects.interview }, expect.any(Object) ); @@ -202,11 +179,7 @@ describe('SurveyObjectAuditor', () => { const audits = await SurveyObjectAuditor.auditSurveyObjects(surveyObjects); expect(runHomeAuditChecks).toHaveBeenCalledWith( - { - home: surveyObjects.home, - household: surveyObjects.household, - interview: surveyObjects.interview - }, + { home: surveyObjects.home, household: surveyObjects.household, interview: surveyObjects.interview }, expect.any(Object) ); @@ -255,16 +228,7 @@ describe('SurveyObjectAuditor', () => { interview: undefined, household: undefined, home: undefined, - auditsByObject: { - interview: [], - household: [], - home: [], - persons: {}, - journeys: {}, - visitedPlaces: {}, - trips: {}, - segments: {} - } + auditsByObject: { interview: [], household: [], home: [], persons: {}, journeys: {}, visitedPlaces: {}, trips: {}, segments: {} } }; const audits = await SurveyObjectAuditor.auditSurveyObjects(surveyObjects); diff --git a/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectParsers.test.ts b/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectParsers.test.ts index 146f08274..9c7d793c9 100644 --- a/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectParsers.test.ts +++ b/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectParsers.test.ts @@ -46,19 +46,10 @@ describe('SurveyObjectParsers Integration', () => { it('should call interview parser when configured in SurveyObjectsFactory', async () => { const mockInterviewParser = jest.fn().mockReturnValue({ _language: 'en' }); - setProjectConfig({ - surveyObjectParsers: { - interview: mockInterviewParser - } - }); + setProjectConfig({ surveyObjectParsers: { interview: mockInterviewParser } }); const factory = new SurveyObjectsFactory(); - const interviewAttributes: InterviewAttributes = { - uuid: uuidV4(), - corrected_response: { - _language: 'en' - } - } as any; + const interviewAttributes: InterviewAttributes = { uuid: uuidV4(), corrected_response: { _language: 'en' } } as any; await factory.createAllObjectsWithErrors(interviewAttributes); @@ -67,58 +58,31 @@ describe('SurveyObjectParsers Integration', () => { }); it('should call home parser when configured in SurveyObjectsFactory', async () => { - const mockHomeParser = jest.fn().mockReturnValue({ - _uuid: uuidV4(), - address: '123 Test St' - }); - - setProjectConfig({ - surveyObjectParsers: { - home: mockHomeParser - } - }); + const mockHomeParser = jest.fn().mockReturnValue({ _uuid: uuidV4(), address: '123 Test St' }); + + setProjectConfig({ surveyObjectParsers: { home: mockHomeParser } }); const factory = new SurveyObjectsFactory(); const interviewAttributes: InterviewAttributes = { uuid: uuidV4(), - corrected_response: { - home: { - _uuid: uuidV4(), - address: '123 Test St' - } - } + corrected_response: { home: { _uuid: uuidV4(), address: '123 Test St' } } } as any; await factory.createAllObjectsWithErrors(interviewAttributes); - expect(mockHomeParser).toHaveBeenCalledWith( - interviewAttributes.corrected_response!.home, - interviewAttributes.corrected_response - ); + expect(mockHomeParser).toHaveBeenCalledWith(interviewAttributes.corrected_response!.home, interviewAttributes.corrected_response); expect(mockHomeParser).toHaveBeenCalledTimes(1); }); it('should call household parser when configured in SurveyObjectsFactory', async () => { - const mockHouseholdParser = jest.fn().mockReturnValue({ - _uuid: uuidV4(), - size: 2 - }); - - setProjectConfig({ - surveyObjectParsers: { - household: mockHouseholdParser - } - }); + const mockHouseholdParser = jest.fn().mockReturnValue({ _uuid: uuidV4(), size: 2 }); + + setProjectConfig({ surveyObjectParsers: { household: mockHouseholdParser } }); const factory = new SurveyObjectsFactory(); const interviewAttributes: InterviewAttributes = { uuid: uuidV4(), - corrected_response: { - household: { - _uuid: uuidV4(), - size: 2 - } - } + corrected_response: { household: { _uuid: uuidV4(), size: 2 } } } as any; await factory.createAllObjectsWithErrors(interviewAttributes); @@ -131,17 +95,9 @@ describe('SurveyObjectParsers Integration', () => { }); it('should call person parser when configured in PersonFactory', async () => { - const mockPersonParser = jest.fn().mockReturnValue({ - _uuid: 'person-uuid', - _sequence: 1, - age: 30 - }); - - setProjectConfig({ - surveyObjectParsers: { - person: mockPersonParser - } - }); + const mockPersonParser = jest.fn().mockReturnValue({ _uuid: 'person-uuid', _sequence: 1, age: 30 }); + + setProjectConfig({ surveyObjectParsers: { person: mockPersonParser } }); const personUuid = 'person-uuid'; const householdUuid = uuidV4(); @@ -172,42 +128,19 @@ describe('SurveyObjectParsers Integration', () => { }; const correctedResponse: CorrectedResponse = { - household: { - persons: { - [personUuid]: { - _uuid: personUuid, - _sequence: 1, - age: 30 - } - } - } + household: { persons: { [personUuid]: { _uuid: personUuid, _sequence: 1, age: 30 } } } } as any; - await populatePersonsForHousehold( - surveyObjectsWithErrors, - surveyObjectsWithErrors.household!, - correctedResponse, - surveyObjectsRegistry - ); + await populatePersonsForHousehold(surveyObjectsWithErrors, surveyObjectsWithErrors.household!, correctedResponse, surveyObjectsRegistry); - expect(mockPersonParser).toHaveBeenCalledWith( - correctedResponse.household!.persons![personUuid], - correctedResponse - ); + expect(mockPersonParser).toHaveBeenCalledWith(correctedResponse.household!.persons![personUuid], correctedResponse); expect(mockPersonParser).toHaveBeenCalledTimes(1); }); it('should call journey parser when configured in JourneyFactory', async () => { - const mockJourneyParser = jest.fn().mockReturnValue({ - _uuid: 'journey-uuid', - _sequence: 1 - }); - - setProjectConfig({ - surveyObjectParsers: { - journey: mockJourneyParser - } - }); + const mockJourneyParser = jest.fn().mockReturnValue({ _uuid: 'journey-uuid', _sequence: 1 }); + + setProjectConfig({ surveyObjectParsers: { journey: mockJourneyParser } }); const surveyObjectsWithErrors: SurveyObjectsWithErrors = { interview: undefined, @@ -230,18 +163,9 @@ describe('SurveyObjectParsers Integration', () => { const person = Person.create({ _uuid: 'person-uuid', age: 30 }, surveyObjectsRegistry); if ('result' in person) { - const personAttributes: ExtendedPersonAttributes = { - journeys: { - 'journey-uuid': { - _uuid: 'journey-uuid', - _sequence: 1 - } - } - } as any; + const personAttributes: ExtendedPersonAttributes = { journeys: { 'journey-uuid': { _uuid: 'journey-uuid', _sequence: 1 } } } as any; - const correctedResponse: CorrectedResponse = { - _language: 'en' - } as any; + const correctedResponse: CorrectedResponse = { _language: 'en' } as any; await populateJourneysForPerson( surveyObjectsWithErrors, @@ -252,25 +176,15 @@ describe('SurveyObjectParsers Integration', () => { surveyObjectsRegistry ); - expect(mockJourneyParser).toHaveBeenCalledWith( - personAttributes.journeys!['journey-uuid'], - correctedResponse - ); + expect(mockJourneyParser).toHaveBeenCalledWith(personAttributes.journeys!['journey-uuid'], correctedResponse); expect(mockJourneyParser).toHaveBeenCalledTimes(1); } }); it('should call trip parser when configured in TripFactory', async () => { - const mockTripParser = jest.fn().mockReturnValue({ - _uuid: 'trip-uuid', - _sequence: 1 - }); - - setProjectConfig({ - surveyObjectParsers: { - trip: mockTripParser - } - }); + const mockTripParser = jest.fn().mockReturnValue({ _uuid: 'trip-uuid', _sequence: 1 }); + + setProjectConfig({ surveyObjectParsers: { trip: mockTripParser } }); const surveyObjectsWithErrors: SurveyObjectsWithErrors = { interview: undefined, @@ -295,18 +209,9 @@ describe('SurveyObjectParsers Integration', () => { const journey = Journey.create({ _uuid: 'journey-uuid' }, surveyObjectsRegistry); if ('result' in person && 'result' in journey) { - const journeyAttributes: ExtendedJourneyAttributes = { - trips: { - 'trip-uuid': { - _uuid: 'trip-uuid', - _sequence: 1 - } - } - } as any; + const journeyAttributes: ExtendedJourneyAttributes = { trips: { 'trip-uuid': { _uuid: 'trip-uuid', _sequence: 1 } } } as any; - const correctedResponse: CorrectedResponse = { - _language: 'en' - } as any; + const correctedResponse: CorrectedResponse = { _language: 'en' } as any; await populateTripsForJourney( surveyObjectsWithErrors, @@ -317,26 +222,15 @@ describe('SurveyObjectParsers Integration', () => { surveyObjectsRegistry ); - expect(mockTripParser).toHaveBeenCalledWith( - journeyAttributes.trips!['trip-uuid'], - correctedResponse - ); + expect(mockTripParser).toHaveBeenCalledWith(journeyAttributes.trips!['trip-uuid'], correctedResponse); expect(mockTripParser).toHaveBeenCalledTimes(1); } }); it('should call segment parser when configured in SegmentFactory', async () => { - const mockSegmentParser = jest.fn().mockReturnValue({ - _uuid: 'segment-uuid', - _sequence: 1, - mode: 'walk' - }); - - setProjectConfig({ - surveyObjectParsers: { - segment: mockSegmentParser - } - }); + const mockSegmentParser = jest.fn().mockReturnValue({ _uuid: 'segment-uuid', _sequence: 1, mode: 'walk' }); + + setProjectConfig({ surveyObjectParsers: { segment: mockSegmentParser } }); const surveyObjectsWithErrors: SurveyObjectsWithErrors = { interview: undefined, @@ -361,47 +255,22 @@ describe('SurveyObjectParsers Integration', () => { if ('result' in trip) { const tripAttributes: ExtendedTripAttributes = { - segments: { - 'segment-uuid': { - _uuid: 'segment-uuid', - _sequence: 1, - mode: 'walk' - } - } + segments: { 'segment-uuid': { _uuid: 'segment-uuid', _sequence: 1, mode: 'walk' } } } as any; - const correctedResponse: CorrectedResponse = { - _language: 'en' - } as any; + const correctedResponse: CorrectedResponse = { _language: 'en' } as any; - await populateSegmentsForTrip( - surveyObjectsWithErrors, - trip.result, - tripAttributes, - correctedResponse, - surveyObjectsRegistry - ); + await populateSegmentsForTrip(surveyObjectsWithErrors, trip.result, tripAttributes, correctedResponse, surveyObjectsRegistry); - expect(mockSegmentParser).toHaveBeenCalledWith( - tripAttributes.segments!['segment-uuid'], - correctedResponse - ); + expect(mockSegmentParser).toHaveBeenCalledWith(tripAttributes.segments!['segment-uuid'], correctedResponse); expect(mockSegmentParser).toHaveBeenCalledTimes(1); } }); it('should call visitedPlace parser when configured in VisitedPlaceFactory', async () => { - const mockVisitedPlaceParser = jest.fn().mockReturnValue({ - _uuid: 'place-uuid', - _sequence: 1, - activity: 'home' - }); - - setProjectConfig({ - surveyObjectParsers: { - visitedPlace: mockVisitedPlaceParser - } - }); + const mockVisitedPlaceParser = jest.fn().mockReturnValue({ _uuid: 'place-uuid', _sequence: 1, activity: 'home' }); + + setProjectConfig({ surveyObjectParsers: { visitedPlace: mockVisitedPlaceParser } }); const surveyObjectsWithErrors: SurveyObjectsWithErrors = { interview: undefined, @@ -427,18 +296,10 @@ describe('SurveyObjectParsers Integration', () => { if ('result' in person && 'result' in journey) { const journeyAttributes: ExtendedJourneyAttributes = { - visitedPlaces: { - 'place-uuid': { - _uuid: 'place-uuid', - _sequence: 1, - activity: 'home' - } - } + visitedPlaces: { 'place-uuid': { _uuid: 'place-uuid', _sequence: 1, activity: 'home' } } } as any; - const correctedResponse: CorrectedResponse = { - _language: 'en' - } as any; + const correctedResponse: CorrectedResponse = { _language: 'en' } as any; await populateVisitedPlacesForJourney( surveyObjectsWithErrors, @@ -450,34 +311,19 @@ describe('SurveyObjectParsers Integration', () => { surveyObjectsRegistry ); - expect(mockVisitedPlaceParser).toHaveBeenCalledWith( - journeyAttributes.visitedPlaces!['place-uuid'], - correctedResponse - ); + expect(mockVisitedPlaceParser).toHaveBeenCalledWith(journeyAttributes.visitedPlaces!['place-uuid'], correctedResponse); expect(mockVisitedPlaceParser).toHaveBeenCalledTimes(1); } }); it('should not call parsers when not configured', async () => { // Set empty parser configuration - setProjectConfig({ - surveyObjectParsers: {} - }); + setProjectConfig({ surveyObjectParsers: {} }); const factory = new SurveyObjectsFactory(); const interviewAttributes: InterviewAttributes = { uuid: uuidV4(), - corrected_response: { - _language: 'en', - home: { - _uuid: uuidV4(), - address: '123 Test St' - }, - household: { - _uuid: uuidV4(), - size: 2 - } - } + corrected_response: { _language: 'en', home: { _uuid: uuidV4(), address: '123 Test St' }, household: { _uuid: uuidV4(), size: 2 } } } as any; // This should not throw errors even without parsers configured @@ -487,17 +333,10 @@ describe('SurveyObjectParsers Integration', () => { it('should handle undefined parser configuration gracefully', async () => { // Set no parser configuration - setProjectConfig({ - surveyObjectParsers: undefined - }); + setProjectConfig({ surveyObjectParsers: undefined }); const factory = new SurveyObjectsFactory(); - const interviewAttributes: InterviewAttributes = { - uuid: uuidV4(), - corrected_response: { - _language: 'en' - } - } as any; + const interviewAttributes: InterviewAttributes = { uuid: uuidV4(), corrected_response: { _language: 'en' } } as any; // This should not throw errors even without parsers configured const result = await factory.createAllObjectsWithErrors(interviewAttributes); diff --git a/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectsAndAuditsFactory.test.ts b/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectsAndAuditsFactory.test.ts index 405f80d1a..336065c68 100644 --- a/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectsAndAuditsFactory.test.ts +++ b/packages/evolution-backend/src/services/audits/__tests__/SurveyObjectsAndAuditsFactory.test.ts @@ -19,29 +19,18 @@ describe('SurveyObjectsAndAuditsFactory', () => { const mockInterviewUuid = uuidV4(); const mockHouseholdUuid = uuidV4(); - const createMockInterview = (): InterviewAttributes => ({ - id: 123, - uuid: mockInterviewUuid, - participant_id: 456, - is_valid: false, - is_completed: false, - response: { - _uuid: mockInterviewUuid, - household: { - _uuid: mockHouseholdUuid, - size: 2 - } - }, - corrected_response: { - _uuid: mockInterviewUuid, - household: { - _uuid: mockHouseholdUuid, - size: 2 - } - }, - validations: {}, - logs: [] - } as InterviewAttributes); + const createMockInterview = (): InterviewAttributes => + ({ + id: 123, + uuid: mockInterviewUuid, + participant_id: 456, + is_valid: false, + is_completed: false, + response: { _uuid: mockInterviewUuid, household: { _uuid: mockHouseholdUuid, size: 2 } }, + corrected_response: { _uuid: mockInterviewUuid, household: { _uuid: mockHouseholdUuid, size: 2 } }, + validations: {}, + logs: [] + }) as InterviewAttributes; beforeEach(() => { jest.clearAllMocks(); @@ -62,16 +51,7 @@ describe('SurveyObjectsAndAuditsFactory', () => { ignore: false } ], - auditsByObject: { - interview: [], - household: [], - home: [], - persons: {}, - journeys: {}, - visitedPlaces: {}, - trips: {}, - segments: {} - } + auditsByObject: { interview: [], household: [], home: [], persons: {}, journeys: {}, visitedPlaces: {}, trips: {}, segments: {} } }); (auditsDbQueries.setAuditsForInterview as jest.Mock).mockResolvedValue([ @@ -92,9 +72,9 @@ describe('SurveyObjectsAndAuditsFactory', () => { const interview = createMockInterview(); interview.corrected_response = undefined as any; - await expect( - SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb(interview) - ).rejects.toThrow('Corrected response is required to create survey objects and audits'); + await expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb(interview)).rejects.toThrow( + 'Corrected response is required to create survey objects and audits' + ); expect(AuditService.auditInterview).not.toHaveBeenCalled(); }); @@ -131,11 +111,7 @@ describe('SurveyObjectsAndAuditsFactory', () => { expect(AuditService.auditInterview).toHaveBeenCalledWith(interview, false); expect(auditsDbQueries.setAuditsForInterview).toHaveBeenCalledWith( interview.id, - expect.arrayContaining([ - expect.objectContaining({ - errorCode: 'TEST_AUDIT' - }) - ]) + expect.arrayContaining([expect.objectContaining({ errorCode: 'TEST_AUDIT' })]) ); expect(result.audits).toHaveLength(1); }); @@ -150,4 +126,3 @@ describe('SurveyObjectsAndAuditsFactory', () => { }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/__tests__/AuditCheckUtils.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/__tests__/AuditCheckUtils.test.ts index 5331efe36..82be29cda 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/__tests__/AuditCheckUtils.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/__tests__/AuditCheckUtils.test.ts @@ -6,20 +6,10 @@ */ // Mock the project config directly to avoid loading external files and environment variables -jest.mock('evolution-common/lib/config/project.config', () => ({ - __esModule: true, - default: { - surveyAreaGeojsonPath: undefined - } -})); +jest.mock('evolution-common/lib/config/project.config', () => ({ __esModule: true, default: { surveyAreaGeojsonPath: undefined } })); // Mock the file manager directly to avoid dependency on chaire-lib-backend config side effects -jest.mock('chaire-lib-backend/lib/utils/filesystem/fileManager', () => ({ - fileManager: { - fileExists: jest.fn(), - readFile: jest.fn() - } -})); +jest.mock('chaire-lib-backend/lib/utils/filesystem/fileManager', () => ({ fileManager: { fileExists: jest.fn(), readFile: jest.fn() } })); describe('AuditCheckUtils', () => { test('should return undefined if surveyAreaGeojsonPath is not set', async () => { @@ -45,16 +35,9 @@ describe('AuditCheckUtils', () => { const mockFileManager = fileManager as jest.Mocked; (projectConfig as any).surveyAreaGeojsonPath = testPath; - const mockFeature = { - type: 'Feature', - geometry: { type: 'Polygon', coordinates: [] }, - properties: { name: 'Test' } - }; + const mockFeature = { type: 'Feature', geometry: { type: 'Polygon', coordinates: [] }, properties: { name: 'Test' } }; mockFileManager.fileExists.mockReturnValue(true); - mockFileManager.readFile.mockReturnValue(JSON.stringify({ - type: 'FeatureCollection', - features: [mockFeature] - })); + mockFileManager.readFile.mockReturnValue(JSON.stringify({ type: 'FeatureCollection', features: [mockFeature] })); const result = getSurveyArea(); expect(result).toEqual(mockFeature); @@ -101,14 +84,12 @@ describe('AuditCheckUtils', () => { (projectConfig as any).surveyAreaGeojsonPath = testPath; mockFileManager.fileExists.mockReturnValue(true); - mockFileManager.readFile.mockReturnValue(JSON.stringify({ - type: 'FeatureCollection', - features: [{ - type: 'Feature', - geometry: { type: 'Polygon', coordinates: [] }, - properties: {} - }] - })); + mockFileManager.readFile.mockReturnValue( + JSON.stringify({ + type: 'FeatureCollection', + features: [{ type: 'Feature', geometry: { type: 'Polygon', coordinates: [] }, properties: {} }] + }) + ); const result1 = getSurveyArea(); const result2 = getSurveyArea(); diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/AuditLocales.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/AuditLocales.test.ts index 5b6caecb7..8591dffc1 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/AuditLocales.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/AuditLocales.test.ts @@ -67,7 +67,6 @@ describe('Audit Locales', () => { expect(translation.trim()).not.toBe(''); // Translation should not be empty }); }); - }); describe('Orphaned audit translations', () => { @@ -84,14 +83,14 @@ describe('Audit Locales', () => { // Define the mapping between prefixes and audit check modules const auditCheckModules: { [prefix: string]: Record } = { - 'I_': interviewAuditChecks, - 'HM_': homeAuditChecks, - 'HH_': householdAuditChecks, - 'J_': journeyAuditChecks, - 'P_': personAuditChecks, - 'S_': segmentAuditChecks, - 'T_': tripAuditChecks, - 'VP_': visitedPlaceAuditChecks + I_: interviewAuditChecks, + HM_: homeAuditChecks, + HH_: householdAuditChecks, + J_: journeyAuditChecks, + P_: personAuditChecks, + S_: segmentAuditChecks, + T_: tripAuditChecks, + VP_: visitedPlaceAuditChecks }; describe.each(languages)('Language: %s', (language) => { @@ -138,9 +137,7 @@ describe('Audit Locales', () => { Object.assign(auditCheckModules, originalModules); getTranslationKeysFromFile = originalGetKeys; } - }); }); }); - }); diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/HouseholdAuditChecks.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/HouseholdAuditChecks.test.ts index c5e1c3f33..5cabe398a 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/HouseholdAuditChecks.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/HouseholdAuditChecks.test.ts @@ -61,7 +61,6 @@ describe('runHouseholdAuditChecks - Integration', () => { expect(audits.some((a) => a.errorCode === 'TEST_FAIL_1')).toBe(true); expect(audits.some((a) => a.errorCode === 'TEST_FAIL_2')).toBe(true); expect(audits.some((a) => a.errorCode === 'TEST_PASS')).toBe(false); - }); it('should handle empty audit checks object', async () => { diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/TripAuditChecks.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/TripAuditChecks.test.ts index 67cad3a65..a8c3e4e7b 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/TripAuditChecks.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/TripAuditChecks.test.ts @@ -14,7 +14,6 @@ import { createContextWithTrip } from './trip/testHelper'; describe('runTripAuditChecks - Integration', () => { const validUuid = uuidV4(); - it('should run all audit checks and return empty array when all checks pass', async () => { const context = createContextWithTrip(); diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/VisitedPlaceAuditChecks.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/VisitedPlaceAuditChecks.test.ts index 904fb7f17..c92bd2f10 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/VisitedPlaceAuditChecks.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/VisitedPlaceAuditChecks.test.ts @@ -134,9 +134,7 @@ describe('runVisitedPlaceAuditChecks - Integration', () => { } }; - await expect(runVisitedPlaceAuditChecks(context, mockAuditChecks)).rejects.toThrow( - 'Check function threw an error' - ); + await expect(runVisitedPlaceAuditChecks(context, mockAuditChecks)).rejects.toThrow('Check function threw an error'); }); it('should propagate errors when an async check rejects', async () => { diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_Geography.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_Geography.test.ts index 94e128bdf..06240f2ba 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_Geography.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_Geography.test.ts @@ -21,16 +21,19 @@ describe('HM_I_Geography audit check', () => { }); it('should error when geography has invalid coordinates', () => { - const context = createContextWithHome({ - geography: { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: [-73.5] // Missing latitude + const context = createContextWithHome( + { + geography: { + type: 'Feature', + properties: {}, + geometry: { + type: 'Point', + coordinates: [-73.5] // Missing latitude + } } - } - }, validUuid); + }, + validUuid + ); const result = homeAuditChecks.HM_I_Geography(context); @@ -46,16 +49,10 @@ describe('HM_I_Geography audit check', () => { }); it('should error when geography has no coordinates', () => { - const context = createContextWithHome({ - geography: { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: [] as number[] - } - } - }, validUuid); + const context = createContextWithHome( + { geography: { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [] as number[] } } }, + validUuid + ); const result = homeAuditChecks.HM_I_Geography(context); @@ -78,4 +75,3 @@ describe('HM_I_Geography audit check', () => { expect(result).toBeUndefined(); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_GeographyNotInSurveyTerritory.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_GeographyNotInSurveyTerritory.test.ts index 3e1dbf549..7919a0b60 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_GeographyNotInSurveyTerritory.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_GeographyNotInSurveyTerritory.test.ts @@ -10,9 +10,7 @@ import { createContextWithHome } from './testHelper'; import * as auditCheckUtils from '../../../AuditCheckUtils'; // Mock the AuditCheckUtils to provide a controlled survey area -jest.mock('../../../AuditCheckUtils', () => ({ - getSurveyArea: jest.fn() -})); +jest.mock('../../../AuditCheckUtils', () => ({ getSurveyArea: jest.fn() })); const mockGetSurveyArea = auditCheckUtils.getSurveyArea as jest.Mock; @@ -42,14 +40,7 @@ describe('HM_I_geographyNotInSurveyTerritory audit check', () => { it('should pass when home geography is inside the survey area', () => { mockGetSurveyArea.mockReturnValue(surveyArea); const context = createContextWithHome({ - geography: { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: [-73.5, 45.5] - } - } + geography: { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [-73.5, 45.5] } } }); const result = homeAuditChecks.HM_I_geographyNotInSurveyTerritory(context); @@ -59,16 +50,19 @@ describe('HM_I_geographyNotInSurveyTerritory audit check', () => { it('should fail when home geography is outside the survey area', () => { mockGetSurveyArea.mockReturnValue(surveyArea); - const context = createContextWithHome({ - geography: { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: [-75.0, 45.5] // Outside + const context = createContextWithHome( + { + geography: { + type: 'Feature', + properties: {}, + geometry: { + type: 'Point', + coordinates: [-75.0, 45.5] // Outside + } } - } - }, validUuid); + }, + validUuid + ); const result = homeAuditChecks.HM_I_geographyNotInSurveyTerritory(context); @@ -86,14 +80,7 @@ describe('HM_I_geographyNotInSurveyTerritory audit check', () => { it('should pass when no survey area is available', () => { mockGetSurveyArea.mockReturnValue(undefined); const context = createContextWithHome({ - geography: { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: [-75.0, 45.5] - } - } + geography: { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [-75.0, 45.5] } } }); const result = homeAuditChecks.HM_I_geographyNotInSurveyTerritory(context); diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartError.integration.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartError.integration.test.ts index 1023b6972..8fec50e78 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartError.integration.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartError.integration.test.ts @@ -23,7 +23,7 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartError audit check - Integr geography: { type: 'Feature' as const, properties: {}, - geometry: { type: 'Point' as const, coordinates: [-71.2080, 46.8139] } // Quebec City + geometry: { type: 'Point' as const, coordinates: [-71.208, 46.8139] } // Quebec City } }, validUuid @@ -43,16 +43,8 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartError audit check - Integr it('should pass when coordinates are close together (~7.8 meters)', () => { const context = createContextWithHome( { - preGeography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5673, 45.5017] } - }, - geography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5672, 45.5017] } - } + preGeography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5673, 45.5017] } }, + geography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5672, 45.5017] } } }, validUuid ); @@ -62,4 +54,3 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartError audit check - Integr expect(result).toBeUndefined(); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartError.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartError.test.ts index 481526d7a..d24abeaea 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartError.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartError.test.ts @@ -9,9 +9,7 @@ import { v4 as uuidV4 } from 'uuid'; import { homeAuditChecks, MAX_DISTANCE_PRE_AND_GEOGRAPHY_ERROR } from '../../HomeAuditChecks'; import { createContextWithHome } from './testHelper'; -jest.mock('@turf/distance', () => ({ - distance: jest.fn() -})); +jest.mock('@turf/distance', () => ({ distance: jest.fn() })); import { distance as turfDistance } from '@turf/distance'; @@ -27,34 +25,16 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartError audit check', () => { name: 'preGeography is missing', preGeography: undefined, - geography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - } + geography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } } }, { name: 'geography is missing', - preGeography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - }, + preGeography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } }, geography: undefined }, - { - name: 'both geographies are missing', - preGeography: undefined, - geography: undefined - } + { name: 'both geographies are missing', preGeography: undefined, geography: undefined } ])('$name', ({ preGeography, geography }) => { - const context = createContextWithHome( - { - preGeography, - geography - }, - validUuid - ); + const context = createContextWithHome({ preGeography, geography }, validUuid); const result = homeAuditChecks.HM_I_preGeographyAndHomeGeographyTooFarApartError(context); @@ -73,16 +53,8 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartError audit check', () => const context = createContextWithHome( { - preGeography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - }, - geography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - } + preGeography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } }, + geography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } } }, validUuid ); @@ -108,16 +80,8 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartError audit check', () => const context = createContextWithHome( { - preGeography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - }, - geography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - } + preGeography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } }, + geography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } } }, validUuid ); diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartWarning.integration.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartWarning.integration.test.ts index 8a04ea1ed..6513675ec 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartWarning.integration.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartWarning.integration.test.ts @@ -15,16 +15,8 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartWarning audit check - Inte it('should warn when coordinates are moderately far apart (~93.5 meters)', () => { const context = createContextWithHome( { - preGeography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5673, 45.5017] } - }, - geography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5661, 45.5017] } - } + preGeography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5673, 45.5017] } }, + geography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5661, 45.5017] } } }, validUuid ); @@ -43,16 +35,8 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartWarning audit check - Inte it('should pass when coordinates are very close together (~7.8 meters)', () => { const context = createContextWithHome( { - preGeography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5673, 45.5017] } - }, - geography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5672, 45.5017] } - } + preGeography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5673, 45.5017] } }, + geography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5672, 45.5017] } } }, validUuid ); @@ -73,7 +57,7 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartWarning audit check - Inte geography: { type: 'Feature' as const, properties: {}, - geometry: { type: 'Point' as const, coordinates: [-71.2080, 46.8139] } // Quebec City + geometry: { type: 'Point' as const, coordinates: [-71.208, 46.8139] } // Quebec City } }, validUuid @@ -84,4 +68,3 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartWarning audit check - Inte expect(result).toBeUndefined(); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartWarning.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartWarning.test.ts index b04685a74..5954ee3ee 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartWarning.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_I_preGeographyAndHomeGeographyTooFarApartWarning.test.ts @@ -9,9 +9,7 @@ import { v4 as uuidV4 } from 'uuid'; import { homeAuditChecks, MAX_DISTANCE_PRE_AND_GEOGRAPHY_ERROR, MIN_DISTANCE_PRE_AND_GEOGRAPHY_WARNING } from '../../HomeAuditChecks'; import { createContextWithHome } from './testHelper'; -jest.mock('@turf/distance', () => ({ - distance: jest.fn() -})); +jest.mock('@turf/distance', () => ({ distance: jest.fn() })); import { distance as turfDistance } from '@turf/distance'; @@ -27,34 +25,16 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartWarning audit check', () = { name: 'preGeography is missing', preGeography: undefined, - geography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - } + geography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } } }, { name: 'geography is missing', - preGeography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - }, + preGeography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } }, geography: undefined }, - { - name: 'both geographies are missing', - preGeography: undefined, - geography: undefined - } + { name: 'both geographies are missing', preGeography: undefined, geography: undefined } ])('$name', ({ preGeography, geography }) => { - const context = createContextWithHome( - { - preGeography, - geography - }, - validUuid - ); + const context = createContextWithHome({ preGeography, geography }, validUuid); const result = homeAuditChecks.HM_I_preGeographyAndHomeGeographyTooFarApartWarning(context); @@ -77,16 +57,8 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartWarning audit check', () = const context = createContextWithHome( { - preGeography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - }, - geography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - } + preGeography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } }, + geography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } } }, validUuid ); @@ -99,29 +71,15 @@ describe('HM_I_preGeographyAndHomeGeographyTooFarApartWarning audit check', () = describe(`should warn when distance is >= ${MIN_DISTANCE_PRE_AND_GEOGRAPHY_WARNING} and < ${MAX_DISTANCE_PRE_AND_GEOGRAPHY_ERROR} meters`, () => { it.each([ - { - name: `distance exactly ${MIN_DISTANCE_PRE_AND_GEOGRAPHY_WARNING} meters`, - distance: MIN_DISTANCE_PRE_AND_GEOGRAPHY_WARNING - }, - { - name: `distance exactly ${MAX_DISTANCE_PRE_AND_GEOGRAPHY_ERROR - 1} meters`, - distance: MAX_DISTANCE_PRE_AND_GEOGRAPHY_ERROR - 1 - } + { name: `distance exactly ${MIN_DISTANCE_PRE_AND_GEOGRAPHY_WARNING} meters`, distance: MIN_DISTANCE_PRE_AND_GEOGRAPHY_WARNING }, + { name: `distance exactly ${MAX_DISTANCE_PRE_AND_GEOGRAPHY_ERROR - 1} meters`, distance: MAX_DISTANCE_PRE_AND_GEOGRAPHY_ERROR - 1 } ])('$name', ({ distance }) => { (turfDistance as jest.Mock).mockReturnValue(distance); const context = createContextWithHome( { - preGeography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - }, - geography: { - type: 'Feature' as const, - properties: {}, - geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } - } + preGeography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } }, + geography: { type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] } } }, validUuid ); diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_M_Geography.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_M_Geography.test.ts index 88d8c111f..e24394c74 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_M_Geography.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/HM_M_Geography.test.ts @@ -36,4 +36,3 @@ describe('HM_M_Geography audit check', () => { }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/testHelper.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/testHelper.ts index cd40582e8..1c742b587 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/testHelper.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/home/testHelper.ts @@ -26,7 +26,10 @@ export const createMockHome = (overrides: Partial = {}, validUuid = uuidV4 } as Home; }; -export const createContextWithHome = (homeOverrides: Partial = {}, validUuid = uuidV4()): HomeAuditCheckContext => { +export const createContextWithHome = ( + homeOverrides: Partial = {}, + validUuid = uuidV4() +): HomeAuditCheckContext => { return { home: createMockHome(homeOverrides, validUuid), interview: { _uuid: uuidV4() } as unknown as Interview, diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/HH_I_Size.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/HH_I_Size.test.ts index 4532aecff..e3cbfd4be 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/HH_I_Size.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/HH_I_Size.test.ts @@ -63,6 +63,4 @@ describe('HH_I_Size audit check', () => { const result = householdAuditChecks.HH_I_Size(context); expect(result).toBeUndefined(); }); - }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/HH_M_Size.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/HH_M_Size.test.ts index c583286ca..2749ebc5f 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/HH_M_Size.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/HH_M_Size.test.ts @@ -36,4 +36,3 @@ describe('HH_M_Size audit check', () => { }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/testHelper.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/testHelper.ts index c28a76ea9..33eeeb5ad 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/testHelper.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/household/testHelper.ts @@ -18,7 +18,10 @@ export const createMockHousehold = (overrides: Partial = {}, validUui } as Household; }; -export const createContextWithHousehold = (householdOverrides: Partial = {}, validUuid = uuidV4()): HouseholdAuditCheckContext => { +export const createContextWithHousehold = ( + householdOverrides: Partial = {}, + validUuid = uuidV4() +): HouseholdAuditCheckContext => { return { household: createMockHousehold(householdOverrides, validUuid), interview: { _uuid: uuidV4() } as unknown as Interview diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_I_InvalidAccessCode.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_I_InvalidAccessCode.test.ts index 737352b6b..a4b71ba6d 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_I_InvalidAccessCode.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_I_InvalidAccessCode.test.ts @@ -22,30 +22,12 @@ describe('I_I_InvalidAccessCodeFormat audit check', () => { describe('should pass in valid scenarios', () => { it.each([ - { - description: 'interview has valid access code with hyphen', - accessCode: '1234-5678' - }, - { - description: 'interview has valid access code without hyphen', - accessCode: '12345678' - }, - { - description: 'interview has no access code (undefined)', - accessCode: undefined - }, - { - description: 'interview has null access code', - accessCode: null - }, - { - description: 'access code is single space', - accessCode: ' ' - }, - { - description: 'interview has empty string access code', - accessCode: '' - } + { description: 'interview has valid access code with hyphen', accessCode: '1234-5678' }, + { description: 'interview has valid access code without hyphen', accessCode: '12345678' }, + { description: 'interview has no access code (undefined)', accessCode: undefined }, + { description: 'interview has null access code', accessCode: null }, + { description: 'access code is single space', accessCode: ' ' }, + { description: 'interview has empty string access code', accessCode: '' } ])('$description', ({ accessCode }) => { const interview = createMockInterview({ accessCode: accessCode as string | undefined }); const context: InterviewAuditCheckContext = { interview }; @@ -58,30 +40,12 @@ describe('I_I_InvalidAccessCodeFormat audit check', () => { describe('should fail when access code is invalid', () => { it.each([ - { - description: 'access code with letters', - accessCode: 'invalid-code' - }, - { - description: 'access code too short', - accessCode: '123' - }, - { - description: 'access code with special characters', - accessCode: '!@#$%^&*()' - }, - { - description: 'access code too long', - accessCode: '1234567890123456789012345678901234567890' - }, - { - description: 'access code with 7 digits only', - accessCode: '1234567' - }, - { - description: 'access code with multiple hyphens', - accessCode: '12-34-56-78' - } + { description: 'access code with letters', accessCode: 'invalid-code' }, + { description: 'access code too short', accessCode: '123' }, + { description: 'access code with special characters', accessCode: '!@#$%^&*()' }, + { description: 'access code too long', accessCode: '1234567890123456789012345678901234567890' }, + { description: 'access code with 7 digits only', accessCode: '1234567' }, + { description: 'access code with multiple hyphens', accessCode: '12-34-56-78' } ])('$description', ({ accessCode }) => { const interview = createMockInterview({ accessCode }, validUuid); const context: InterviewAuditCheckContext = { interview }; @@ -100,4 +64,3 @@ describe('I_I_InvalidAccessCodeFormat audit check', () => { }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_AccessCode.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_AccessCode.test.ts index 43c4e5304..0ff0ecd43 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_AccessCode.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_AccessCode.test.ts @@ -17,31 +17,11 @@ describe('I_M_AccessCode audit check', () => { describe('should pass when access code is present', () => { it.each([ - { - description: 'access code is required and present', - accessCode: '1234-5678', - isRequired: true - }, - { - description: 'access code is not required and present', - accessCode: '1234-5678', - isRequired: false - }, - { - description: 'access code is not required and missing', - accessCode: undefined, - isRequired: false - }, - { - description: 'access code is not required and null', - accessCode: null, - isRequired: false - }, - { - description: 'access code is not required and empty string', - accessCode: '', - isRequired: false - } + { description: 'access code is required and present', accessCode: '1234-5678', isRequired: true }, + { description: 'access code is not required and present', accessCode: '1234-5678', isRequired: false }, + { description: 'access code is not required and missing', accessCode: undefined, isRequired: false }, + { description: 'access code is not required and null', accessCode: null, isRequired: false }, + { description: 'access code is not required and empty string', accessCode: '', isRequired: false } ])('$description', async ({ accessCode, isRequired }) => { await jest.isolateModulesAsync(async () => { // Import modules inside isolateModules to get fresh instances @@ -66,30 +46,12 @@ describe('I_M_AccessCode audit check', () => { describe('should fail when access code is required and missing', () => { it.each([ - { - description: 'access code is undefined', - accessCode: undefined - }, - { - description: 'access code is null', - accessCode: null - }, - { - description: 'access code is empty string', - accessCode: '' - }, - { - description: 'access code is whitespace only', - accessCode: ' ' - }, - { - description: 'access code is tab character', - accessCode: '\t' - }, - { - description: 'access code is newline character', - accessCode: '\n' - } + { description: 'access code is undefined', accessCode: undefined }, + { description: 'access code is null', accessCode: null }, + { description: 'access code is empty string', accessCode: '' }, + { description: 'access code is whitespace only', accessCode: ' ' }, + { description: 'access code is tab character', accessCode: '\t' }, + { description: 'access code is newline character', accessCode: '\n' } ])('$description', async ({ accessCode }) => { await jest.isolateModulesAsync(async () => { // Import modules inside isolateModules to get fresh instances @@ -97,10 +59,7 @@ describe('I_M_AccessCode audit check', () => { const { interviewAuditChecks } = await import('../../InterviewAuditChecks'); // Set config to require access code - projectConfig.requiredFieldsBySurveyObject = { - ...projectConfig.requiredFieldsBySurveyObject, - interview: ['accessCode'] - }; + projectConfig.requiredFieldsBySurveyObject = { ...projectConfig.requiredFieldsBySurveyObject, interview: ['accessCode'] }; const interview = createMockInterview({ accessCode: accessCode as string | undefined }, validUuid); const context: InterviewAuditCheckContext = { interview }; @@ -120,4 +79,3 @@ describe('I_M_AccessCode audit check', () => { }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_Languages.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_Languages.test.ts index fa1da9bbe..836b486ab 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_Languages.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_Languages.test.ts @@ -14,9 +14,7 @@ describe('I_M_Languages audit check', () => { const validUuid = uuidV4(); it('should pass when interview has languages', () => { - const context = createContextWithInterview({ - paradata: new InterviewParadata({ languages: [{ language: 'en' }, { language: 'fr' }] }) - }); + const context = createContextWithInterview({ paradata: new InterviewParadata({ languages: [{ language: 'en' }, { language: 'fr' }] }) }); const result = interviewAuditChecks.I_M_Languages(context); @@ -24,9 +22,7 @@ describe('I_M_Languages audit check', () => { }); it('should pass when interview has at least one language', () => { - const context = createContextWithInterview({ - paradata: new InterviewParadata({ languages: [{ language: 'en' }] }) - }, validUuid); + const context = createContextWithInterview({ paradata: new InterviewParadata({ languages: [{ language: 'en' }] }) }, validUuid); const result = interviewAuditChecks.I_M_Languages(context); @@ -34,9 +30,7 @@ describe('I_M_Languages audit check', () => { }); it('should fail when interview missing languages', () => { - const context = createContextWithInterview({ - paradata: new InterviewParadata({ languages: undefined }) - }, validUuid); + const context = createContextWithInterview({ paradata: new InterviewParadata({ languages: undefined }) }, validUuid); const result = interviewAuditChecks.I_M_Languages(context); @@ -52,9 +46,7 @@ describe('I_M_Languages audit check', () => { }); it('should fail when interview has empty languages array', () => { - const context = createContextWithInterview({ - paradata: new InterviewParadata({ languages: [] }) - }, validUuid); + const context = createContextWithInterview({ paradata: new InterviewParadata({ languages: [] }) }, validUuid); const result = interviewAuditChecks.I_M_Languages(context); @@ -70,9 +62,7 @@ describe('I_M_Languages audit check', () => { }); it('should fail when interview has no paradata', () => { - const context = createContextWithInterview({ - paradata: undefined - }, validUuid); + const context = createContextWithInterview({ paradata: undefined }, validUuid); const result = interviewAuditChecks.I_M_Languages(context); @@ -87,4 +77,3 @@ describe('I_M_Languages audit check', () => { }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_StartedAt.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_StartedAt.test.ts index c567ac3e3..356437e79 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_StartedAt.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/I_M_StartedAt.test.ts @@ -16,14 +16,8 @@ describe('I_M_StartedAt audit check', () => { describe('should pass when startedAt is present', () => { it.each([ - { - description: 'interview has valid startedAt timestamp', - startedAt: 1625097600 - }, - { - description: 'interview has startedAt timestamp of 0', - startedAt: 0 - } + { description: 'interview has valid startedAt timestamp', startedAt: 1625097600 }, + { description: 'interview has startedAt timestamp of 0', startedAt: 0 } ])('$description', ({ startedAt }) => { const interview = createMockInterview(); interview.paradata = new InterviewParadata({ startedAt }); @@ -37,71 +31,21 @@ describe('I_M_StartedAt audit check', () => { describe('should fail when startedAt is missing', () => { it.each([ - { - description: 'interview missing startedAt', - hasParadata: true, - startedAt: undefined - }, - { - description: 'interview has no paradata', - hasParadata: false, - startedAt: undefined - }, - { - description: 'interview has null startedAt', - hasParadata: true, - startedAt: null - }, - { - description: 'interview has null startedAt with no paradata', - hasParadata: false, - startedAt: null - }, - { - description: 'interview has negative startedAt timestamp', - hasParadata: true, - startedAt: -1234567890 - }, - { - description: 'interview has negative startedAt timestamp with no paradata', - hasParadata: false, - startedAt: -1234567890 - }, - { - description: 'interview has NaN startedAt', - hasParadata: true, - startedAt: NaN - }, - { - description: 'interview has NaN startedAt with no paradata', - hasParadata: false, - startedAt: NaN - }, - { - description: 'interview has Infinity startedAt', - hasParadata: true, - startedAt: Infinity - }, - { - description: 'interview has Infinity startedAt with no paradata', - hasParadata: false, - startedAt: Infinity - }, - { - description: 'interview has -Infinity startedAt', - hasParadata: true, - startedAt: -Infinity - }, - { - description: 'interview has -Infinity startedAt with no paradata', - hasParadata: false, - startedAt: -Infinity - } + { description: 'interview missing startedAt', hasParadata: true, startedAt: undefined }, + { description: 'interview has no paradata', hasParadata: false, startedAt: undefined }, + { description: 'interview has null startedAt', hasParadata: true, startedAt: null }, + { description: 'interview has null startedAt with no paradata', hasParadata: false, startedAt: null }, + { description: 'interview has negative startedAt timestamp', hasParadata: true, startedAt: -1234567890 }, + { description: 'interview has negative startedAt timestamp with no paradata', hasParadata: false, startedAt: -1234567890 }, + { description: 'interview has NaN startedAt', hasParadata: true, startedAt: NaN }, + { description: 'interview has NaN startedAt with no paradata', hasParadata: false, startedAt: NaN }, + { description: 'interview has Infinity startedAt', hasParadata: true, startedAt: Infinity }, + { description: 'interview has Infinity startedAt with no paradata', hasParadata: false, startedAt: Infinity }, + { description: 'interview has -Infinity startedAt', hasParadata: true, startedAt: -Infinity }, + { description: 'interview has -Infinity startedAt with no paradata', hasParadata: false, startedAt: -Infinity } ])('$description', ({ hasParadata, startedAt }) => { const interview = createMockInterview(undefined, validUuid); - interview.paradata = hasParadata - ? new InterviewParadata({ startedAt: startedAt as number | undefined }) - : undefined; + interview.paradata = hasParadata ? new InterviewParadata({ startedAt: startedAt as number | undefined }) : undefined; const context: InterviewAuditCheckContext = { interview }; const result = interviewAuditChecks.I_M_StartedAt(context); @@ -118,4 +62,3 @@ describe('I_M_StartedAt audit check', () => { }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/testHelper.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/testHelper.ts index 5ddca8636..6777ec344 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/testHelper.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/interview/testHelper.ts @@ -13,14 +13,19 @@ import { InterviewParadata } from 'evolution-common/lib/services/baseObjects/int export const createMockInterview = (overrides: Partial = {}, validUuid = uuidV4(), validId = 123) => { return { _uuid: validUuid, - get uuid() { return this._uuid; }, + get uuid() { + return this._uuid; + }, id: validId, paradata: new InterviewParadata({ languages: [{ language: 'en' }] }), ...overrides } as Interview; }; -export const createContextWithInterview = (interviewOverrides: Partial = {}, validUuid = uuidV4()): InterviewAuditCheckContext => { +export const createContextWithInterview = ( + interviewOverrides: Partial = {}, + validUuid = uuidV4() +): InterviewAuditCheckContext => { return { interview: createMockInterview(interviewOverrides, validUuid) }; diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/journey/J_M_StartDate.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/journey/J_M_StartDate.test.ts index e8b93d4a5..8ea173a4a 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/journey/J_M_StartDate.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/journey/J_M_StartDate.test.ts @@ -51,5 +51,4 @@ describe('J_M_StartDate audit check', () => { ignore: false }); }); - }); diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/journey/testHelper.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/journey/testHelper.ts index 3fea47c25..1ff8e7ae5 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/journey/testHelper.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/journey/testHelper.ts @@ -20,7 +20,10 @@ export const createMockJourney = (overrides: Partial = {}, validUuid = } as Journey; }; -export const createContextWithJourney = (journeyOverrides: Partial = {}, validUuid = uuidV4()): JourneyAuditCheckContext => { +export const createContextWithJourney = ( + journeyOverrides: Partial = {}, + validUuid = uuidV4() +): JourneyAuditCheckContext => { return { journey: createMockJourney(journeyOverrides, validUuid), person: { _uuid: uuidV4() } as unknown as Person, diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/person/P_M_Age.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/person/P_M_Age.test.ts index 08c31b5c3..522dd8581 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/person/P_M_Age.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/person/P_M_Age.test.ts @@ -51,6 +51,4 @@ describe('P_M_Age audit check', () => { ignore: false }); }); - }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/person/testHelper.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/person/testHelper.ts index b16a9513f..db76c511e 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/person/testHelper.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/person/testHelper.ts @@ -18,7 +18,10 @@ export const createMockPerson = (overrides: Partial = {}, validUuid = uu } as Person; }; -export const createContextWithPerson = (personOverrides: Partial = {}, validUuid = uuidV4()): PersonAuditCheckContext => { +export const createContextWithPerson = ( + personOverrides: Partial = {}, + validUuid = uuidV4() +): PersonAuditCheckContext => { return { person: createMockPerson(personOverrides, validUuid), household: undefined, diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/segment/S_M_Mode.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/segment/S_M_Mode.test.ts index 54f59c326..bbaf5f0a7 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/segment/S_M_Mode.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/segment/S_M_Mode.test.ts @@ -35,6 +35,4 @@ describe('S_M_Mode audit check', () => { ignore: false }); }); - }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/segment/testHelper.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/segment/testHelper.ts index 6b2cfb93a..e9438f0bd 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/segment/testHelper.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/segment/testHelper.ts @@ -21,7 +21,10 @@ export const createMockSegment = (overrides: Partial = {}, validUuid = } as Segment; }; -export const createContextWithSegment = (segmentOverrides: Partial = {}, validUuid = uuidV4()): SegmentAuditCheckContext => { +export const createContextWithSegment = ( + segmentOverrides: Partial = {}, + validUuid = uuidV4() +): SegmentAuditCheckContext => { return { segment: createMockSegment(segmentOverrides, validUuid), trip: { _uuid: uuidV4() } as unknown as Trip, diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/trip/T_M_Segments.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/trip/T_M_Segments.test.ts index 50af12c25..6a65231f5 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/trip/T_M_Segments.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/trip/T_M_Segments.test.ts @@ -53,4 +53,3 @@ describe('T_M_Segments audit check', () => { }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/trip/testHelper.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/trip/testHelper.ts index ecc57e381..be26d08f1 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/trip/testHelper.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/trip/testHelper.ts @@ -20,7 +20,10 @@ export const createMockTrip = (overrides: Partial = {}, validUuid = uuidV4 } as Trip; }; -export const createContextWithTrip = (tripOverrides: Partial = {}, validUuid = uuidV4()): TripAuditCheckContext => { +export const createContextWithTrip = ( + tripOverrides: Partial = {}, + validUuid = uuidV4() +): TripAuditCheckContext => { return { trip: createMockTrip(tripOverrides, validUuid), journey: { _uuid: uuidV4() } as unknown as Journey, diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/VP_I_Geography.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/VP_I_Geography.test.ts index 89efbaefb..00f10fd64 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/VP_I_Geography.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/VP_I_Geography.test.ts @@ -21,16 +21,19 @@ describe('VP_I_Geography audit check', () => { }); it('should error when geography has invalid coordinates', () => { - const context = createContextWithVisitedPlace({ - geography: { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: [-73.5] // Missing latitude + const context = createContextWithVisitedPlace( + { + geography: { + type: 'Feature', + properties: {}, + geometry: { + type: 'Point', + coordinates: [-73.5] // Missing latitude + } } - } - }, validUuid); + }, + validUuid + ); const result = visitedPlaceAuditChecks.VP_I_Geography(context); @@ -46,16 +49,10 @@ describe('VP_I_Geography audit check', () => { }); it('should error when geography has no coordinates', () => { - const context = createContextWithVisitedPlace({ - geography: { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: [] as number[] - } - } - }, validUuid); + const context = createContextWithVisitedPlace( + { geography: { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [] as number[] } } }, + validUuid + ); const result = visitedPlaceAuditChecks.VP_I_Geography(context); @@ -78,4 +75,3 @@ describe('VP_I_Geography audit check', () => { expect(result).toBeUndefined(); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/VP_M_Geography.test.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/VP_M_Geography.test.ts index 87cae8ec6..4a8b820c2 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/VP_M_Geography.test.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/VP_M_Geography.test.ts @@ -36,4 +36,3 @@ describe('VP_M_Geography audit check', () => { }); }); }); - diff --git a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/testHelper.ts b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/testHelper.ts index 12281dad8..cb8e8a4d5 100644 --- a/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/testHelper.ts +++ b/packages/evolution-backend/src/services/audits/auditChecks/checks/__tests__/visitedPlace/testHelper.ts @@ -28,7 +28,10 @@ export const createMockVisitedPlace = (overrides: Partial = {}, va } as VisitedPlace; }; -export const createContextWithVisitedPlace = (visitedPlaceOverrides: Partial = {}, validUuid = uuidV4()): VisitedPlaceAuditCheckContext => { +export const createContextWithVisitedPlace = ( + visitedPlaceOverrides: Partial = {}, + validUuid = uuidV4() +): VisitedPlaceAuditCheckContext => { return { visitedPlace: createMockVisitedPlace(visitedPlaceOverrides, validUuid), person: { _uuid: uuidV4() } as unknown as Person, diff --git a/packages/evolution-backend/src/services/auth/__tests__/authByFieldLogin.test.ts b/packages/evolution-backend/src/services/auth/__tests__/authByFieldLogin.test.ts index 8c2f773ce..da338f473 100644 --- a/packages/evolution-backend/src/services/auth/__tests__/authByFieldLogin.test.ts +++ b/packages/evolution-backend/src/services/auth/__tests__/authByFieldLogin.test.ts @@ -26,17 +26,17 @@ jest.mock('../../accessCode', () => ({ // Mock the ParticipantModel class jest.mock('../participantAuthModel', () => { const originalModule = jest.requireActual('../participantAuthModel'); - + // Create a mock ParticipantModel class class MockParticipantModel { public attributes: any; - + constructor(attributes: any) { this.attributes = attributes; } - + recordLogin = mockRecordLogin; - + sanitize() { return { id: this.attributes.id, @@ -48,13 +48,13 @@ jest.mock('../participantAuthModel', () => { serializedPermissions: [] }; } - + async verifyPassword(password: string) { // Simple mock implementation return password.toUpperCase() === validPostalCode; } } - + return { ...originalModule, participantAuthModel: { @@ -66,18 +66,18 @@ jest.mock('../participantAuthModel', () => { }; }); -jest.mock('../../../models/interviewsPreFill.db.queries', () => ({ - getByReferenceValue: jest.fn() -})); +jest.mock('../../../models/interviewsPreFill.db.queries', () => ({ getByReferenceValue: jest.fn() })); // Mock the project config to set the postalCodeRegion jest.mock('evolution-common/lib/config/project.config', () => ({ - postalCodeRegion: 'canada' // Default for tests + postalCodeRegion: 'canada' // Default for tests })); const mockFind = participantAuthModel.find as jest.MockedFunction; const mockCreateAndSave = participantAuthModel.createAndSave as jest.MockedFunction; -const mockGetByReferenceValue = interviewsPreFillQueries.getByReferenceValue as jest.MockedFunction; +const mockGetByReferenceValue = interviewsPreFillQueries.getByReferenceValue as jest.MockedFunction< + typeof interviewsPreFillQueries.getByReferenceValue +>; // Apply the auth strategy to passport byFieldLogin(passport, participantAuthModel); @@ -94,22 +94,9 @@ const preFillAccessCode = '1111-2222'; const preFillPostalCode = 'G1T2R3'; const encryptedPostalCode = `encrypted_${validPostalCode}`; -const mockValidUser = new ParticipantModel({ - id: 5, - username: validAccessCode, - password: encryptedPostalCode, - is_confirmed: true, - is_valid: true -}); +const mockValidUser = new ParticipantModel({ id: 5, username: validAccessCode, password: encryptedPostalCode, is_confirmed: true, is_valid: true }); -const preFilledResponse = { - 'home.postalCode': { - value: preFillPostalCode - }, - 'someOtherField': { - value: 'someValue' - } -}; +const preFilledResponse = { 'home.postalCode': { value: preFillPostalCode }, someOtherField: { value: 'someValue' } }; const newUserId = 7; @@ -119,11 +106,8 @@ beforeEach(() => { mockFind.mockClear(); mockFind.mockResolvedValue(undefined); // undefined by default mockCreateAndSave.mockClear(); - mockCreateAndSave.mockImplementation(async(attribs) => { - return new ParticipantModel({ - id: newUserId, - ...attribs - }); + mockCreateAndSave.mockImplementation(async (attribs) => { + return new ParticipantModel({ id: newUserId, ...attribs }); }); mockGetByReferenceValue.mockClear(); mockGetByReferenceValue.mockResolvedValue(undefined); @@ -132,26 +116,28 @@ beforeEach(() => { describe('Auth by Field Login Strategy Tests', () => { test('Login with valid existing user', async () => { mockFind.mockResolvedValueOnce(mockValidUser); - const req = { - logIn: logInFct, - body: { - accessCode: validAccessCode, - postalCode: validPostalCode - } - }; - + const req = { logIn: logInFct, body: { accessCode: validAccessCode, postalCode: validPostalCode } }; + const authPromise = new Promise((resolve, reject) => { - passport.authenticate('auth-by-field')(req, {end: jest.fn()}, (err, result) => { + passport.authenticate('auth-by-field')(req, { end: jest.fn() }, (err, result) => { resolve({ result, err }); }); }); - + const authResult: any = await authPromise; expect(authResult.err).toBeUndefined(); expect(logInFct).toHaveBeenCalledTimes(1); expect(logInFct).toHaveBeenCalledWith( - { id: mockValidUser.attributes.id, username: mockValidUser.attributes.username, email: undefined, firstName: undefined, lastName: undefined, preferences: {}, serializedPermissions: [] }, - expect.anything(), + { + id: mockValidUser.attributes.id, + username: mockValidUser.attributes.username, + email: undefined, + firstName: undefined, + lastName: undefined, + preferences: {}, + serializedPermissions: [] + }, + expect.anything(), expect.anything() ); expect(mockFind).toHaveBeenCalledTimes(1); @@ -161,23 +147,17 @@ describe('Auth by Field Login Strategy Tests', () => { test('Login with prefilled data match', async () => { mockGetByReferenceValue.mockResolvedValueOnce(preFilledResponse); - + const authPromise = new Promise((resolve, reject) => { passport.authenticate('auth-by-field')( - { - logIn: logInFct, - body: { - accessCode: preFillAccessCode, - postalCode: preFillPostalCode - } - }, - {end: jest.fn()}, + { logIn: logInFct, body: { accessCode: preFillAccessCode, postalCode: preFillPostalCode } }, + { end: jest.fn() }, (err, result) => { resolve({ result, err }); } ); }); - + const authResult: any = await authPromise; expect(authResult.err).toBeUndefined(); expect(logInFct).toHaveBeenCalledTimes(1); @@ -186,9 +166,7 @@ describe('Auth by Field Login Strategy Tests', () => { expect(mockGetByReferenceValue).toHaveBeenCalledTimes(1); expect(mockGetByReferenceValue).toHaveBeenCalledWith(preFillAccessCode.toUpperCase()); expect(mockCreateAndSave).toHaveBeenCalledTimes(1); - expect(mockCreateAndSave).toHaveBeenCalledWith({ - username: `${preFillAccessCode.toUpperCase()}-${preFillPostalCode.toUpperCase()}` - }); + expect(mockCreateAndSave).toHaveBeenCalledWith({ username: `${preFillAccessCode.toUpperCase()}-${preFillPostalCode.toUpperCase()}` }); expect(mockRecordLogin).toHaveBeenCalledTimes(1); }); @@ -196,30 +174,21 @@ describe('Auth by Field Login Strategy Tests', () => { // No existing user or prefill data, but user confirms it's ok const authPromise = new Promise((resolve, reject) => { passport.authenticate('auth-by-field')( - { - logIn: logInFct, - body: { - accessCode: '5555-5555', - postalCode: 'j4r5t6', - confirmCredentials: true - } - }, - {end: jest.fn()}, + { logIn: logInFct, body: { accessCode: '5555-5555', postalCode: 'j4r5t6', confirmCredentials: true } }, + { end: jest.fn() }, (err, result) => { resolve({ result, err }); } ); }); - + const authResult: any = await authPromise; expect(authResult.err).toBeUndefined(); expect(logInFct).toHaveBeenCalledTimes(1); expect(mockFind).toHaveBeenCalledTimes(1); expect(mockGetByReferenceValue).toHaveBeenCalledTimes(1); expect(mockCreateAndSave).toHaveBeenCalledTimes(1); - expect(mockCreateAndSave).toHaveBeenCalledWith({ - username: expect.stringMatching(/^5555-5555-J4R5T6-[A-Z0-9]{6}$/) - }); + expect(mockCreateAndSave).toHaveBeenCalledWith({ username: expect.stringMatching(/^5555-5555-J4R5T6-[A-Z0-9]{6}$/) }); expect(mockRecordLogin).toHaveBeenCalledTimes(1); }); @@ -227,20 +196,14 @@ describe('Auth by Field Login Strategy Tests', () => { // No user, no prefill data, and no confirmation const authPromise = new Promise((resolve, reject) => { passport.authenticate('auth-by-field')( - { - logIn: logInFct, - body: { - accessCode: '4444-4444', - postalCode: 'h2e 2e2' - } - }, - {end: jest.fn()}, + { logIn: logInFct, body: { accessCode: '4444-4444', postalCode: 'h2e 2e2' } }, + { end: jest.fn() }, (err, result) => { resolve({ result, err }); } ); }); - + const authResult: any = await authPromise; expect(authResult.err).toEqual('FieldCombinationNotFound'); expect(authResult.result).toBeFalsy(); @@ -255,20 +218,14 @@ describe('Auth by Field Login Strategy Tests', () => { // No user, no prefill data, and no confirmation const authPromise = new Promise((resolve, reject) => { passport.authenticate('auth-by-field')( - { - logIn: logInFct, - body: { - accessCode: 'invalid', - postalCode: 'h2e 2e2' - } - }, - {end: jest.fn()}, + { logIn: logInFct, body: { accessCode: 'invalid', postalCode: 'h2e 2e2' } }, + { end: jest.fn() }, (err, result) => { resolve({ result, err }); } ); }); - + const authResult: any = await authPromise; expect(authResult.err).toEqual('InvalidData'); expect(authResult.result).toBeFalsy(); @@ -283,20 +240,14 @@ describe('Auth by Field Login Strategy Tests', () => { // No user, no prefill data, and no confirmation const authPromise = new Promise((resolve, reject) => { passport.authenticate('auth-by-field')( - { - logIn: logInFct, - body: { - accessCode: '1111-1111', - postalCode: 'h2e 222' - } - }, - {end: jest.fn()}, + { logIn: logInFct, body: { accessCode: '1111-1111', postalCode: 'h2e 222' } }, + { end: jest.fn() }, (err, result) => { resolve({ result, err }); } ); }); - + const authResult: any = await authPromise; expect(authResult.err).toEqual('InvalidData'); expect(authResult.result).toBeFalsy(); @@ -309,23 +260,17 @@ describe('Auth by Field Login Strategy Tests', () => { test('Login with database error in authModel find', async () => { mockFind.mockRejectedValueOnce(new Error('Database connection error')); - + const authPromise = new Promise((resolve, reject) => { passport.authenticate('auth-by-field')( - { - logIn: logInFct, - body: { - accessCode: validAccessCode, - postalCode: validPostalCode - } - }, - {end: jest.fn()}, + { logIn: logInFct, body: { accessCode: validAccessCode, postalCode: validPostalCode } }, + { end: jest.fn() }, (err, result) => { resolve({ result, err }); } ); }); - + const authResult: any = await authPromise; expect(authResult.err).toEqual('FailedToCreateUser'); expect(authResult.result).toBeFalsy(); @@ -338,23 +283,17 @@ describe('Auth by Field Login Strategy Tests', () => { test('Login with database error in prefill lookup', async () => { mockGetByReferenceValue.mockRejectedValueOnce(new Error('Database connection error')); - + const authPromise = new Promise((resolve, reject) => { passport.authenticate('auth-by-field')( - { - logIn: logInFct, - body: { - accessCode: validAccessCode, - postalCode: validPostalCode - } - }, - {end: jest.fn()}, + { logIn: logInFct, body: { accessCode: validAccessCode, postalCode: validPostalCode } }, + { end: jest.fn() }, (err, result) => { resolve({ result, err }); } ); }); - + const authResult: any = await authPromise; expect(authResult.err).toEqual('FailedToCreateUser'); expect(authResult.result).toBeFalsy(); @@ -368,23 +307,17 @@ describe('Auth by Field Login Strategy Tests', () => { test('Login with failed user creation', async () => { mockGetByReferenceValue.mockResolvedValueOnce(preFilledResponse); mockCreateAndSave.mockRejectedValue('Error creating user'); - + const authPromise = new Promise((resolve, reject) => { passport.authenticate('auth-by-field')( - { - logIn: logInFct, - body: { - accessCode: preFillAccessCode, - postalCode: preFillPostalCode - } - }, - {end: jest.fn()}, + { logIn: logInFct, body: { accessCode: preFillAccessCode, postalCode: preFillPostalCode } }, + { end: jest.fn() }, (err, result) => { resolve({ result, err }); } ); }); - + const authResult: any = await authPromise; expect(authResult.err).toEqual('FailedToCreateUser'); expect(authResult.result).toBeFalsy(); @@ -397,23 +330,17 @@ describe('Auth by Field Login Strategy Tests', () => { test('Login with case-insensitive matching', async () => { mockFind.mockResolvedValueOnce(mockValidUser); - + const authPromise = new Promise((resolve, reject) => { passport.authenticate('auth-by-field')( - { - logIn: logInFct, - body: { - accessCode: validAccessCode.toLowerCase(), - postalCode: validPostalCode.toLowerCase() - } - }, - {end: jest.fn()}, + { logIn: logInFct, body: { accessCode: validAccessCode.toLowerCase(), postalCode: validPostalCode.toLowerCase() } }, + { end: jest.fn() }, (err, result) => { resolve({ result, err }); } ); }); - + const authResult: any = await authPromise; expect(authResult.err).toBeUndefined(); expect(logInFct).toHaveBeenCalledTimes(1); diff --git a/packages/evolution-backend/src/services/auth/__tests__/participantAuthModel.test.ts b/packages/evolution-backend/src/services/auth/__tests__/participantAuthModel.test.ts index 27339aae1..8ca1eedd2 100644 --- a/packages/evolution-backend/src/services/auth/__tests__/participantAuthModel.test.ts +++ b/packages/evolution-backend/src/services/auth/__tests__/participantAuthModel.test.ts @@ -23,7 +23,7 @@ jest.mock('../../../models/participants.db.queries', () => ({ })); const mockSave = participantsDbQueries.update as jest.MockedFunction; -const mockFind = participantsDbQueries.find as jest.MockedFunction +const mockFind = participantsDbQueries.find as jest.MockedFunction; const mockGetById = participantsDbQueries.getById as jest.MockedFunction; const mockCreate = participantsDbQueries.create as jest.MockedFunction; const mockLogLastLogin = participantsDbQueries.logLastLogin as jest.MockedFunction; @@ -64,12 +64,7 @@ test('sanitizeUserAttributes', () => { const last_name = 'last'; const id = 4; const preferences = { pref1: 'abc', pref2: true }; - let participantAttributes: ParticipantAttributes = { - id, - username, - first_name, - last_name - }; + let participantAttributes: ParticipantAttributes = { id, username, first_name, last_name }; expect(sanitizeUserAttributes(participantAttributes)).toEqual({ id, username, @@ -105,28 +100,19 @@ test('sanitizeUserAttributes', () => { describe('ParticipantAuthModel: Account confirmation', () => { test('Test valid token confirmation', async () => { - const token = "thisisanarbitraytoken"; - mockFind.mockResolvedValueOnce({ - id: defaultUserId, - confirmation_token: token, - is_confirmed: false - }); + const token = 'thisisanarbitraytoken'; + mockFind.mockResolvedValueOnce({ id: defaultUserId, confirmation_token: token, is_confirmed: false }); const result = await participantAuthModel.confirmAccount(token); expect(result).toEqual('Confirmed'); expect(mockFind).toHaveBeenCalledTimes(1); expect(mockFind).toHaveBeenCalledWith({ confirmation_token: token }); expect(mockSave).toHaveBeenCalledTimes(1); expect(mockSave).toHaveBeenCalledWith(defaultUserId, { confirmation_token: null, is_confirmed: true }); - }); - + test('Test valid token with callback', async () => { - const token = "thisisanarbitraytoken"; - mockFind.mockResolvedValueOnce({ - id: defaultUserId, - confirmation_token: token, - is_confirmed: false - }) + const token = 'thisisanarbitraytoken'; + mockFind.mockResolvedValueOnce({ id: defaultUserId, confirmation_token: token, is_confirmed: false }); const callback = jest.fn(); const result = await participantAuthModel.confirmAccount(token, callback); expect(result).toEqual('Confirmed'); @@ -136,10 +122,10 @@ describe('ParticipantAuthModel: Account confirmation', () => { expect(mockSave).toHaveBeenCalledWith(defaultUserId, { confirmation_token: null, is_confirmed: true }); expect(callback).toHaveBeenCalled(); }); - + test('Test invalid token confirmation', async () => { - const token = "thisisanarbitraytoken"; - mockFind.mockResolvedValueOnce(undefined) + const token = 'thisisanarbitraytoken'; + mockFind.mockResolvedValueOnce(undefined); const result = await participantAuthModel.confirmAccount(token); expect(result).toEqual('NotFound'); expect(mockFind).toHaveBeenCalledTimes(1); @@ -170,7 +156,7 @@ describe('ParticipantAuthModel: Reset password', () => { password_reset_token: null }); }); - + test('Reset password expired', async () => { const token = 'thisisanarbitraytoken'; const newPassword = 'newPassword'; @@ -184,7 +170,7 @@ describe('ParticipantAuthModel: Reset password', () => { expect(result).toEqual('Expired'); expect(mockSave).not.toHaveBeenCalled(); }); - + test('Reset password not found', async () => { const token = 'thisisanarbitraytoken'; const newPassword = 'newPassword'; @@ -193,7 +179,7 @@ describe('ParticipantAuthModel: Reset password', () => { expect(result).toEqual('NotFound'); expect(mockSave).not.toHaveBeenCalled(); }); - + test('Reset password, no password', async () => { const token = 'thisisanarbitraytoken'; mockFind.mockResolvedValue({ @@ -205,7 +191,7 @@ describe('ParticipantAuthModel: Reset password', () => { let result = await participantAuthModel.resetPassword(token); expect(result).toEqual('Confirmed'); expect(mockSave).not.toHaveBeenCalled(); - + result = await participantAuthModel.resetPassword(token, undefined); expect(result).toEqual('Confirmed'); expect(mockSave).not.toHaveBeenCalled(); @@ -234,11 +220,7 @@ test('ParticipantAuthModel: getById', async () => { }); describe('ParticipantAuthModel: createAndSave', () => { - const mockedReturnedAttributes = { - id: 200, - username: 'random', - email: 'foo@bar.com' - } + const mockedReturnedAttributes = { id: 200, username: 'random', email: 'foo@bar.com' }; const mockedReturnedParticipant = new ParticipantModel(mockedReturnedAttributes); test('Empty participant', async () => { @@ -335,11 +317,7 @@ describe('ParticipantAuthModel: createAndSave', () => { }); test('With an extra id parameter', async () => { - const newUserParams = { - username: 'username', - email: 'foo@bar.com', - id: 4 - }; + const newUserParams = { username: 'username', email: 'foo@bar.com', id: 4 }; mockCreate.mockResolvedValueOnce(mockedReturnedAttributes); const newPart = await participantAuthModel.createAndSave(newUserParams); expect(JSON.stringify(newPart)).toEqual(JSON.stringify(mockedReturnedParticipant)); @@ -360,55 +338,41 @@ describe('ParticipantAuthModel: createAndSave', () => { }); test('rejected create call', async () => { - const newUserParams = { - username: 'username', - email: 'foo@bar.com', - id: 4 - }; + const newUserParams = { username: 'username', email: 'foo@bar.com', id: 4 }; mockCreate.mockRejectedValueOnce('Error'); let error: any = undefined; try { - await participantAuthModel.createAndSave(newUserParams) - } catch(err) { + await participantAuthModel.createAndSave(newUserParams); + } catch (err) { error = err; } expect(error).toEqual('Error'); /*await expect(participantAuthModel.createAndSave(newUserParams)) .rejects .toThrow('Error');*/ - }); }); - describe('ParticipantModel: Password verification', () => { test('Test password verification with string password', async () => { - const password = "test"; - const user = new ParticipantModel({ - id: defaultUserId, - password: participantAuthModel.encryptPassword(password), - }); + const password = 'test'; + const user = new ParticipantModel({ id: defaultUserId, password: participantAuthModel.encryptPassword(password) }); expect(await user.verifyPassword(password)).toBeTruthy(); expect(await user.verifyPassword('')).toBeFalsy; expect(await user.verifyPassword('Other password')).toBeFalsy(); }); - + test('Test password verification with null password', async () => { - const user = new ParticipantModel({ - id: defaultUserId, - password: null, - }); + const user = new ParticipantModel({ id: defaultUserId, password: null }); expect(await user.verifyPassword('')).toBeFalsy; expect(await user.verifyPassword('Other password')).toBeFalsy(); - }) - + }); + test('Test password verification with default user', async () => { - const user = new ParticipantModel({ - id: defaultUserId - }); + const user = new ParticipantModel({ id: defaultUserId }); expect(await user.verifyPassword('')).toBeFalsy; expect(await user.verifyPassword('Other password')).toBeFalsy(); - }) + }); }); test('ParticipantModel: Test get display name', async () => { @@ -416,53 +380,23 @@ test('ParticipantModel: Test get display name', async () => { const first_name = 'first'; const last_name = 'last'; const email = 'test@test.com'; - let user = new ParticipantModel({ - id: defaultUserId, - password: null, - }); + let user = new ParticipantModel({ id: defaultUserId, password: null }); expect(user.displayName).toEqual(''); - user = new ParticipantModel({ - id: defaultUserId, - password: null, - username, - email - }); + user = new ParticipantModel({ id: defaultUserId, password: null, username, email }); expect(user.displayName).toEqual(username); - user = new ParticipantModel({ - id: defaultUserId, - password: null, - username, - first_name - }); + user = new ParticipantModel({ id: defaultUserId, password: null, username, first_name }); expect(user.displayName).toEqual(first_name); - user = new ParticipantModel({ - id: defaultUserId, - password: null, - username, - last_name - }); + user = new ParticipantModel({ id: defaultUserId, password: null, username, last_name }); expect(user.displayName).toEqual(last_name); - user = new ParticipantModel({ - id: defaultUserId, - password: null, - username, - first_name, - last_name - }); + user = new ParticipantModel({ id: defaultUserId, password: null, username, first_name, last_name }); expect(user.displayName).toEqual(first_name + ' ' + last_name); - user = new ParticipantModel({ - id: defaultUserId, - password: null, - username: email, - email - }); + user = new ParticipantModel({ id: defaultUserId, password: null, username: email, email }); expect(user.displayName).toEqual(''); - }); test('ParticipantModel: sanitize', () => { @@ -472,14 +406,9 @@ test('ParticipantModel: sanitize', () => { const email = 'foo@bar.com'; const id = 100; const preferences = { pref1: 'abc', pref2: true }; - let user = new ParticipantModel({ + let user = new ParticipantModel({ id, password: null, username, first_name, last_name }); + expect(user.sanitize()).toEqual({ id, - password: null, - username, - first_name, - last_name - }); - expect(user.sanitize()).toEqual({ id, username, firstName: first_name, lastName: last_name, @@ -522,7 +451,6 @@ test('ParticipantModel: sanitize', () => { }); describe('ParticipantModel: Update attributes', () => { - const basePartAttribs = { id: defaultUserId, first_name: 'Foo', @@ -531,7 +459,7 @@ describe('ParticipantModel: Update attributes', () => { password: '$fdafdasfdas', is_test: false, is_active: false - } + }; let baseUser = new ParticipantModel(basePartAttribs); beforeEach(() => { @@ -556,7 +484,7 @@ describe('ParticipantModel: Update attributes', () => { expect(baseUser.attributes.is_test).toEqual(true); // not is_admin value - baseUser.updateAndSanitizeAttributes({ }); + baseUser.updateAndSanitizeAttributes({}); expect(baseUser.attributes.is_test).toEqual(true); // truthy number @@ -582,7 +510,7 @@ describe('ParticipantModel: Update attributes', () => { expect(baseUser.attributes.is_active).toEqual(true); // not is_admin value - baseUser.updateAndSanitizeAttributes({ }); + baseUser.updateAndSanitizeAttributes({}); expect(baseUser.attributes.is_active).toEqual(true); // truthy number @@ -592,7 +520,7 @@ describe('ParticipantModel: Update attributes', () => { test('First/last name', () => { // Valid strings - const name = 'Test' + const name = 'Test'; baseUser.updateAndSanitizeAttributes({ first_name: name, last_name: name }); expect(baseUser.attributes.last_name).toEqual(name); expect(baseUser.attributes.first_name).toEqual(name); @@ -611,12 +539,10 @@ describe('ParticipantModel: Update attributes', () => { test('Test random attributes', () => { baseUser.updateAndSanitizeAttributes({ arbitrary: 'some value', other: 'abc' }); expect(baseUser.attributes).toEqual(basePartAttribs); - }) - + }); }); test('ParticipantModel: properties', () => { - const user = new ParticipantModel(defaultParticipantAttributes); expect(user.id).toEqual(defaultParticipantAttributes.id); expect(user.email).toEqual(defaultParticipantAttributes.email); diff --git a/packages/evolution-backend/src/services/auth/__tests__/participantAuthorization.test.ts b/packages/evolution-backend/src/services/auth/__tests__/participantAuthorization.test.ts index a92660bc5..ea9b7c65c 100644 --- a/packages/evolution-backend/src/services/auth/__tests__/participantAuthorization.test.ts +++ b/packages/evolution-backend/src/services/auth/__tests__/participantAuthorization.test.ts @@ -12,27 +12,24 @@ import Interviews from '../../interviews/interviews'; let mockRequest: Partial; let mockResponse: Partial; -let nextFunction: NextFunction = jest.fn(); -let mockParticipant = { id: 3, username: 'participant' }; -let mockOtherParticipant = { id: 4, username: 'other' }; +const nextFunction: NextFunction = jest.fn(); +const mockParticipant = { id: 3, username: 'participant' }; +const mockOtherParticipant = { id: 4, username: 'other' }; -const mockGetInterviewByUuid = Interviews.getInterviewByUuid = jest.fn(); +const mockGetInterviewByUuid = (Interviews.getInterviewByUuid = jest.fn()); const interviewId = uuidV4(); beforeEach(() => { mockRequest = {}; - mockResponse = { - json: jest.fn(), - status: jest.fn().mockReturnThis() - }; + mockResponse = { json: jest.fn(), status: jest.fn().mockReturnThis() }; (nextFunction as any).mockClear(); mockGetInterviewByUuid.mockClear(); }); describe('Participant interview access', () => { - const defaultGetParams = { params: { interviewId }}; - const defaultPostParams = { body: { interviewId }}; + const defaultGetParams = { params: { interviewId } }; + const defaultPostParams = { body: { interviewId } }; each([ ['No participant', undefined, defaultGetParams, true, 401], @@ -49,7 +46,7 @@ describe('Participant interview access', () => { ['Post and get params, not identical, ok', mockParticipant, { ...defaultGetParams, body: { interviewId: uuidV4() } }, true, 400] ]).test('%s', async (_title, participant, reqParams, isDefined, expectedNextOrCode, is_active = true) => { mockRequest.user = participant; - const request = {...mockRequest, ...reqParams }; + const request = { ...mockRequest, ...reqParams }; mockGetInterviewByUuid.mockResolvedValue(isDefined ? { id: 1, participant_id: mockParticipant.id, is_active } : undefined); await isAuthorized()(request as Request, mockResponse as Response, nextFunction); if (typeof expectedNextOrCode === 'number') { diff --git a/packages/evolution-backend/src/services/auth/__tests__/roleDefinition.test.ts b/packages/evolution-backend/src/services/auth/__tests__/roleDefinition.test.ts index 3fa354c23..64cd09f3c 100644 --- a/packages/evolution-backend/src/services/auth/__tests__/roleDefinition.test.ts +++ b/packages/evolution-backend/src/services/auth/__tests__/roleDefinition.test.ts @@ -13,19 +13,10 @@ import each from 'jest-each'; defineDefaultRoles(); -const interview = { - id: 1, - uuid: 'arbitrary', - participant_id: 1, - is_active: true -} +const interview = { id: 1, uuid: 'arbitrary', participant_id: 1, is_active: true }; describe('default role', () => { - const user = { - id: 1, - uuid: 'arbitrary user', - is_admin: false - }; + const user = { id: 1, uuid: 'arbitrary user', is_admin: false }; each([ ['read same id', true, 'read'], @@ -39,7 +30,7 @@ describe('default role', () => { ['cannot delete other', false, 'delete'], ['cannot create other', false, 'create'], ['cannot validate other', false, 'validate'], - ['cannot confirm other', false, 'confirm'], + ['cannot confirm other', false, 'confirm'] ]).test('Default has no permission: %s', (_title, same, permission) => { const testUser = _cloneDeep(user); testUser.id = same ? interview.participant_id : interview.participant_id + 1; @@ -59,11 +50,7 @@ describe('default role', () => { }); describe('admin role', () => { - const user = { - id: 1, - uuid: 'arbitrary user', - is_admin: true - }; + const user = { id: 1, uuid: 'arbitrary user', is_admin: true }; each([ ['can read other', 'read'], @@ -71,7 +58,7 @@ describe('admin role', () => { ['can delete other', 'delete'], ['can create other', 'create'], ['can validate other', 'validate'], - ['can confirm other', 'confirm'], + ['can confirm other', 'confirm'] ]).test('%s', (_title, permission) => { const permissions = defineAbilitiesFor(user); expect(permissions.can(permission, subject(InterviewSubject, interview))).toEqual(true); @@ -85,16 +72,11 @@ describe('admin role', () => { expect(permissions.can('delete', InterviewsSubject)).toBeTruthy(); expect(permissions.can('validate', InterviewsSubject)).toBeTruthy(); expect(permissions.can('confirm', InterviewsSubject)).toBeTruthy(); - }) + }); }); describe('validator Lvl 1', () => { - const user = { - id: 1, - uuid: 'arbitrary user', - is_admin: false, - permissions: { [VALIDATOR_LVL1_ROLE]: true, [VALIDATOR_LVL2_ROLE]: false } - }; + const user = { id: 1, uuid: 'arbitrary user', is_admin: false, permissions: { [VALIDATOR_LVL1_ROLE]: true, [VALIDATOR_LVL2_ROLE]: false } }; each([ ['can read other', 'read', true], @@ -102,7 +84,7 @@ describe('validator Lvl 1', () => { ['cannot delete other', 'delete', false], ['cannot create other', 'create', false], ['can validate other', 'validate', true], - ['cannot confirm other', 'confirm', false], + ['cannot confirm other', 'confirm', false] ]).test('%s', (_title, permission, expectedResult) => { const permissions = defineAbilitiesFor(user); expect(permissions.can(permission, subject(InterviewSubject, interview))).toEqual(expectedResult); @@ -120,12 +102,7 @@ describe('validator Lvl 1', () => { }); describe('validator Lvl 2', () => { - const user = { - id: 1, - uuid: 'arbitrary user', - is_admin: false, - permissions: { [VALIDATOR_LVL1_ROLE]: false, [VALIDATOR_LVL2_ROLE]: true } - }; + const user = { id: 1, uuid: 'arbitrary user', is_admin: false, permissions: { [VALIDATOR_LVL1_ROLE]: false, [VALIDATOR_LVL2_ROLE]: true } }; each([ ['can read other', 'read', true], @@ -133,7 +110,7 @@ describe('validator Lvl 2', () => { ['cannot delete other', 'delete', false], ['cannot create other', 'create', false], ['can validate other', 'validate', true], - ['cannot confirm other', 'confirm', true], + ['cannot confirm other', 'confirm', true] ]).test('%s', (_title, permission, expectedResult) => { const permissions = defineAbilitiesFor(user); expect(permissions.can(permission, subject(InterviewSubject, interview))).toEqual(expectedResult); @@ -148,4 +125,4 @@ describe('validator Lvl 2', () => { expect(permissions.can('confirm', InterviewsSubject)).toBeTruthy(); expect(permissions.can('delete', InterviewsSubject)).toBeFalsy(); }); -}); \ No newline at end of file +}); diff --git a/packages/evolution-backend/src/services/auth/__tests__/userAuthorization.test.ts b/packages/evolution-backend/src/services/auth/__tests__/userAuthorization.test.ts index c547c7ab3..f35923458 100644 --- a/packages/evolution-backend/src/services/auth/__tests__/userAuthorization.test.ts +++ b/packages/evolution-backend/src/services/auth/__tests__/userAuthorization.test.ts @@ -6,7 +6,7 @@ */ import { NextFunction, Request, Response } from 'express'; import { v4 as uuidV4 } from 'uuid'; -import isAuthorized, { isUserAllowed } from '../userAuthorization'; +import isAuthorized, { isUserAllowed } from '../userAuthorization'; import each from 'jest-each'; import Interviews from '../../interviews/interviews'; import defineDefaultRoles from '../roleDefinition'; @@ -19,16 +19,13 @@ const nextFunction: NextFunction = jest.fn(); const mockUser = { id: 3, username: 'notAdmin', is_admin: false }; const mockAdmin = { id: 4, username: 'admin', is_admin: true }; -const mockGetInterviewByUuid = Interviews.getInterviewByUuid = jest.fn(); +const mockGetInterviewByUuid = (Interviews.getInterviewByUuid = jest.fn()); const interviewId = uuidV4(); beforeEach(() => { mockRequest = {}; - mockResponse = { - json: jest.fn(), - status: jest.fn().mockReturnThis() - }; + mockResponse = { json: jest.fn(), status: jest.fn().mockReturnThis() }; (nextFunction as any).mockClear(); mockGetInterviewByUuid.mockClear(); }); @@ -52,7 +49,14 @@ describe('User interview access', () => { ['Get params, Admin user, interview exists', mockAdmin, defaultPostParams, defaultPermissions, true, true], ['Get params, Admin user, interview with extra permissions', mockAdmin, defaultPostParams, [...defaultPermissions, 'validate'], true, true], ['Post and get params, identical, ok', mockAdmin, { ...defaultPostParams, ...defaultGetParams }, defaultPermissions, true, true], - ['Post and get params, not identical, not ok', mockAdmin, { ...defaultGetParams, body: { interviewId: uuidV4() } }, defaultPermissions, true, 400] + [ + 'Post and get params, not identical, not ok', + mockAdmin, + { ...defaultGetParams, body: { interviewId: uuidV4() } }, + defaultPermissions, + true, + 400 + ] ]).test('%s', async (_title, user, reqParams, requestedPermissions, retUndefined, expectedNextOrCode, is_active = true) => { mockRequest.user = user; const request = { ...mockRequest, ...reqParams }; @@ -75,7 +79,7 @@ describe('is User allowed', () => { each([ ['Normal user', mockUser, defaultPermissions, false], ['Admin user', mockAdmin, defaultPermissions, true], - ['Admin user, with extra permissions', mockAdmin, [...defaultPermissions, 'validate'], true], + ['Admin user, with extra permissions', mockAdmin, [...defaultPermissions, 'validate'], true] ]).test('%s', async (_title, user, requestedPermissions, expectedResult, is_active = true) => { const interview = { id: 3, diff --git a/packages/evolution-backend/src/services/interviews/__tests__/InterviewUtils.test.ts b/packages/evolution-backend/src/services/interviews/__tests__/InterviewUtils.test.ts index 9ff6cfd2e..97fd90b36 100644 --- a/packages/evolution-backend/src/services/interviews/__tests__/InterviewUtils.test.ts +++ b/packages/evolution-backend/src/services/interviews/__tests__/InterviewUtils.test.ts @@ -10,9 +10,7 @@ import moment from 'moment'; // Mock moment.unix to return a consistent value for testing jest.mock('moment', () => { - const mockMoment = jest.fn(() => ({ - unix: jest.fn(() => 1234567890) - })) as any; + const mockMoment = jest.fn(() => ({ unix: jest.fn(() => 1234567890) })) as any; mockMoment.unix = jest.fn(() => mockMoment()); return mockMoment; }); @@ -23,10 +21,9 @@ describe('mapResponseToCorrectedResponse', () => { response: { accessCode: '2222', newField: { foo: 'bar' } }, validations: { accessCode: { is_valid: false }, response: false } }; - const unsetPaths = [ 'response', 'validations' ]; + const unsetPaths = ['response', 'validations']; - const { valuesByPath: updatedValues, unsetPaths: updatedUnsetPaths } = - mapResponseToCorrectedResponse(valuesByPath, unsetPaths); + const { valuesByPath: updatedValues, unsetPaths: updatedUnsetPaths } = mapResponseToCorrectedResponse(valuesByPath, unsetPaths); expect(updatedValues).toEqual({ corrected_response: { accessCode: '2222', newField: { foo: 'bar' } }, @@ -42,12 +39,11 @@ describe('mapResponseToCorrectedResponse', () => { 'validations.accessCode.is_valid': false, 'validations.response': false, 'response2.notToBeModified': true, - 'response2': 'notToBeModified' + response2: 'notToBeModified' }; - const unsetPaths = [ 'response.accessCode', 'response2', 'response2.notToBeModified' ]; + const unsetPaths = ['response.accessCode', 'response2', 'response2.notToBeModified']; - const { valuesByPath: updatedValues, unsetPaths: updatedUnsetPaths } = - mapResponseToCorrectedResponse(valuesByPath, unsetPaths); + const { valuesByPath: updatedValues, unsetPaths: updatedUnsetPaths } = mapResponseToCorrectedResponse(valuesByPath, unsetPaths); expect(updatedValues).toEqual({ 'corrected_response.accessCode': '2222', @@ -55,7 +51,7 @@ describe('mapResponseToCorrectedResponse', () => { 'validations.accessCode.is_valid': false, 'validations.response': false, 'response2.notToBeModified': true, - 'response2': 'notToBeModified' + response2: 'notToBeModified' }); expect(updatedUnsetPaths).toEqual(['corrected_response.accessCode', 'response2', 'response2.notToBeModified']); }); @@ -65,25 +61,15 @@ describe('handleUserActionSideEffect', () => { test('sectionChange action without iteration context', () => { const interview = { id: 'interview-1', - response: { - _sections: { - _actions: [ - { section: 'section1', action: 'start', ts: 1234567880 } - ] - } - } + response: { _sections: { _actions: [{ section: 'section1', action: 'start', ts: 1234567880 }] } } } as any; const valuesByPath = {}; const userAction = { type: 'sectionChange' as const, - targetSection: { - sectionShortname: 'section2' - }, - previousSection: { - sectionShortname: 'section1' - } + targetSection: { sectionShortname: 'section2' }, + previousSection: { sectionShortname: 'section1' } }; const result = handleUserActionSideEffect(interview, _cloneDeep(valuesByPath), userAction); @@ -101,27 +87,15 @@ describe('handleUserActionSideEffect', () => { test('sectionChange action with iteration context', () => { const interview = { id: 'interview-1', - response: { - _sections: { - _actions: [ - { section: 'section1', iterationContext: ['0'], action: 'start', ts: 1234567880 } - ] - } - } + response: { _sections: { _actions: [{ section: 'section1', iterationContext: ['0'], action: 'start', ts: 1234567880 }] } } } as any; const valuesByPath = { 'response.someField': 'someValue', 'validations.someField': true }; const userAction = { type: 'sectionChange' as const, - targetSection: { - sectionShortname: 'section2', - iterationContext: ['person', '1'] - }, - previousSection: { - sectionShortname: 'section1', - iterationContext: ['0'] - } + targetSection: { sectionShortname: 'section2', iterationContext: ['person', '1'] }, + previousSection: { sectionShortname: 'section1', iterationContext: ['0'] } }; const result = handleUserActionSideEffect(interview, _cloneDeep(valuesByPath), userAction); @@ -139,21 +113,11 @@ describe('handleUserActionSideEffect', () => { }); test('widgetInteraction action (should not modify values)', () => { - const interview = { - id: 'interview-1', - response: {} - } as any; + const interview = { id: 'interview-1', response: {} } as any; - const valuesByPath = { - 'response.someField2': 'someValue' - }; + const valuesByPath = { 'response.someField2': 'someValue' }; - const userAction = { - type: 'widgetInteraction' as const, - widgetType: 'string', - path: 'response.someField', - value: 'newValue' - }; + const userAction = { type: 'widgetInteraction' as const, widgetType: 'string', path: 'response.someField', value: 'newValue' }; const result = handleUserActionSideEffect(interview, _cloneDeep(valuesByPath), userAction); @@ -162,18 +126,13 @@ describe('handleUserActionSideEffect', () => { }); test('sectionChange action with no previous section', () => { - const interview = { - id: 'interview-1', - response: {} - } as any; + const interview = { id: 'interview-1', response: {} } as any; const valuesByPath = {}; const userAction = { type: 'sectionChange' as const, - targetSection: { - sectionShortname: 'section1' - } + targetSection: { sectionShortname: 'section1' } // No previousSection }; @@ -181,30 +140,19 @@ describe('handleUserActionSideEffect', () => { expect(result).toEqual({ 'response._sections.section1._startedAt': 1234567890, - 'response._sections._actions': [ - { section: 'section1', action: 'start', ts: 1234567890 } - ] + 'response._sections._actions': [{ section: 'section1', action: 'start', ts: 1234567890 }] }); }); test('languageChange action', () => { - const interview = { - id: 'interview-1', - response: {} - } as any; + const interview = { id: 'interview-1', response: {} } as any; const valuesByPath = { 'response.someField': 'someValue' }; - const userAction = { - type: 'languageChange' as const, - language: 'fr' - }; + const userAction = { type: 'languageChange' as const, language: 'fr' }; const result = handleUserActionSideEffect(interview, _cloneDeep(valuesByPath), userAction); - expect(result).toEqual({ - ...valuesByPath, - 'response._language': 'fr' - }); + expect(result).toEqual({ ...valuesByPath, 'response._language': 'fr' }); }); }); diff --git a/packages/evolution-backend/src/services/interviews/__tests__/interview.test.ts b/packages/evolution-backend/src/services/interviews/__tests__/interview.test.ts index 769066f71..45f03ba7c 100644 --- a/packages/evolution-backend/src/services/interviews/__tests__/interview.test.ts +++ b/packages/evolution-backend/src/services/interviews/__tests__/interview.test.ts @@ -17,23 +17,16 @@ import { registerServerUpdateCallbacks } from '../../../config/projectConfig'; import TestUtils from 'chaire-lib-common/lib/test/TestUtils'; import { ParadataLoggingFunction } from '../../logging/paradataLogging'; -jest.mock('../../validations/serverValidation', () => - jest.fn() -); +jest.mock('../../validations/serverValidation', () => jest.fn()); const mockedServerValidate = serverValidate as jest.MockedFunction; mockedServerValidate.mockResolvedValue(true); -jest.mock('../serverFieldUpdate', () => - jest.fn() -); +jest.mock('../serverFieldUpdate', () => jest.fn()); const mockedServerUpdate = serverUpdate as jest.MockedFunction; // Do not use a single return value as the returned object can be mutated by calling function (and tests) mockedServerUpdate.mockImplementation(async () => [{}, undefined]); -jest.mock('../../../models/interviews.db.queries', () => ({ - update: jest.fn(), - getInterviewByUuid: jest.fn() -})); +jest.mock('../../../models/interviews.db.queries', () => ({ update: jest.fn(), getInterviewByUuid: jest.fn() })); const mockUpdate = interviewsQueries.update as jest.MockedFunction; const mockGetInterviewByUuid = interviewsQueries.getInterviewByUuid as jest.MockedFunction; @@ -46,13 +39,7 @@ const interviewAttributes: InterviewAttributes = { is_valid: true, is_active: true, is_completed: false, - response: { - accessCode: '11111', - testFields: { - fieldA: 'a', - fieldB: 'b' - } - } as any, + response: { accessCode: '11111', testFields: { fieldA: 'a', fieldB: 'b' } } as any, survey_id: 1, validations: {} }; @@ -63,14 +50,9 @@ beforeEach(() => { }); describe('Set interview fields', () => { - test('Test with valuesByPath with deep string path', () => { const testAttributes = _cloneDeep(interviewAttributes); - const valuesByPath = { - 'response.accessCode': '2222', - 'validations.accessCode': { is_valid: false }, - 'response.newField.foo': 'bar' - }; + const valuesByPath = { 'response.accessCode': '2222', 'validations.accessCode': { is_valid: false }, 'response.newField.foo': 'bar' }; setInterviewFields(testAttributes, { valuesByPath }); expect(testAttributes).toEqual({ uuid: interviewAttributes.uuid, @@ -79,27 +61,15 @@ describe('Set interview fields', () => { is_valid: interviewAttributes.is_valid, is_active: interviewAttributes.is_active, is_completed: interviewAttributes.is_completed, - response: { - accessCode: '2222', - testFields: { - fieldA: 'a', - fieldB: 'b' - }, - newField: { foo: 'bar' } - }, - validations: { - accessCode: { is_valid: false } - }, + response: { accessCode: '2222', testFields: { fieldA: 'a', fieldB: 'b' }, newField: { foo: 'bar' } }, + validations: { accessCode: { is_valid: false } }, survey_id: 1 }); }); test('Test with objects', () => { const testAttributes = _cloneDeep(interviewAttributes); - const valuesByPath = { - response: { accessCode: '2222', newField: { foo: 'bar' } }, - validations: { accessCode: { is_valid: false } } - }; + const valuesByPath = { response: { accessCode: '2222', newField: { foo: 'bar' } }, validations: { accessCode: { is_valid: false } } }; setInterviewFields(testAttributes, { valuesByPath }); expect(testAttributes).toEqual({ uuid: interviewAttributes.uuid, @@ -108,24 +78,16 @@ describe('Set interview fields', () => { is_valid: interviewAttributes.is_valid, is_active: interviewAttributes.is_active, is_completed: interviewAttributes.is_completed, - response: { - accessCode: '2222', - newField: { foo: 'bar' } - }, - validations: { - accessCode: { is_valid: false } - }, + response: { accessCode: '2222', newField: { foo: 'bar' } }, + validations: { accessCode: { is_valid: false } }, survey_id: 1 }); }); test('Test with valuesByPath and unsetPaths', () => { const testAttributes = _cloneDeep(interviewAttributes); - const valuesByPath = { - 'response.accessCode': '2222', - 'response.newField.foo': 'bar' - }; - const unsetPaths = [ 'response.testFields.fieldA' ]; + const valuesByPath = { 'response.accessCode': '2222', 'response.newField.foo': 'bar' }; + const unsetPaths = ['response.testFields.fieldA']; setInterviewFields(testAttributes, { valuesByPath, unsetPaths }); expect(testAttributes).toEqual({ uuid: interviewAttributes.uuid, @@ -134,13 +96,7 @@ describe('Set interview fields', () => { is_valid: interviewAttributes.is_valid, is_active: interviewAttributes.is_active, is_completed: interviewAttributes.is_completed, - response: { - accessCode: '2222', - testFields: { - fieldB: 'b' - }, - newField: { foo: 'bar' } - }, + response: { accessCode: '2222', testFields: { fieldB: 'b' }, newField: { foo: 'bar' } }, validations: {}, survey_id: 1 }); @@ -148,11 +104,8 @@ describe('Set interview fields', () => { test('Test with root values', () => { const testAttributes = _cloneDeep(interviewAttributes); - const valuesByPath = { - 'is_valid': !interviewAttributes.is_valid, - is_active: !interviewAttributes.is_active - }; - const unsetPaths = [ 'response' ]; + const valuesByPath = { is_valid: !interviewAttributes.is_valid, is_active: !interviewAttributes.is_active }; + const unsetPaths = ['response']; setInterviewFields(testAttributes, { valuesByPath, unsetPaths }); expect(testAttributes).toEqual({ uuid: interviewAttributes.uuid, @@ -165,16 +118,14 @@ describe('Set interview fields', () => { survey_id: 1 }); }); - }); describe('Update Interview', () => { - beforeEach(async () => { jest.clearAllMocks(); }); - test('With values by path', async() => { + test('With values by path', async () => { const testAttributes = _cloneDeep(interviewAttributes); const interview = await updateInterview(testAttributes, { valuesByPath: { 'response.foo': 'abc', 'response.testFields.fieldA': 'new' } }); expect(interview.interviewId).toEqual(testAttributes.uuid); @@ -191,11 +142,14 @@ describe('Update Interview', () => { expect(mockLog).not.toHaveBeenCalled(); }); - test('With values by path, and user action of type "buttonClick"', async() => { + test('With values by path, and user action of type "buttonClick"', async () => { // Prepare test data const testAttributes = _cloneDeep(interviewAttributes); const userAction = { type: 'buttonClick' as const, buttonId: 'test' }; - const interview = await updateInterview(testAttributes, { userAction, valuesByPath: { 'response.foo': 'abc', 'response.testFields.fieldA': 'new' } }); + const interview = await updateInterview(testAttributes, { + userAction, + valuesByPath: { 'response.foo': 'abc', 'response.testFields.fieldA': 'new' } + }); expect(interview.interviewId).toEqual(testAttributes.uuid); expect(interview.serverValidations).toEqual(true); expect(interviewsQueries.update).toHaveBeenCalledTimes(1); @@ -210,7 +164,7 @@ describe('Update Interview', () => { expect(mockLog).not.toHaveBeenCalled(); }); - test('With values by path, unset path and user action of type "widgetInteraction"', async() => { + test('With values by path, unset path and user action of type "widgetInteraction"', async () => { // Prepare test data const testAttributes = _cloneDeep(interviewAttributes); const valuesByPath = { 'response.foo': 'abc', 'corrected_response.foo': 'def' }; @@ -237,7 +191,7 @@ describe('Update Interview', () => { expect(mockLog).not.toHaveBeenCalled(); }); - test('Specifying fields to update', async() => { + test('Specifying fields to update', async () => { const testAttributes = _cloneDeep(interviewAttributes); const valuesByPath = { 'corrected_response.foo': 'abc', 'response.bar': 'abc' }; const interview = await updateInterview(testAttributes, { valuesByPath, fieldsToUpdate: ['corrected_response'] }); @@ -249,17 +203,15 @@ describe('Update Interview', () => { expect(mockedServerUpdate).toHaveBeenCalledTimes(1); expect(mockedServerUpdate).toHaveBeenCalledWith(testAttributes, [], valuesByPath, undefined, undefined); - const expectedUpdatedValues = { - corrected_response: { foo: 'abc' }, - }; + const expectedUpdatedValues = { corrected_response: { foo: 'abc' } }; expect(interviewsQueries.update).toHaveBeenCalledWith(testAttributes.uuid, expectedUpdatedValues); expect(mockLog).not.toHaveBeenCalled(); }); - test('With completed', async() => { + test('With completed', async () => { // Test with true value let testAttributes = _cloneDeep(interviewAttributes); - let valuesByPath = { 'is_completed': true }; + let valuesByPath = { is_completed: true }; let interview = await updateInterview(testAttributes, { valuesByPath, fieldsToUpdate: ['is_completed'] }); expect(interview.interviewId).toEqual(testAttributes.uuid); expect(interview.serverValidations).toEqual(true); @@ -273,7 +225,7 @@ describe('Update Interview', () => { // Test with false value testAttributes = _cloneDeep(interviewAttributes); - valuesByPath = { 'is_completed': false }; + valuesByPath = { is_completed: false }; interview = await updateInterview(testAttributes, { valuesByPath, fieldsToUpdate: ['is_completed'] }); expect(interview.interviewId).toEqual(testAttributes.uuid); expect(interview.serverValidations).toEqual(true); @@ -283,7 +235,7 @@ describe('Update Interview', () => { // Test with null value testAttributes = _cloneDeep(interviewAttributes); - valuesByPath = { 'is_completed': null } as any; + valuesByPath = { is_completed: null } as any; interview = await updateInterview(testAttributes, { valuesByPath, fieldsToUpdate: ['is_completed'] }); expect(interview.interviewId).toEqual(testAttributes.uuid); expect(interview.serverValidations).toEqual(true); @@ -293,10 +245,10 @@ describe('Update Interview', () => { expect(mockLog).not.toHaveBeenCalled(); }); - test('With valid', async() => { + test('With valid', async () => { // Test with true value let testAttributes = _cloneDeep(interviewAttributes); - let valuesByPath = { 'is_valid': true }; + let valuesByPath = { is_valid: true }; let interview = await updateInterview(testAttributes, { valuesByPath, fieldsToUpdate: ['is_valid'] }); expect(interview.interviewId).toEqual(testAttributes.uuid); expect(interview.serverValidations).toEqual(true); @@ -310,7 +262,7 @@ describe('Update Interview', () => { // Test with false value testAttributes = _cloneDeep(interviewAttributes); - valuesByPath = { 'is_valid': false }; + valuesByPath = { is_valid: false }; interview = await updateInterview(testAttributes, { valuesByPath, fieldsToUpdate: ['is_valid'] }); expect(interview.interviewId).toEqual(testAttributes.uuid); expect(interview.serverValidations).toEqual(true); @@ -320,7 +272,7 @@ describe('Update Interview', () => { // Test with null value testAttributes = _cloneDeep(interviewAttributes); - valuesByPath = { 'is_valid': null } as any; + valuesByPath = { is_valid: null } as any; interview = await updateInterview(testAttributes, { valuesByPath, fieldsToUpdate: ['is_valid'] }); expect(interview.interviewId).toEqual(testAttributes.uuid); expect(interview.serverValidations).toEqual(true); @@ -330,9 +282,9 @@ describe('Update Interview', () => { expect(mockLog).not.toHaveBeenCalled(); }); - test('With no field to be updated', async() => { + test('With no field to be updated', async () => { const testAttributes = _cloneDeep(interviewAttributes); - const interview = await updateInterview(testAttributes, { valuesByPath: { 'notAnInterviewField': 'abc' } }); + const interview = await updateInterview(testAttributes, { valuesByPath: { notAnInterviewField: 'abc' } }); expect(interview.interviewId).toEqual(testAttributes.uuid); expect(interview.serverValidations).toEqual(true); expect(interviewsQueries.update).toHaveBeenCalledTimes(1); @@ -345,18 +297,11 @@ describe('Update Interview', () => { expect(mockLog).not.toHaveBeenCalled(); }); - test('With invalid server validations', async() => { + test('With invalid server validations', async () => { const testAttributes = _cloneDeep(interviewAttributes); const valuesByPath = { 'response.foo': 'abc' }; // Prepare server validations - const serverValidations = { - foo: { - validations: [{ - validation: (_val) => true, - errorMessage: { fr: 'erreur', en: 'error' } - }] - } - }; + const serverValidations = { foo: { validations: [{ validation: (_val) => true, errorMessage: { fr: 'erreur', en: 'error' } }] } }; const serverValidationErrors = { foo: serverValidations.foo.validations[0].errorMessage }; mockedServerValidate.mockResolvedValueOnce(serverValidationErrors); const interview = await updateInterview(testAttributes, { valuesByPath, serverValidations }); @@ -378,14 +323,16 @@ describe('Update Interview', () => { expect(mockLog).not.toHaveBeenCalled(); }); - test('With server field updates', async() => { + test('With server field updates', async () => { const testAttributes = _cloneDeep(interviewAttributes); - const valuesByPath = { 'response.testFields.fieldB': 'abc', 'response.testFields.fieldA': 'clientVal', 'validations.testFields.fieldA': true }; + const valuesByPath = { + 'response.testFields.fieldB': 'abc', + 'response.testFields.fieldA': 'clientVal', + 'validations.testFields.fieldA': true + }; const unsetPaths = ['response.accessCode']; // Prepare server update response, callbacks won't be called, but we need an object - const updateCallbacks = [ - { field: 'testFields.fieldA', callback: jest.fn().mockResolvedValue({}) } - ]; + const updateCallbacks = [{ field: 'testFields.fieldA', callback: jest.fn().mockResolvedValue({}) }]; registerServerUpdateCallbacks(updateCallbacks); const updatedValuesByPath = { 'response.testFields.fieldB': 'newVal' }; mockedServerUpdate.mockResolvedValueOnce([updatedValuesByPath, undefined]); @@ -415,16 +362,18 @@ describe('Update Interview', () => { expect(mockLog).not.toHaveBeenCalled(); }); - test('With server field updates and execution callback, with paradata logging', async() => { + test('With server field updates and execution callback, with paradata logging', async () => { const deferredUpdateCallback = jest.fn(); const testAttributes = _cloneDeep(interviewAttributes); - const valuesByPath = { 'response.testFields.fieldB': 'abc', 'response.testFields.fieldA': 'clientVal', 'validations.testFields.fieldA': true }; + const valuesByPath = { + 'response.testFields.fieldB': 'abc', + 'response.testFields.fieldA': 'clientVal', + 'validations.testFields.fieldA': true + }; const unsetPaths = ['response.accessCode']; // Prepare server update response, callbacks won't be called, but we need an object - const updateCallbacks = [ - { field: 'testFields.fieldA', callback: jest.fn().mockResolvedValue({}) } - ]; + const updateCallbacks = [{ field: 'testFields.fieldA', callback: jest.fn().mockResolvedValue({}) }]; registerServerUpdateCallbacks(updateCallbacks); const updatedValuesByPath = { 'response.testFields.fieldB': 'newVal' }; const asyncUpdatedValuesByPath = { 'response.testFields.fieldC': 'valC' }; @@ -475,32 +424,24 @@ describe('Update Interview', () => { expect(interviewsQueries.update).toHaveBeenCalledWith(testAttributes.uuid, asyncExpectedUpdatedValues); expect(mockLog).toHaveBeenCalledTimes(3); // Should have been called once with server false with original updated data - expect(mockLog).toHaveBeenCalledWith({ - server: false, - valuesByPath, - unsetPaths - }); + expect(mockLog).toHaveBeenCalledWith({ server: false, valuesByPath, unsetPaths }); // Should have been called once with server flag and simple updated values by path - expect(mockLog).toHaveBeenCalledWith({ - server: true, - valuesByPath: updatedValuesByPath - }); + expect(mockLog).toHaveBeenCalledWith({ server: true, valuesByPath: updatedValuesByPath }); // Should have been called once with server flag and async values by path - expect(mockLog).toHaveBeenCalledWith({ - server: true, - valuesByPath: asyncUpdatedValuesByPath - }); + expect(mockLog).toHaveBeenCalledWith({ server: true, valuesByPath: asyncUpdatedValuesByPath }); }); - test('With server field updates and redirect URL', async() => { + test('With server field updates and redirect URL', async () => { const testRedirectURL = 'http://localhost:8080/test'; const testAttributes = _cloneDeep(interviewAttributes); - const valuesByPath = { 'response.testFields.fieldB': 'abc', 'response.testFields.fieldA': 'clientVal', 'validations.testFields.fieldA': true }; + const valuesByPath = { + 'response.testFields.fieldB': 'abc', + 'response.testFields.fieldA': 'clientVal', + 'validations.testFields.fieldA': true + }; const unsetPaths = ['response.accessCode']; // Prepare server update response, callbacks won't be called, but we need an object - const updateCallbacks = [ - { field: 'testFields.fieldA', callback: jest.fn().mockResolvedValue({}) } - ]; + const updateCallbacks = [{ field: 'testFields.fieldA', callback: jest.fn().mockResolvedValue({}) }]; registerServerUpdateCallbacks(updateCallbacks); const updatedValuesByPath = { 'response.testFields.fieldB': 'newVal' }; mockedServerUpdate.mockResolvedValueOnce([updatedValuesByPath, testRedirectURL]); @@ -530,7 +471,7 @@ describe('Update Interview', () => { expect(mockLog).not.toHaveBeenCalled(); }); - test('With logs', async() => { + test('With logs', async () => { try { // Prepare data to update const updatedAt = 1234; // Update timestamp @@ -548,7 +489,7 @@ describe('Update Interview', () => { const expectedUpdatedValues = { response: _cloneDeep(interviewAttributes.response) as any, - validations: _cloneDeep(interviewAttributes.validations), + validations: _cloneDeep(interviewAttributes.validations) }; expectedUpdatedValues.response.foo = 'abc'; expectedUpdatedValues.response._updatedAt = updatedAt; @@ -559,7 +500,7 @@ describe('Update Interview', () => { } }); - test('With default logs', async() => { + test('With default logs', async () => { try { // Prepare data to update const updatedAt = 1234; // Update timestamp @@ -588,42 +529,34 @@ describe('Update Interview', () => { } }); - test('Database error', async() => { + test('Database error', async () => { const testAttributes = _cloneDeep(interviewAttributes); (interviewsQueries.update as any).mockRejectedValueOnce('fake error'); let error: unknown = undefined; try { - await updateInterview(testAttributes, - { - valuesByPath: { 'response.foo': 'abc', 'response.testFields.fieldA': 'new' } - } - ); + await updateInterview(testAttributes, { valuesByPath: { 'response.foo': 'abc', 'response.testFields.fieldA': 'new' } }); } catch (err) { error = err; } expect(error).toBeDefined(); - }); - test('Database error and logging', async() => { + test('Database error and logging', async () => { const testAttributes = _cloneDeep(interviewAttributes); (interviewsQueries.update as any).mockRejectedValueOnce('fake error'); let error: unknown = undefined; try { - await updateInterview(testAttributes, - { - logUpdate: mockLog, - valuesByPath: { 'response.foo': 'abc', 'response.testFields.fieldA': 'new' } - } - ); + await updateInterview(testAttributes, { + logUpdate: mockLog, + valuesByPath: { 'response.foo': 'abc', 'response.testFields.fieldA': 'new' } + }); } catch (err) { error = err; } expect(error).toBeDefined(); expect(mockLog).not.toHaveBeenCalled(); - }); test('With values by path to sanitize as top level strings', async () => { @@ -633,7 +566,9 @@ describe('Update Interview', () => { const sanitizedValue = 'Hover over me'; // Execute interview update - const { interviewId, serverValuesByPath, serverValidations } = await updateInterview(testAttributes, { valuesByPath: { 'response.foo': scriptInjectionString } }); + const { interviewId, serverValuesByPath, serverValidations } = await updateInterview(testAttributes, { + valuesByPath: { 'response.foo': scriptInjectionString } + }); // Validate results expect(interviewId).toEqual(testAttributes.uuid); @@ -658,7 +593,9 @@ describe('Update Interview', () => { const sanitizedObject = { someNumber: 42, someString: 'Hover over me' }; // Execute interview update - const { interviewId, serverValuesByPath, serverValidations } = await updateInterview(testAttributes, { valuesByPath: { 'response.foo': 'abc', 'response.bar': objectAsAnswer } }); + const { interviewId, serverValuesByPath, serverValidations } = await updateInterview(testAttributes, { + valuesByPath: { 'response.foo': 'abc', 'response.bar': objectAsAnswer } + }); // Validate results expect(interviewId).toEqual(testAttributes.uuid); @@ -681,11 +618,13 @@ describe('Update Interview', () => { const testAttributes = _cloneDeep(interviewAttributes); const scriptInjectionString = '
Hover over me
'; const sanitizedValue = 'Hover over me'; - const arrayResponse = [scriptInjectionString, { name: scriptInjectionString}, 42]; - const sanitizedArray = [sanitizedValue, { name: sanitizedValue}, 42]; + const arrayResponse = [scriptInjectionString, { name: scriptInjectionString }, 42]; + const sanitizedArray = [sanitizedValue, { name: sanitizedValue }, 42]; // Execute interview update - const { interviewId, serverValuesByPath, serverValidations } = await updateInterview(testAttributes, { valuesByPath: { 'response.foo': arrayResponse } }); + const { interviewId, serverValuesByPath, serverValidations } = await updateInterview(testAttributes, { + valuesByPath: { 'response.foo': arrayResponse } + }); // Validate results expect(interviewId).toEqual(testAttributes.uuid); @@ -711,7 +650,9 @@ describe('Update Interview', () => { mockedServerUpdate.mockResolvedValueOnce([serverUpdatedValuesByPath, undefined]); // Execute interview update - const { interviewId, serverValuesByPath, serverValidations } = await updateInterview(testAttributes, { valuesByPath: { 'response.foo': scriptInjectionString } }); + const { interviewId, serverValuesByPath, serverValidations } = await updateInterview(testAttributes, { + valuesByPath: { 'response.foo': scriptInjectionString } + }); // Validate results expect(interviewId).toEqual(testAttributes.uuid); @@ -727,16 +668,14 @@ describe('Update Interview', () => { expect(mockLog).not.toHaveBeenCalled(); expect(serverValuesByPath).toEqual(serverUpdatedValuesByPath); }); - }); describe('copyResponseToCorrectedResponse', () => { - beforeEach(async () => { mockUpdate.mockClear(); }); - test('First copy', async() => { + test('First copy', async () => { const testAttributes = _cloneDeep(interviewAttributes); expect(testAttributes.corrected_response).not.toBeDefined(); @@ -747,45 +686,36 @@ describe('copyResponseToCorrectedResponse', () => { expect(mockUpdate).toHaveBeenCalledWith(testAttributes.uuid, { corrected_response: testAttributes.corrected_response }); }); - test('Copy with existing validation data', async() => { + test('Copy with existing validation data', async () => { const testAttributes = _cloneDeep(interviewAttributes); const originalTimestamp = moment('2023-09-12 15:02:00').unix(); - testAttributes.corrected_response = { + ((testAttributes.corrected_response = { _correctedResponseCopiedAt: originalTimestamp, accessCode: '2222', - testFields: { - fieldA: 'test', - fieldB: 'changed' - } - } as any, - - await copyResponseToCorrectedResponse(testAttributes); + testFields: { fieldA: 'test', fieldB: 'changed' } + } as any), + await copyResponseToCorrectedResponse(testAttributes)); expect(testAttributes.corrected_response).toEqual(expect.objectContaining(testAttributes.response)); expect(testAttributes.corrected_response?._correctedResponseCopiedAt).toBeDefined(); expect(testAttributes.corrected_response?._correctedResponseCopiedAt).not.toEqual(originalTimestamp); expect(mockUpdate).toHaveBeenCalledWith(testAttributes.uuid, { corrected_response: testAttributes.corrected_response }); }); - test('Copy with existing and comment', async() => { + test('Copy with existing and comment', async () => { const testAttributes = _cloneDeep(interviewAttributes); const originalTimestamp = moment('2023-09-12 15:02:00').unix(); const validationComment = 'This was commented previously'; - testAttributes.corrected_response = { + ((testAttributes.corrected_response = { _correctedResponseCopiedAt: originalTimestamp, accessCode: '2222', - testFields: { - fieldA: 'test', - fieldB: 'changed' - }, + testFields: { fieldA: 'test', fieldB: 'changed' }, _validationComment: validationComment - } as any, - - await copyResponseToCorrectedResponse(testAttributes); + } as any), + await copyResponseToCorrectedResponse(testAttributes)); expect(testAttributes.corrected_response).toEqual(expect.objectContaining(testAttributes.response)); expect(testAttributes.corrected_response?._validationComment).toEqual(validationComment); expect(testAttributes.corrected_response?._correctedResponseCopiedAt).toBeDefined(); expect(mockUpdate).toHaveBeenCalledWith(testAttributes.uuid, { corrected_response: testAttributes.corrected_response }); }); - }); diff --git a/packages/evolution-backend/src/services/interviews/__tests__/interviews.test.ts b/packages/evolution-backend/src/services/interviews/__tests__/interviews.test.ts index d98de203d..44f051efd 100644 --- a/packages/evolution-backend/src/services/interviews/__tests__/interviews.test.ts +++ b/packages/evolution-backend/src/services/interviews/__tests__/interviews.test.ts @@ -24,21 +24,15 @@ jest.mock('../../../models/interviews.db.queries', () => ({ getValidationAuditStats: jest.fn() })); -jest.mock('../../../models/interviewsAccesses.db.queries', () => ({ - statEditingUsers: jest.fn() -})); +jest.mock('../../../models/interviewsAccesses.db.queries', () => ({ statEditingUsers: jest.fn() })); const mockDbCreate = interviewsQueries.create as jest.MockedFunction; const mockDbGetByUuid = interviewsQueries.getInterviewByUuid as jest.MockedFunction; const mockStatEditingUsers = interviewsAccessesQueries.statEditingUsers as jest.MockedFunction; -jest.mock('../interview', () => ({ - updateInterview: jest.fn() -})); +jest.mock('../interview', () => ({ updateInterview: jest.fn() })); const mockInterviewUpdate = updateInterview as jest.MockedFunction; -jest.mock('../../logging/paradataLogging', () => ({ - getParadataLoggingFunction: jest.fn().mockReturnValue(undefined) -})); +jest.mock('../../logging/paradataLogging', () => ({ getParadataLoggingFunction: jest.fn().mockReturnValue(undefined) })); const mockGetParadataLogFunction = getParadataLoggingFunction as jest.MockedFunction; // Create 10 interviews, half are active @@ -61,7 +55,7 @@ mockDbGetByUuid.mockResolvedValue(returnedInterview as InterviewAttributes); mockDbCreate.mockImplementation(async (newObject: Partial, returning: string | string[] = 'id') => { const returnFields = typeof returning === 'string' ? [returning] : returning; const ret: Partial = {}; - returnFields.forEach((field) => ret[field] = newObject[field] || returnedInterview[field]); + returnFields.forEach((field) => (ret[field] = newObject[field] || returnedInterview[field])); return ret; }); (interviewsQueries.getList as any).mockResolvedValue({ interviews: allInterviews, totalCount: allInterviews.length }); @@ -75,20 +69,18 @@ describe('Find by access code', () => { (interviewsQueries.findByResponse as any).mockClear(); }); - test('Get all users', async() => { - + test('Get all users', async () => { const response = await Interviews.findByAccessCode(validCode); expect(interviewsQueries.findByResponse).toHaveBeenCalledTimes(1); expect(interviewsQueries.findByResponse).toHaveBeenCalledWith({ accessCode: validCode }); expect(response.length).toBeGreaterThan(0); }); - test('Invalid access code', async() => { + test('Invalid access code', async () => { const response = await Interviews.findByAccessCode('not an access code'); expect(interviewsQueries.findByResponse).toHaveBeenCalledTimes(0); expect(response).toEqual([]); }); - }); describe('Get interview by interview ID', () => { @@ -98,14 +90,14 @@ describe('Get interview by interview ID', () => { (interviewsQueries.getInterviewByUuid as any).mockClear(); }); - test('Get interview', async() => { + test('Get interview', async () => { const interviewUserId = await Interviews.getInterviewByUuid(interviewId); expect(interviewsQueries.getInterviewByUuid).toHaveBeenCalledTimes(1); expect(interviewsQueries.getInterviewByUuid).toHaveBeenCalledWith(interviewId); expect(interviewUserId).toEqual(returnedInterview); }); - test('Interview not found', async() => { + test('Interview not found', async () => { (interviewsQueries.getInterviewByUuid as any).mockResolvedValue(undefined); const interviewUserId = await Interviews.getInterviewByUuid(interviewId); expect(interviewsQueries.getInterviewByUuid).toHaveBeenCalledTimes(1); @@ -113,18 +105,17 @@ describe('Get interview by interview ID', () => { expect(interviewUserId).toBeUndefined(); }); - test('Invalid uuid', async() => { + test('Invalid uuid', async () => { const interviewUserId = await Interviews.getInterviewByUuid('not a valid uuid'); expect(interviewsQueries.getInterviewByUuid).not.toHaveBeenCalled(); expect(interviewUserId).toBeUndefined(); }); - test('Invalid data', async() => { + test('Invalid data', async () => { const interviewUserId = await Interviews.getInterviewByUuid({ foo: 'bar' } as any); expect(interviewsQueries.getInterviewByUuid).not.toHaveBeenCalled(); expect(interviewUserId).toBeUndefined(); }); - }); describe('Get interview by userId', () => { @@ -134,14 +125,14 @@ describe('Get interview by userId', () => { (interviewsQueries.getUserInterview as any).mockClear(); }); - test('Get interview', async() => { + test('Get interview', async () => { const interview = await Interviews.getUserInterview(userId); expect(interviewsQueries.getUserInterview).toHaveBeenCalledTimes(1); expect(interviewsQueries.getUserInterview).toHaveBeenCalledWith(userId); expect(interview).toEqual(returnedInterview); }); - test('Interview not found', async() => { + test('Interview not found', async () => { (interviewsQueries.getUserInterview as any).mockResolvedValue(undefined); const interview = await Interviews.getUserInterview(userId); expect(interviewsQueries.getUserInterview).toHaveBeenCalledTimes(1); @@ -149,7 +140,7 @@ describe('Get interview by userId', () => { expect(interview).toBeUndefined(); }); - test('Exception thrown by db query', async() => { + test('Exception thrown by db query', async () => { const error = 'Fake database error'; (interviewsQueries.getUserInterview as any).mockRejectedValueOnce(error); @@ -161,60 +152,45 @@ describe('Get interview by userId', () => { } expect(thrownError).toEqual(error); }); - }); describe('Create interviews', () => { - const participantId = 20; let createdInterview: InterviewAttributes | undefined = undefined; beforeEach(() => { jest.clearAllMocks(); mockDbCreate.mockImplementationOnce(async (interview, returning = 'uuid') => { - const newInterview = { - ...interview, - uuid: interview.uuid ? interview.uuid : uuidV4() - }; + const newInterview = { ...interview, uuid: interview.uuid ? interview.uuid : uuidV4() }; createdInterview = newInterview as InterviewAttributes; const returnInterview = {}; const returningArr = typeof returning === 'string' ? [returning] : returning; - returningArr?.forEach((field) => returnInterview[field] = newInterview[field]); + returningArr?.forEach((field) => (returnInterview[field] = newInterview[field])); return returnInterview; }); }); - test('Create with empty response', async() => { - + test('Create with empty response', async () => { const newInterview = await Interviews.createInterviewForUser(participantId, {}); expect(mockDbCreate).toHaveBeenCalledTimes(1); - expect(mockDbCreate).toHaveBeenCalledWith({ - participant_id: participantId, - response: { _startedAt: expect.anything() }, - is_active: true, - validations: {} - }, 'uuid'); + expect(mockDbCreate).toHaveBeenCalledWith( + { participant_id: participantId, response: { _startedAt: expect.anything() }, is_active: true, validations: {} }, + 'uuid' + ); expect(newInterview).toEqual({ uuid: expect.anything() }); expect(mockDbGetByUuid).not.toHaveBeenCalled(); expect(mockInterviewUpdate).not.toHaveBeenCalled(); }); - test('Create with default response', async() => { + test('Create with default response', async () => { mockDbGetByUuid.mockImplementationOnce(async () => createdInterview); - const response = { - foo: 'bar', - fooObj: { - baz: 'test' - } - }; + const response = { foo: 'bar', fooObj: { baz: 'test' } }; const newInterview = await Interviews.createInterviewForUser(participantId, response); expect(mockDbCreate).toHaveBeenCalledTimes(1); - expect(mockDbCreate).toHaveBeenCalledWith({ - participant_id: participantId, - response: { ...response, _startedAt: expect.anything() }, - is_active: true, - validations: {} - }, 'uuid'); + expect(mockDbCreate).toHaveBeenCalledWith( + { participant_id: participantId, response: { ...response, _startedAt: expect.anything() }, is_active: true, validations: {} }, + 'uuid' + ); expect(newInterview).toEqual({ uuid: expect.anything() }); expect(mockDbGetByUuid).toHaveBeenCalledTimes(1); expect(mockDbGetByUuid).toHaveBeenCalledWith(newInterview.uuid); @@ -224,32 +200,28 @@ describe('Create interviews', () => { }); }); - test('Create and return single other field', async() => { + test('Create and return single other field', async () => { const userId = 1; const newInterview = await Interviews.createInterviewForUser(participantId, {}, userId, 'participant_id'); expect(mockDbCreate).toHaveBeenCalledTimes(1); - expect(mockDbCreate).toHaveBeenCalledWith({ - participant_id: participantId, - response: { _startedAt: expect.anything() }, - is_active: true, - validations: {} - }, 'participant_id'); + expect(mockDbCreate).toHaveBeenCalledWith( + { participant_id: participantId, response: { _startedAt: expect.anything() }, is_active: true, validations: {} }, + 'participant_id' + ); expect(newInterview).toEqual({ participant_id: participantId }); expect(mockDbGetByUuid).not.toHaveBeenCalled(); expect(mockInterviewUpdate).not.toHaveBeenCalled(); }); - test('Create and return many other field', async() => { + test('Create and return many other field', async () => { const initialTimeStamp = moment().unix(); const returningFields = ['participant_id', 'response', 'uuid']; const newInterview = await Interviews.createInterviewForUser(participantId, {}, undefined, returningFields); expect(mockDbCreate).toHaveBeenCalledTimes(1); - expect(mockDbCreate).toHaveBeenCalledWith({ - participant_id: participantId, - response: { _startedAt: expect.anything() }, - is_active: true, - validations: {} - }, returningFields); + expect(mockDbCreate).toHaveBeenCalledWith( + { participant_id: participantId, response: { _startedAt: expect.anything() }, is_active: true, validations: {} }, + returningFields + ); expect(newInterview).toEqual({ participant_id: participantId, uuid: expect.anything(), response: { _startedAt: expect.anything() } }); expect(mockDbGetByUuid).not.toHaveBeenCalled(); expect(mockInterviewUpdate).not.toHaveBeenCalled(); @@ -258,7 +230,7 @@ describe('Create interviews', () => { expect((newInterview.response as any)._startedAt).toBeGreaterThanOrEqual(initialTimeStamp); }); - test('Create with log update', async() => { + test('Create with log update', async () => { mockDbGetByUuid.mockImplementationOnce(async () => createdInterview); // Return a log function and make sure it is passed to the update const logFunction = jest.fn(); @@ -267,12 +239,10 @@ describe('Create interviews', () => { const newInterview = await Interviews.createInterviewForUser(participantId, { initial: 'value' }, userId); expect(mockDbCreate).toHaveBeenCalledTimes(1); - expect(mockDbCreate).toHaveBeenCalledWith({ - participant_id: participantId, - response: { _startedAt: expect.anything(), initial: 'value' }, - is_active: true, - validations: {} - }, 'uuid'); + expect(mockDbCreate).toHaveBeenCalledWith( + { participant_id: participantId, response: { _startedAt: expect.anything(), initial: 'value' }, is_active: true, validations: {} }, + 'uuid' + ); expect(newInterview).toEqual({ uuid: expect.anything() }); expect(mockDbGetByUuid).toHaveBeenCalledTimes(1); expect(mockDbGetByUuid).toHaveBeenCalledWith(newInterview.uuid); @@ -284,45 +254,30 @@ describe('Create interviews', () => { fieldsToUpdate: ['response'] }); }); - }); describe('Get all matching', () => { - beforeEach(() => { (interviewsQueries.getList as any).mockClear(); }); - test('Empty parameters', async() => { + test('Empty parameters', async () => { await Interviews.getAllMatching(); expect(interviewsQueries.getList).toHaveBeenCalledTimes(1); - expect(interviewsQueries.getList).toHaveBeenCalledWith({ - filters: {}, - pageIndex: 0, - pageSize: -1 - }); + expect(interviewsQueries.getList).toHaveBeenCalledWith({ filters: {}, pageIndex: 0, pageSize: -1 }); }); - test('Page index and page size', async() => { + test('Page index and page size', async () => { const pageIndex = 3; const pageSize = 10; - await Interviews.getAllMatching({ - pageIndex, - pageSize, - }); + await Interviews.getAllMatching({ pageIndex, pageSize }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(1); - expect(interviewsQueries.getList).toHaveBeenCalledWith({ - filters: {}, - pageIndex, - pageSize - }); + expect(interviewsQueries.getList).toHaveBeenCalledWith({ filters: {}, pageIndex, pageSize }); }); - test('Filters: updatedAt and others', async() => { + test('Filters: updatedAt and others', async () => { const updatedAt = 12300000; - await Interviews.getAllMatching({ - updatedAt - }); + await Interviews.getAllMatching({ updatedAt }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(1); expect(interviewsQueries.getList).toHaveBeenCalledWith({ filters: { updated_at: { value: updatedAt, op: 'gte' } }, @@ -331,72 +286,36 @@ describe('Get all matching', () => { }); // Updated_at is 0, should not be sent to the query - await Interviews.getAllMatching({ - updatedAt: 0 - }); + await Interviews.getAllMatching({ updatedAt: 0 }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(2); - expect(interviewsQueries.getList).toHaveBeenLastCalledWith({ - filters: {}, - pageIndex: 0, - pageSize: -1 - }); + expect(interviewsQueries.getList).toHaveBeenLastCalledWith({ filters: {}, pageIndex: 0, pageSize: -1 }); }); - test('Various isValid filter values', async() => { + test('Various isValid filter values', async () => { const pageIndex = 3; const pageSize = 10; // isValid: valid - await Interviews.getAllMatching({ - pageIndex, - pageSize, - filter: { is_valid: 'valid' } - }); + await Interviews.getAllMatching({ pageIndex, pageSize, filter: { is_valid: 'valid' } }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(1); - expect(interviewsQueries.getList).toHaveBeenCalledWith({ - filters: { is_valid: { value: true, op: 'eq' } }, - pageIndex, - pageSize - }); + expect(interviewsQueries.getList).toHaveBeenCalledWith({ filters: { is_valid: { value: true, op: 'eq' } }, pageIndex, pageSize }); // isValid: all - await Interviews.getAllMatching({ - pageIndex, - pageSize, - filter: { is_valid: 'all' } - }); + await Interviews.getAllMatching({ pageIndex, pageSize, filter: { is_valid: 'all' } }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(2); - expect(interviewsQueries.getList).toHaveBeenLastCalledWith({ - filters: { }, - pageIndex, - pageSize - }); + expect(interviewsQueries.getList).toHaveBeenLastCalledWith({ filters: {}, pageIndex, pageSize }); // isValid: invalid - await Interviews.getAllMatching({ - filter: { is_valid: 'invalid' } - }); + await Interviews.getAllMatching({ filter: { is_valid: 'invalid' } }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(3); - expect(interviewsQueries.getList).toHaveBeenLastCalledWith({ - filters: { is_valid: { value: false, op: 'eq' } }, - pageIndex: 0, - pageSize: -1 - }); + expect(interviewsQueries.getList).toHaveBeenLastCalledWith({ filters: { is_valid: { value: false, op: 'eq' } }, pageIndex: 0, pageSize: -1 }); // isValid: notValidated - await Interviews.getAllMatching({ - filter: { is_valid: 'notValidated' } - }); + await Interviews.getAllMatching({ filter: { is_valid: 'notValidated' } }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(4); - expect(interviewsQueries.getList).toHaveBeenLastCalledWith({ - filters: { is_valid: { value: null, op: 'eq' } }, - pageIndex: 0, - pageSize: -1 - }); + expect(interviewsQueries.getList).toHaveBeenLastCalledWith({ filters: { is_valid: { value: null, op: 'eq' } }, pageIndex: 0, pageSize: -1 }); // isValid: notInvalid - await Interviews.getAllMatching({ - filter: { is_valid: 'notInvalid' } - }); + await Interviews.getAllMatching({ filter: { is_valid: 'notInvalid' } }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(5); expect(interviewsQueries.getList).toHaveBeenLastCalledWith({ filters: { is_valid: { value: false, op: 'not' } }, @@ -405,66 +324,38 @@ describe('Get all matching', () => { }); }); - test('Only page size', async() => { + test('Only page size', async () => { const pageSize = 10; // isValid: valid - await Interviews.getAllMatching({ - pageSize, - }); + await Interviews.getAllMatching({ pageSize }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(1); - expect(interviewsQueries.getList).toHaveBeenCalledWith({ - filters: {}, - pageIndex: 0, - pageSize - }); + expect(interviewsQueries.getList).toHaveBeenCalledWith({ filters: {}, pageIndex: 0, pageSize }); }); - test('Only page index', async() => { + test('Only page index', async () => { const pageIndex = 3; // isValid: valid - await Interviews.getAllMatching({ - pageIndex, - }); + await Interviews.getAllMatching({ pageIndex }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(1); - expect(interviewsQueries.getList).toHaveBeenCalledWith({ - filters: {}, - pageIndex, - pageSize: -1 - }); + expect(interviewsQueries.getList).toHaveBeenCalledWith({ filters: {}, pageIndex, pageSize: -1 }); }); - test('With sort', async() => { + test('With sort', async () => { const pageIndex = 3; // isValid: valid - await Interviews.getAllMatching({ - pageIndex, - sort: ['uuid'] - }); + await Interviews.getAllMatching({ pageIndex, sort: ['uuid'] }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(1); - expect(interviewsQueries.getList).toHaveBeenCalledWith({ - filters: {}, - pageIndex, - pageSize: -1, - sort: ['uuid'] - }); + expect(interviewsQueries.getList).toHaveBeenCalledWith({ filters: {}, pageIndex, pageSize: -1, sort: ['uuid'] }); }); - test('Filters: various filters', async() => { + test('Filters: various filters', async () => { // string audit - await Interviews.getAllMatching({ - filter: { audits: 'myAudit' } - }); + await Interviews.getAllMatching({ filter: { audits: 'myAudit' } }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(1); - expect(interviewsQueries.getList).toHaveBeenCalledWith({ - filters: { audits: { value: 'myAudit' } }, - pageIndex: 0, - pageSize: -1 - }); + expect(interviewsQueries.getList).toHaveBeenCalledWith({ filters: { audits: { value: 'myAudit' } }, pageIndex: 0, pageSize: -1 }); // array of string audit - await Interviews.getAllMatching({ - filter: { audits: ['myAudit1', 'myAudit2'] } - }); + await Interviews.getAllMatching({ filter: { audits: ['myAudit1', 'myAudit2'] } }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(2); expect(interviewsQueries.getList).toHaveBeenCalledWith({ filters: { audits: { value: ['myAudit1', 'myAudit2'] } }, @@ -473,67 +364,42 @@ describe('Get all matching', () => { }); // object filter - await Interviews.getAllMatching({ - filter: { audits: { value: 'myAudit', op: 'like' } } - }); + await Interviews.getAllMatching({ filter: { audits: { value: 'myAudit', op: 'like' } } }); expect(interviewsQueries.getList).toHaveBeenCalledTimes(3); - expect(interviewsQueries.getList).toHaveBeenCalledWith({ - filters: { audits: { value: 'myAudit', op: 'like' } }, - pageIndex: 0, - pageSize: -1 - }); - + expect(interviewsQueries.getList).toHaveBeenCalledWith({ filters: { audits: { value: 'myAudit', op: 'like' } }, pageIndex: 0, pageSize: -1 }); }); - }); describe('Get Validation errors', () => { - beforeEach(() => { (interviewsQueries.getValidationAuditStats as any).mockClear(); }); - test('Empty parameters', async() => { + test('Empty parameters', async () => { await Interviews.getValidationAuditStats(); expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledTimes(1); - expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledWith({ - filters: {} - }); + expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledWith({ filters: {} }); }); - test('Various isValid filter values', async() => { + test('Various isValid filter values', async () => { // isValid: valid - await Interviews.getValidationAuditStats({ - filter: { is_valid: 'valid' } - }); + await Interviews.getValidationAuditStats({ filter: { is_valid: 'valid' } }); expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledTimes(1); - expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledWith({ - filters: { is_valid: { value: true, op: 'eq' } } - }); + expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledWith({ filters: { is_valid: { value: true, op: 'eq' } } }); // isValid: all - await Interviews.getValidationAuditStats({ - filter: { is_valid: 'all' } - }); + await Interviews.getValidationAuditStats({ filter: { is_valid: 'all' } }); expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledTimes(2); - expect(interviewsQueries.getValidationAuditStats).toHaveBeenLastCalledWith({ - filters: { } - }); + expect(interviewsQueries.getValidationAuditStats).toHaveBeenLastCalledWith({ filters: {} }); }); - test('Filters: various filters', async() => { - await Interviews.getValidationAuditStats({ - filter: { test: 'foo' } - }); + test('Filters: various filters', async () => { + await Interviews.getValidationAuditStats({ filter: { test: 'foo' } }); expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledTimes(1); - expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledWith({ - filters: { test: { value: 'foo' } } - }); + expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledWith({ filters: { test: { value: 'foo' } } }); // Updated_at is 0, should not be sent to the query - await Interviews.getValidationAuditStats({ - filter: { test: 'foo', other: { value: 'bar', op: 'gte' } } - }); + await Interviews.getValidationAuditStats({ filter: { test: 'foo', other: { value: 'bar', op: 'gte' } } }); expect(interviewsQueries.getValidationAuditStats).toHaveBeenCalledTimes(2); expect(interviewsQueries.getValidationAuditStats).toHaveBeenLastCalledWith({ filters: { test: { value: 'foo' }, other: { value: 'bar', op: 'gte' } } @@ -542,29 +408,42 @@ describe('Get Validation errors', () => { }); describe('Reset interview', () => { - test('Test with bad confirmation parameter', async () => { let exception: unknown = undefined; try { await Interviews.resetInterviews('confirm'); - } catch(error) { + } catch (error) { exception = error; } expect(exception).toBeDefined(); }); - }); describe('Stat editing users', () => { - beforeEach(() => { mockStatEditingUsers.mockClear(); }); test('Test with correct answer', async () => { const userStats = [ - { email: 'foo@bar.com', interview_id: 12, user_id: 3, for_validation: false, update_count: 10, created_at: '2023-06-28', updated_at: '2023-06-28' }, - { email: 'a@b.c', interview_id: 12, user_id: 3, for_validation: false, update_count: 2, created_at: '2023-06-28', updated_at: '2023-06-28' } + { + email: 'foo@bar.com', + interview_id: 12, + user_id: 3, + for_validation: false, + update_count: 10, + created_at: '2023-06-28', + updated_at: '2023-06-28' + }, + { + email: 'a@b.c', + interview_id: 12, + user_id: 3, + for_validation: false, + update_count: 2, + created_at: '2023-06-28', + updated_at: '2023-06-28' + } ]; mockStatEditingUsers.mockResolvedValueOnce(userStats); const stats = await Interviews.statEditingUsers(); @@ -574,13 +453,29 @@ describe('Stat editing users', () => { test('Test with permissions', async () => { const userStats = [ - { email: 'foo@bar.com', interview_id: 12, user_id: 3, for_validation: false, update_count: 10, created_at: '2023-06-28', updated_at: '2023-06-28' }, - { email: 'a@b.c', interview_id: 12, user_id: 3, for_validation: false, update_count: 2, created_at: '2023-06-28', updated_at: '2023-06-28' } + { + email: 'foo@bar.com', + interview_id: 12, + user_id: 3, + for_validation: false, + update_count: 10, + created_at: '2023-06-28', + updated_at: '2023-06-28' + }, + { + email: 'a@b.c', + interview_id: 12, + user_id: 3, + for_validation: false, + update_count: 2, + created_at: '2023-06-28', + updated_at: '2023-06-28' + } ]; mockStatEditingUsers.mockResolvedValueOnce(userStats); - const stats = await Interviews.statEditingUsers({ permissions: [ 'role1', 'role2' ] }); + const stats = await Interviews.statEditingUsers({ permissions: ['role1', 'role2'] }); expect(stats).toEqual(userStats); - expect(mockStatEditingUsers).toHaveBeenCalledWith({ permissions: [ 'role1', 'role2' ] }); + expect(mockStatEditingUsers).toHaveBeenCalledWith({ permissions: ['role1', 'role2'] }); }); test('Test with exception', async () => { @@ -588,10 +483,9 @@ describe('Stat editing users', () => { let exception: unknown = undefined; try { await Interviews.statEditingUsers(); - } catch(error) { + } catch (error) { exception = error; } expect(exception).toBeDefined(); }); - }); diff --git a/packages/evolution-backend/src/services/interviews/__tests__/serverFieldUpdate.test.ts b/packages/evolution-backend/src/services/interviews/__tests__/serverFieldUpdate.test.ts index 7c9bf9bb8..65fb9bd2e 100644 --- a/packages/evolution-backend/src/services/interviews/__tests__/serverFieldUpdate.test.ts +++ b/packages/evolution-backend/src/services/interviews/__tests__/serverFieldUpdate.test.ts @@ -4,12 +4,11 @@ import updateServerFields, { getPreFilledResponseByPath, setPreFilledResponse } import prefilledDbQueries from '../../../models/interviewsPreFill.db.queries'; import TestUtils from 'chaire-lib-common/lib/test/TestUtils'; -jest.mock('../../../models/interviewsPreFill.db.queries', () => ({ - getByReferenceValue: jest.fn(), - setPreFilledResponseForRef: jest.fn() -})); +jest.mock('../../../models/interviewsPreFill.db.queries', () => ({ getByReferenceValue: jest.fn(), setPreFilledResponseForRef: jest.fn() })); const mockGetByReferenceValue = prefilledDbQueries.getByReferenceValue as jest.MockedFunction; -const mockSetPreFilledResponseForRef = prefilledDbQueries.setPreFilledResponseForRef as jest.MockedFunction; +const mockSetPreFilledResponseForRef = prefilledDbQueries.setPreFilledResponseForRef as jest.MockedFunction< + typeof prefilledDbQueries.setPreFilledResponseForRef +>; const deferredUpdateCallback = jest.fn(); const interviewAttributes = { @@ -20,13 +19,7 @@ const interviewAttributes = { is_active: true, is_completed: false, is_questionable: false, - response: { - accessCode: '11111', - testFields: { - fieldA: 'a', - fieldB: 'b' - } - }, + response: { accessCode: '11111', testFields: { fieldA: 'a', fieldB: 'b' } }, validations: {}, survey_id: 1 } as any; @@ -37,19 +30,24 @@ const updateCallbacks = [ { field: 'testFields.fieldA', runOnCorrectedResponse: true, - callback: jest.fn().mockImplementation((interview, fieldValue) => - fieldValue === 'foo' - ? { 'testFields.fieldB': 'bar' } - : fieldValue === 'same' - ? { 'testFields.fieldB': interviewAttributes.response.testFields.fieldB } - : {}) - }, { + callback: jest + .fn() + .mockImplementation((interview, fieldValue) => + fieldValue === 'foo' + ? { 'testFields.fieldB': 'bar' } + : fieldValue === 'same' + ? { 'testFields.fieldB': interviewAttributes.response.testFields.fieldB } + : {} + ) + }, + { field: { regex: 'household\.persons\..+\.origin' }, callback: jest.fn().mockImplementation((interview, fieldValue, fieldPath: string) => { const newFieldPath = fieldPath.substring(0, fieldPath.length - '.origin'.length) + '.new'; return { [newFieldPath]: 'FOO' }; }) - }, { + }, + { field: '_isCompleted', callback: jest.fn().mockImplementation((interview, fieldValue, fieldPath: string) => { if (fieldValue === true) { @@ -57,7 +55,8 @@ const updateCallbacks = [ } return {}; }) - }, { + }, + { field: 'testFields.callback', callback: jest.fn().mockImplementation((interview, fieldValue, fieldPath: string, registerCallback) => { registerCallback({ @@ -77,42 +76,93 @@ beforeEach(() => { describe('Simple field update', () => { each([ ['Values by path, but field not updated', { 'response.testFields.fieldB': 'abc' }, []], - ['Unset path, but field not updated', { }, ['response.testFields.fieldB']], + ['Unset path, but field not updated', {}, ['response.testFields.fieldB']], ['Field set, should return empty', { 'response.testFields.fieldA': '121' }, [], 0, {}], ['Field set in corrected response, should run for this callback', { 'corrected_response.testFields.fieldA': '121' }, [], 0, {}], - ['Field set to specific value, should return an update', { 'response.testFields.fieldA': 'foo' }, [], 0, { 'response.testFields.fieldB': 'bar' }], - ['Field set to specific value in corrected_response, should return an update', { 'corrected_response.testFields.fieldA': 'foo' }, [], 0, { 'corrected_response.testFields.fieldB': 'bar' }], + [ + 'Field set to specific value, should return an update', + { 'response.testFields.fieldA': 'foo' }, + [], + 0, + { 'response.testFields.fieldB': 'bar' } + ], + [ + 'Field set to specific value in corrected_response, should return an update', + { 'corrected_response.testFields.fieldA': 'foo' }, + [], + 0, + { 'corrected_response.testFields.fieldB': 'bar' } + ], ['Field updated to same value, should not be returned', { 'response.testFields.fieldA': 'same' }, [], 0, {}], ['Field unset, should return empty', { 'response.testFields.fieldB': 'abc' }, ['response.testFields.fieldA'], 0, {}], - ['Field update, should return URL', { 'response._isCompleted' : true }, [], 2, {}, testRedirectUrl], - ['Field update, no URL return', { 'response._isCompleted' : false }, [], 2, {}, undefined], - ['Field update in corrected_response, should not run for this callback', { 'corrected_response._isCompleted' : false }, [], false, {}, undefined], - ['Not a field in response or corrected_response', { '_isCompleted' : false }, [], false, {}, undefined], - ['No data', { }, undefined], - ]).test('%s', async (_description, valuesByPath, unsetPath, called: number | false = false, expectedFieldValues: { [path: string]: unknown } = {}, expectedUrl = undefined) => { - expect(await updateServerFields(interviewAttributes, updateCallbacks, valuesByPath, unsetPath, deferredUpdateCallback)).toEqual([expectedFieldValues, expectedUrl]); - if (called !== false) { - expect(updateCallbacks[called].callback).toHaveBeenCalledTimes(1); - if (called === 0) { - expect(updateCallbacks[called].callback).toHaveBeenCalledWith(interviewAttributes, valuesByPath['response.testFields.fieldA'] !== undefined ? valuesByPath['response.testFields.fieldA'] : valuesByPath['corrected_response.testFields.fieldA'], 'testFields.fieldA', expect.anything()); - } else if (called === 2) { - expect(updateCallbacks[called].callback).toHaveBeenCalledWith(interviewAttributes, valuesByPath['response._isCompleted'], '_isCompleted', expect.anything()); - } - - } else { - for (let i = 0; i < updateCallbacks.length; i++) { - expect(updateCallbacks[i].callback).not.toHaveBeenCalled(); + ['Field update, should return URL', { 'response._isCompleted': true }, [], 2, {}, testRedirectUrl], + ['Field update, no URL return', { 'response._isCompleted': false }, [], 2, {}, undefined], + [ + 'Field update in corrected_response, should not run for this callback', + { 'corrected_response._isCompleted': false }, + [], + false, + {}, + undefined + ], + ['Not a field in response or corrected_response', { _isCompleted: false }, [], false, {}, undefined], + ['No data', {}, undefined] + ]).test( + '%s', + async ( + _description, + valuesByPath, + unsetPath, + called: number | false = false, + expectedFieldValues: { [path: string]: unknown } = {}, + expectedUrl = undefined + ) => { + expect(await updateServerFields(interviewAttributes, updateCallbacks, valuesByPath, unsetPath, deferredUpdateCallback)).toEqual([ + expectedFieldValues, + expectedUrl + ]); + if (called !== false) { + expect(updateCallbacks[called].callback).toHaveBeenCalledTimes(1); + if (called === 0) { + expect(updateCallbacks[called].callback).toHaveBeenCalledWith( + interviewAttributes, + valuesByPath['response.testFields.fieldA'] !== undefined + ? valuesByPath['response.testFields.fieldA'] + : valuesByPath['corrected_response.testFields.fieldA'], + 'testFields.fieldA', + expect.anything() + ); + } else if (called === 2) { + expect(updateCallbacks[called].callback).toHaveBeenCalledWith( + interviewAttributes, + valuesByPath['response._isCompleted'], + '_isCompleted', + expect.anything() + ); + } + } else { + for (let i = 0; i < updateCallbacks.length; i++) { + expect(updateCallbacks[i].callback).not.toHaveBeenCalled(); + } } + expect(deferredUpdateCallback).not.toHaveBeenCalled(); } - expect(deferredUpdateCallback).not.toHaveBeenCalled(); - }); + ); }); describe('Field with placeholder and regex', () => { test('Field in valuesByPath, should return updated value', async () => { const valuesByPath = { 'response.household.persons.1.origin': 'foo' }; - expect(await updateServerFields(interviewAttributes, updateCallbacks, valuesByPath, [], deferredUpdateCallback)).toEqual([{ 'response.household.persons.1.new': 'FOO' }, undefined]); - expect(updateCallbacks[1].callback).toHaveBeenCalledWith(interviewAttributes, valuesByPath['response.household.persons.1.origin'], 'household.persons.1.origin', expect.anything()); + expect(await updateServerFields(interviewAttributes, updateCallbacks, valuesByPath, [], deferredUpdateCallback)).toEqual([ + { 'response.household.persons.1.new': 'FOO' }, + undefined + ]); + expect(updateCallbacks[1].callback).toHaveBeenCalledWith( + interviewAttributes, + valuesByPath['response.household.persons.1.origin'], + 'household.persons.1.origin', + expect.anything() + ); }); test('Field in valuesByPath, invalid regex path should not match', async () => { // This path has double dots, which should not match the regex pattern 'household\.persons\..+\.origin' @@ -128,11 +178,13 @@ describe('Field with placeholder and regex', () => { // The callback should NOT be called because the path doesn't match the regex expect(updateCallbacks[1].callback).not.toHaveBeenCalled(); }); - test('Field in unsetPath, should return updated value', async() => { - const unsetPath = [ 'response.household.persons.2.origin' ]; - expect(await updateServerFields(interviewAttributes, updateCallbacks, {}, unsetPath)).toEqual([{ 'response.household.persons.2.new': 'FOO' }, undefined]); + test('Field in unsetPath, should return updated value', async () => { + const unsetPath = ['response.household.persons.2.origin']; + expect(await updateServerFields(interviewAttributes, updateCallbacks, {}, unsetPath)).toEqual([ + { 'response.household.persons.2.new': 'FOO' }, + undefined + ]); expect(updateCallbacks[1].callback).toHaveBeenCalledWith(interviewAttributes, undefined, 'household.persons.2.origin', undefined); - }); }); @@ -143,48 +195,54 @@ test('No field update callbacks', async () => { describe('With an update execution callback registration', () => { test('One execution callback call', async () => { const valuesByPath = { 'response.testFields.callback': 'bar' }; - expect(await updateServerFields(interviewAttributes, updateCallbacks, valuesByPath, [], deferredUpdateCallback)).toEqual([{ }, undefined]); + expect(await updateServerFields(interviewAttributes, updateCallbacks, valuesByPath, [], deferredUpdateCallback)).toEqual([{}, undefined]); // Flush promises to make sure the execution callback has been called await TestUtils.flushPromises(); - expect(updateCallbacks[3].callback).toHaveBeenCalledWith(interviewAttributes, valuesByPath['response.testFields.callback'], 'testFields.callback', expect.anything()); - expect(deferredUpdateCallback).toHaveBeenCalledWith({ 'response.testFields.callbackResponse' : 'bar.appended' }); + expect(updateCallbacks[3].callback).toHaveBeenCalledWith( + interviewAttributes, + valuesByPath['response.testFields.callback'], + 'testFields.callback', + expect.anything() + ); + expect(deferredUpdateCallback).toHaveBeenCalledWith({ 'response.testFields.callbackResponse': 'bar.appended' }); }); test('Multiple calls to same execution, sequentially', async () => { const valuesByPath = { 'response.testFields.callback': 'bar' }; - expect(await updateServerFields(interviewAttributes, updateCallbacks, valuesByPath, [], deferredUpdateCallback)).toEqual([{ }, undefined]); + expect(await updateServerFields(interviewAttributes, updateCallbacks, valuesByPath, [], deferredUpdateCallback)).toEqual([{}, undefined]); // Flush promises to make sure the execution callback has been called await TestUtils.flushPromises(); // Make a second call with other values const valuesByPath2 = { 'response.testFields.callback': 'foo' }; - expect(await updateServerFields(interviewAttributes, updateCallbacks, valuesByPath2, [], deferredUpdateCallback)).toEqual([{ }, undefined]); + expect(await updateServerFields(interviewAttributes, updateCallbacks, valuesByPath2, [], deferredUpdateCallback)).toEqual([{}, undefined]); // Flush promises to make sure the execution callback has been called await TestUtils.flushPromises(); - expect(deferredUpdateCallback).toHaveBeenCalledWith({ 'response.testFields.callbackResponse' : 'bar.appended' }); - expect(deferredUpdateCallback).toHaveBeenCalledWith({ 'response.testFields.callbackResponse' : 'foo.appended' }); + expect(deferredUpdateCallback).toHaveBeenCalledWith({ 'response.testFields.callbackResponse': 'bar.appended' }); + expect(deferredUpdateCallback).toHaveBeenCalledWith({ 'response.testFields.callbackResponse': 'foo.appended' }); }); test('Multiple calls to same execution, at the same time', async () => { const valuesByPath = { 'response.testFields.callback': 'bar' }; let finishOp1 = false; - const call1Response = { 'testFields.callbackResponse' : 'bar.appended.call1' }; + const call1Response = { 'testFields.callbackResponse': 'bar.appended.call1' }; let promiseComplete: Promise | undefined = undefined; mockedOperation.mockImplementationOnce(() => { promiseComplete = new Promise((resolve) => { - const waitFinish = () => setTimeout(() => { - if (finishOp1) { - console.log('resolving'); - resolve(call1Response); - } else { - waitFinish(); - } - }, 1000); + const waitFinish = () => + setTimeout(() => { + if (finishOp1) { + console.log('resolving'); + resolve(call1Response); + } else { + waitFinish(); + } + }, 1000); waitFinish(); }); return promiseComplete; @@ -200,30 +258,38 @@ describe('With an update execution callback registration', () => { // Flush promises to make sure the execution callback has been completed. It should be called only once await TestUtils.flushPromises(); - expect(updateCallbacks[3].callback).toHaveBeenCalledWith(interviewAttributes, valuesByPath['response.testFields.callback'], 'testFields.callback', expect.anything()); + expect(updateCallbacks[3].callback).toHaveBeenCalledWith( + interviewAttributes, + valuesByPath['response.testFields.callback'], + 'testFields.callback', + expect.anything() + ); expect(deferredUpdateCallback).toHaveBeenCalledTimes(1); - expect(deferredUpdateCallback).toHaveBeenCalledWith(Object.keys(call1Response).reduce((acc, key) => { - acc[`response.${key}`] = call1Response[key]; - return acc; - }, {})); + expect(deferredUpdateCallback).toHaveBeenCalledWith( + Object.keys(call1Response).reduce((acc, key) => { + acc[`response.${key}`] = call1Response[key]; + return acc; + }, {}) + ); }); test('Multiple calls, with different unique ID, with cancellation', async () => { const valuesByPath = { 'response.testFields.callback': 'bar' }; let finishOp1 = false; - const call1Response = { 'testFields.callbackResponse' : 'bar.appended.call1' }; + const call1Response = { 'testFields.callbackResponse': 'bar.appended.call1' }; let promiseComplete: Promise | undefined = undefined; mockedOperation.mockImplementationOnce(() => { promiseComplete = new Promise((resolve) => { - const waitFinish = () => setTimeout(() => { - if (finishOp1) { - console.log('resolving'); - resolve(call1Response); - } else { - waitFinish(); - } - }, 1000); + const waitFinish = () => + setTimeout(() => { + if (finishOp1) { + console.log('resolving'); + resolve(call1Response); + } else { + waitFinish(); + } + }, 1000); waitFinish(); }); return promiseComplete; @@ -243,7 +309,7 @@ describe('With an update execution callback registration', () => { // Test that the deferred callback was called only once, with the results of the last operation expect(deferredUpdateCallback).toHaveBeenCalledTimes(1); - expect(deferredUpdateCallback).toHaveBeenCalledWith({ 'response.testFields.callbackResponse' : 'foo.appended' }); + expect(deferredUpdateCallback).toHaveBeenCalledWith({ 'response.testFields.callbackResponse': 'foo.appended' }); }); }); @@ -251,16 +317,15 @@ test('Test with exceptions', async () => { updateCallbacks[0].callback.mockRejectedValueOnce('error'); updateCallbacks[0].callback.mockRejectedValueOnce('error'); expect(await updateServerFields(interviewAttributes, updateCallbacks, { 'response.testFields.fieldA': 'foo' }, [])).toEqual([{}, undefined]); - expect(await updateServerFields(interviewAttributes, updateCallbacks, { }, ['response.testFields.fieldA'])).toEqual([{}, undefined]); + expect(await updateServerFields(interviewAttributes, updateCallbacks, {}, ['response.testFields.fieldA'])).toEqual([{}, undefined]); }); describe('getPreFilledValuesByPath', () => { - beforeEach(() => { mockGetByReferenceValue.mockClear(); }); - test('Return undefined response', async() => { + test('Return undefined response', async () => { mockGetByReferenceValue.mockResolvedValueOnce(undefined); const preFilledValuesByPath = await getPreFilledResponseByPath('test', interviewAttributes); expect(preFilledValuesByPath).toEqual({}); @@ -268,38 +333,35 @@ describe('getPreFilledValuesByPath', () => { }); test('Return fields that are not in the interview', async () => { - const preFilledResponse = { - 'testFields.fieldC': { value: 'abc' }, - 'otherField': { value: 3 } - }; + const preFilledResponse = { 'testFields.fieldC': { value: 'abc' }, otherField: { value: 3 } }; mockGetByReferenceValue.mockResolvedValueOnce(preFilledResponse); const preFilledValuesByPath = await getPreFilledResponseByPath('test', interviewAttributes); expect(preFilledValuesByPath).toEqual({ 'testFields.fieldC': preFilledResponse['testFields.fieldC'].value, - 'otherField': preFilledResponse['otherField'].value + otherField: preFilledResponse['otherField'].value }); expect(mockGetByReferenceValue).toHaveBeenLastCalledWith('test'); }); - test('Return values that are in the interview, that should be forced', async() => { + test('Return values that are in the interview, that should be forced', async () => { const preFilledResponse = { 'testFields.fieldB': { value: 'abc', actionIfPresent: 'force' as const }, - 'accessCode': { value: '2222' }, - 'otherField': { value: 4 } + accessCode: { value: '2222' }, + otherField: { value: 4 } }; mockGetByReferenceValue.mockResolvedValueOnce(preFilledResponse); const preFilledValuesByPath = await getPreFilledResponseByPath('test', interviewAttributes); expect(preFilledValuesByPath).toEqual({ 'testFields.fieldB': preFilledResponse['testFields.fieldB'].value, - 'otherField': preFilledResponse['otherField'].value + otherField: preFilledResponse['otherField'].value }); expect(mockGetByReferenceValue).toHaveBeenLastCalledWith('test'); }); - test('Return values that are in the interview, don\'t update', async() => { + test('Return values that are in the interview, don\'t update', async () => { const preFilledResponse = { 'testFields.fieldB': { value: 'abc', actionIfPresent: 'doNothing' as const }, - 'accessCode': { value: '2222', actionIfPresent: 'doNothing' as const } + accessCode: { value: '2222', actionIfPresent: 'doNothing' as const } }; mockGetByReferenceValue.mockResolvedValueOnce(preFilledResponse); const preFilledValuesByPath = await getPreFilledResponseByPath('test', interviewAttributes); @@ -307,7 +369,7 @@ describe('getPreFilledValuesByPath', () => { expect(mockGetByReferenceValue).toHaveBeenLastCalledWith('test'); }); - test('Exception getting the values', async() => { + test('Exception getting the values', async () => { mockGetByReferenceValue.mockRejectedValueOnce('error'); const preFilledValuesByPath = await getPreFilledResponseByPath('test', interviewAttributes); expect(preFilledValuesByPath).toEqual({}); @@ -316,11 +378,7 @@ describe('getPreFilledValuesByPath', () => { }); describe('setPreFilledResponse', () => { - - const preFilledResponse = { - 'testFields.fieldC': { value: 'abc' }, - 'otherField': { value: 3 } - }; + const preFilledResponse = { 'testFields.fieldC': { value: 'abc' }, otherField: { value: 3 } }; beforeEach(() => { mockSetPreFilledResponseForRef.mockClear(); @@ -332,7 +390,7 @@ describe('setPreFilledResponse', () => { expect(mockSetPreFilledResponseForRef).toHaveBeenLastCalledWith('test', preFilledResponse); }); - test('Exception setting the values', async() => { + test('Exception setting the values', async () => { mockSetPreFilledResponseForRef.mockRejectedValueOnce('error'); await setPreFilledResponse('test', preFilledResponse); expect(mockSetPreFilledResponseForRef).toHaveBeenLastCalledWith('test', preFilledResponse); diff --git a/packages/evolution-backend/src/services/logging/__tests__/messageLogging.test.ts b/packages/evolution-backend/src/services/logging/__tests__/messageLogging.test.ts index 14080f19e..ae1071ba9 100644 --- a/packages/evolution-backend/src/services/logging/__tests__/messageLogging.test.ts +++ b/packages/evolution-backend/src/services/logging/__tests__/messageLogging.test.ts @@ -26,91 +26,48 @@ describe('logClientSide', () => { it('should log an error with message', () => { logClientSide({ interviewId: 123, message: 'Test error', logLevel: 'error' }); - expect(consoleErrorSpy).toHaveBeenCalledWith( - 'Client-side %s in interview %d: %s', - 'error', - 123, - 'Test error' - ); + expect(consoleErrorSpy).toHaveBeenCalledWith('Client-side %s in interview %d: %s', 'error', 123, 'Test error'); }); it('should log a warning with message', () => { logClientSide({ interviewId: 123, message: 'Test warning', logLevel: 'warn' }); - expect(consoleWarnSpy).toHaveBeenCalledWith( - 'Client-side %s in interview %d: %s', - 'warn', - 123, - 'Test warning' - ); + expect(consoleWarnSpy).toHaveBeenCalledWith('Client-side %s in interview %d: %s', 'warn', 123, 'Test warning'); }); it('should log info with message', () => { logClientSide({ interviewId: 123, message: 'Test info', logLevel: 'info' }); - expect(consoleInfoSpy).toHaveBeenCalledWith( - 'Client-side %s in interview %d: %s', - 'info', - 123, - 'Test info' - ); + expect(consoleInfoSpy).toHaveBeenCalledWith('Client-side %s in interview %d: %s', 'info', 123, 'Test info'); }); it('should log a log with message', () => { logClientSide({ interviewId: 123, message: 'Test log', logLevel: 'log' }); - expect(consoleLogSpy).toHaveBeenCalledWith( - 'Client-side %s in interview %d: %s', - 'log', - 123, - 'Test log' - ); + expect(consoleLogSpy).toHaveBeenCalledWith('Client-side %s in interview %d: %s', 'log', 123, 'Test log'); }); it('should log an error when message is missing', () => { logClientSide({ interviewId: 123 }); - expect(consoleErrorSpy).toHaveBeenCalledWith( - 'Client-side %s in interview %d: missing message data', - 'error', - 123 - ); + expect(consoleErrorSpy).toHaveBeenCalledWith('Client-side %s in interview %d: missing message data', 'error', 123); }); it('should truncate long messages to 1000 characters', () => { const longException = 'a'.repeat(1500); logClientSide({ interviewId: 123, message: longException, logLevel: 'error' }); - expect(consoleErrorSpy).toHaveBeenCalledWith( - 'Client-side %s in interview %d: %s', - 'error', - 123, - longException.substring(0, 1000) - ); + expect(consoleErrorSpy).toHaveBeenCalledWith('Client-side %s in interview %d: %s', 'error', 123, longException.substring(0, 1000)); }); it('should handle non-string messages', () => { const messageObject = { message: 'Test object error' }; logClientSide({ interviewId: 123, message: messageObject, logLevel: 'error' }); - expect(consoleErrorSpy).toHaveBeenCalledWith( - 'Client-side %s in interview %d: %s', - 'error', - 123, - String(messageObject) - ); + expect(consoleErrorSpy).toHaveBeenCalledWith('Client-side %s in interview %d: %s', 'error', 123, String(messageObject)); }); it('should log an error when empty parameters', () => { logClientSide(); - expect(consoleErrorSpy).toHaveBeenCalledWith( - 'Client-side %s in interview %d: missing message data', - 'error', - -1 - ); + expect(consoleErrorSpy).toHaveBeenCalledWith('Client-side %s in interview %d: missing message data', 'error', -1); }); it('should log an error with message', () => { logClientSide({ interviewId: 123, message: 'Test error' }); - expect(consoleErrorSpy).toHaveBeenCalledWith( - 'Client-side %s in interview %d: %s', - 'error', - 123, - 'Test error' - ); + expect(consoleErrorSpy).toHaveBeenCalledWith('Client-side %s in interview %d: %s', 'error', 123, 'Test error'); }); }); diff --git a/packages/evolution-backend/src/services/logging/__tests__/paradataLogging.test.ts b/packages/evolution-backend/src/services/logging/__tests__/paradataLogging.test.ts index 844d6cab6..c9247c13f 100644 --- a/packages/evolution-backend/src/services/logging/__tests__/paradataLogging.test.ts +++ b/packages/evolution-backend/src/services/logging/__tests__/paradataLogging.test.ts @@ -10,21 +10,16 @@ import config from 'chaire-lib-backend/lib/config/server.config'; import paradataEventsDbQueries from '../../../models/paradataEvents.db.queries'; import { UserAction } from 'evolution-common/lib/services/questionnaire/types'; -jest.mock('../../../models/paradataEvents.db.queries', () => ({ - log: jest.fn().mockResolvedValue(true), -})); +jest.mock('../../../models/paradataEvents.db.queries', () => ({ log: jest.fn().mockResolvedValue(true) })); const mockLog = paradataEventsDbQueries.log as jest.MockedFunction; -jest.mock('chaire-lib-backend/lib/config/server.config', () => ({ - logDatabaseUpdates: true -})); +jest.mock('chaire-lib-backend/lib/config/server.config', () => ({ logDatabaseUpdates: true })); beforeEach(() => { jest.clearAllMocks(); }); describe('Log for a participant', () => { - beforeEach(() => { (config as any).logDatabaseUpdates = true; }); @@ -35,22 +30,14 @@ describe('Log for a participant', () => { it('Should correctly log a widget interaction', async () => { expect(logFunction).toBeDefined(); - const userAction = { - type: 'widgetInteraction' as const, - widgetType: 'string', - path: 'testWidget', - value: 'myValue' - }; - const logData = { valuesByPath: {someData: 'test'} }; + const userAction = { type: 'widgetInteraction' as const, widgetType: 'string', path: 'testWidget', value: 'myValue' }; + const logData = { valuesByPath: { someData: 'test' } }; - expect(await logFunction!({ userAction, ...logData})).toBe(true); + expect(await logFunction!({ userAction, ...logData })).toBe(true); expect(mockLog).toHaveBeenCalledWith({ eventType: 'widget_interaction', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId: undefined, forCorrection: false @@ -58,96 +45,67 @@ describe('Log for a participant', () => { }); it('Should correctly log a button click', async () => { - const userAction = { - type: 'buttonClick' as const, - buttonId: 'button1' - }; - const logData = { valuesByPath: {someData: 'test'}, unsetPaths: ['path1', 'path2'] }; + const userAction = { type: 'buttonClick' as const, buttonId: 'button1' }; + const logData = { valuesByPath: { someData: 'test' }, unsetPaths: ['path1', 'path2'] }; - expect(await logFunction!({ userAction, ...logData})).toBe(true); + expect(await logFunction!({ userAction, ...logData })).toBe(true); expect(mockLog).toHaveBeenCalledWith({ eventType: 'button_click', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId: undefined, forCorrection: false }); - }); it('Should correctly log a section change', async () => { - const userAction = { - type: 'sectionChange' as const, - targetSection: { sectionShortname: 'section1' } - }; - const logData = { valuesByPath: {someData: 'test'}, unsetPaths: ['path1', 'path2'] }; + const userAction = { type: 'sectionChange' as const, targetSection: { sectionShortname: 'section1' } }; + const logData = { valuesByPath: { someData: 'test' }, unsetPaths: ['path1', 'path2'] }; - expect(await logFunction!({ userAction, ...logData})).toBe(true); + expect(await logFunction!({ userAction, ...logData })).toBe(true); expect(mockLog).toHaveBeenCalledWith({ eventType: 'section_change', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId: undefined, forCorrection: false }); - }); it('Should correctly log a language change', async () => { - const userAction: UserAction = { - type: 'languageChange' as const, - language: 'fr' - }; - const logData = { valuesByPath: {someData: 'test'}, unsetPaths: ['path1', 'path2'] }; + const userAction: UserAction = { type: 'languageChange' as const, language: 'fr' }; + const logData = { valuesByPath: { someData: 'test' }, unsetPaths: ['path1', 'path2'] }; - expect(await logFunction!({ userAction, ...logData})).toBe(true); + expect(await logFunction!({ userAction, ...logData })).toBe(true); expect(mockLog).toHaveBeenCalledWith({ eventType: 'language_change', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId: undefined, forCorrection: false }); - }); it('Should correctly log a interview open', async () => { - const userAction: UserAction = { - type: 'interviewOpen' as const, - browser: { name: 'Firefox'}, - language: 'fr' - }; - const logData = { valuesByPath: {someData: 'test'}, unsetPaths: ['path1', 'path2'] }; + const userAction: UserAction = { type: 'interviewOpen' as const, browser: { name: 'Firefox' }, language: 'fr' }; + const logData = { valuesByPath: { someData: 'test' }, unsetPaths: ['path1', 'path2'] }; - expect(await logFunction!({ userAction, ...logData})).toBe(true); + expect(await logFunction!({ userAction, ...logData })).toBe(true); expect(mockLog).toHaveBeenCalledWith({ eventType: 'interview_open', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId: undefined, forCorrection: false }); - }); it('Should correctly log a server event', async () => { - const logData = { valuesByPath: {someData: 'test'}, server: true }; + const logData = { valuesByPath: { someData: 'test' }, server: true }; expect(await logFunction!(logData)).toBe(true); @@ -161,35 +119,23 @@ describe('Log for a participant', () => { }); it('Should correctly log a side effect', async () => { - const logData = { valuesByPath: {someData: 'test'}, unsetPaths: ['path1', 'path2'] }; + const logData = { valuesByPath: { someData: 'test' }, unsetPaths: ['path1', 'path2'] }; expect(await logFunction!(logData)).toBe(true); - expect(mockLog).toHaveBeenCalledWith({ - eventType: 'side_effect', - eventData: logData, - interviewId, - userId: undefined, - forCorrection: false - }); + expect(mockLog).toHaveBeenCalledWith({ eventType: 'side_effect', eventData: logData, interviewId, userId: undefined, forCorrection: false }); }); it('Should return false if error on user action', async () => { - const userAction = { - type: 'buttonClick' as const, - buttonId: 'button1' - }; - const logData = { valuesByPath: {someData: 'test'} }; + const userAction = { type: 'buttonClick' as const, buttonId: 'button1' }; + const logData = { valuesByPath: { someData: 'test' } }; mockLog.mockRejectedValueOnce(new Error('Database error')); expect(await logFunction!({ userAction, ...logData })).toBe(false); expect(mockLog).toHaveBeenCalledWith({ eventType: 'button_click', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId: undefined, forCorrection: false @@ -197,23 +143,16 @@ describe('Log for a participant', () => { }); it('Should return false if error on side effect', async () => { - const logData = { valuesByPath: {someData: 'test'} }; + const logData = { valuesByPath: { someData: 'test' } }; mockLog.mockRejectedValueOnce(new Error('Database error')); expect(await logFunction!(logData)).toBe(false); - expect(mockLog).toHaveBeenCalledWith({ - eventType: 'side_effect', - eventData: logData, - interviewId, - userId: undefined, - forCorrection: false - }); - + expect(mockLog).toHaveBeenCalledWith({ eventType: 'side_effect', eventData: logData, interviewId, userId: undefined, forCorrection: false }); }); it('Should return false if error on server event', async () => { - const logData = { valuesByPath: {someData: 'test'}, server: true }; + const logData = { valuesByPath: { someData: 'test' }, server: true }; mockLog.mockRejectedValueOnce(new Error('Database error')); expect(await logFunction!(logData)).toBe(false); @@ -228,12 +167,7 @@ describe('Log for a participant', () => { }); }); - -describe.each([ - [true], - [false], -])('Log for a user with forCorrection set to `%s`', (forCorrection) => { - +describe.each([[true], [false]])('Log for a user with forCorrection set to `%s`', (forCorrection) => { const interviewId = 123; const userId = 456; const logFunction = getParadataLoggingFunction({ interviewId, userId, isCorrectedInterview: forCorrection }); @@ -244,22 +178,14 @@ describe.each([ it('Should correctly log a widget interaction', async () => { expect(logFunction).toBeDefined(); - const userAction = { - type: 'widgetInteraction' as const, - widgetType: 'string', - path: 'testWidget', - value: 'myValue' - }; - const logData = { valuesByPath: {someData: 'test'} }; + const userAction = { type: 'widgetInteraction' as const, widgetType: 'string', path: 'testWidget', value: 'myValue' }; + const logData = { valuesByPath: { someData: 'test' } }; - expect(await logFunction!({ userAction, ...logData})).toBe(true); + expect(await logFunction!({ userAction, ...logData })).toBe(true); expect(mockLog).toHaveBeenCalledWith({ eventType: 'widget_interaction', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId, forCorrection @@ -267,96 +193,67 @@ describe.each([ }); it('Should correctly log a button click', async () => { - const userAction = { - type: 'buttonClick' as const, - buttonId: 'button1' - }; - const logData = { valuesByPath: {someData: 'test'}, unsetPaths: ['path1', 'path2'] }; + const userAction = { type: 'buttonClick' as const, buttonId: 'button1' }; + const logData = { valuesByPath: { someData: 'test' }, unsetPaths: ['path1', 'path2'] }; - expect(await logFunction!({ userAction, ...logData})).toBe(true); + expect(await logFunction!({ userAction, ...logData })).toBe(true); expect(mockLog).toHaveBeenCalledWith({ eventType: 'button_click', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId, forCorrection }); - }); it('Should correctly log a section change', async () => { - const userAction = { - type: 'sectionChange' as const, - targetSection: { sectionShortname: 'section1' } - }; - const logData = { valuesByPath: {someData: 'test'}, unsetPaths: ['path1', 'path2'] }; + const userAction = { type: 'sectionChange' as const, targetSection: { sectionShortname: 'section1' } }; + const logData = { valuesByPath: { someData: 'test' }, unsetPaths: ['path1', 'path2'] }; - expect(await logFunction!({ userAction, ...logData})).toBe(true); + expect(await logFunction!({ userAction, ...logData })).toBe(true); expect(mockLog).toHaveBeenCalledWith({ eventType: 'section_change', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId, forCorrection }); - }); it('Should correctly log a language change', async () => { - const userAction: UserAction = { - type: 'languageChange' as const, - language: 'fr' - }; - const logData = { valuesByPath: {someData: 'test'}, unsetPaths: ['path1', 'path2'] }; + const userAction: UserAction = { type: 'languageChange' as const, language: 'fr' }; + const logData = { valuesByPath: { someData: 'test' }, unsetPaths: ['path1', 'path2'] }; - expect(await logFunction!({ userAction, ...logData})).toBe(true); + expect(await logFunction!({ userAction, ...logData })).toBe(true); expect(mockLog).toHaveBeenCalledWith({ eventType: 'language_change', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId, forCorrection }); - }); it('Should correctly log a interview open', async () => { - const userAction: UserAction = { - type: 'interviewOpen' as const, - browser: { name: 'Firefox'}, - language: 'fr' - }; - const logData = { valuesByPath: {someData: 'test'}, unsetPaths: ['path1', 'path2'] }; + const userAction: UserAction = { type: 'interviewOpen' as const, browser: { name: 'Firefox' }, language: 'fr' }; + const logData = { valuesByPath: { someData: 'test' }, unsetPaths: ['path1', 'path2'] }; - expect(await logFunction!({ userAction, ...logData})).toBe(true); + expect(await logFunction!({ userAction, ...logData })).toBe(true); expect(mockLog).toHaveBeenCalledWith({ eventType: 'interview_open', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId, forCorrection }); - }); it('Should correctly log a server event', async () => { - const logData = { valuesByPath: {someData: 'test'}, server: true }; + const logData = { valuesByPath: { someData: 'test' }, server: true }; expect(await logFunction!(logData)).toBe(true); @@ -370,45 +267,31 @@ describe.each([ }); it('Should correctly log a side effect', async () => { - const logData = { valuesByPath: {someData: 'test'}, unsetPaths: ['path1', 'path2'] }; + const logData = { valuesByPath: { someData: 'test' }, unsetPaths: ['path1', 'path2'] }; expect(await logFunction!(logData)).toBe(true); - expect(mockLog).toHaveBeenCalledWith({ - eventType: 'side_effect', - eventData: logData, - interviewId, - userId, - forCorrection - }); + expect(mockLog).toHaveBeenCalledWith({ eventType: 'side_effect', eventData: logData, interviewId, userId, forCorrection }); }); it('Should return false if error on user action', async () => { - const userAction = { - type: 'buttonClick' as const, - buttonId: 'button1' - }; - const logData = { valuesByPath: {someData: 'test'} }; + const userAction = { type: 'buttonClick' as const, buttonId: 'button1' }; + const logData = { valuesByPath: { someData: 'test' } }; mockLog.mockRejectedValueOnce(new Error('Database error')); expect(await logFunction!({ userAction, ...logData })).toBe(false); expect(mockLog).toHaveBeenCalledWith({ eventType: 'button_click', - eventData: { - ...logData, - userAction - }, + eventData: { ...logData, userAction }, interviewId, userId, forCorrection }); }); - }); test('Log when database updates are disabled', () => { - // Disable database logging from the config (config as any).logDatabaseUpdates = false; diff --git a/packages/evolution-backend/src/services/logging/__tests__/queryLoggingMiddleware.test.ts b/packages/evolution-backend/src/services/logging/__tests__/queryLoggingMiddleware.test.ts index d284a4a4f..0444702ba 100644 --- a/packages/evolution-backend/src/services/logging/__tests__/queryLoggingMiddleware.test.ts +++ b/packages/evolution-backend/src/services/logging/__tests__/queryLoggingMiddleware.test.ts @@ -5,31 +5,29 @@ * License text available at https://opensource.org/licenses/MIT */ import { NextFunction, Request, Response } from 'express'; -import { v4 as uuidV4 } from 'uuid' +import { v4 as uuidV4 } from 'uuid'; import { logUserAccessesMiddleware, defaultMiddlewares } from '../queryLoggingMiddleware'; import each from 'jest-each'; import interviewsAccessesDbQueries from '../../../models/interviewsAccesses.db.queries'; -jest.mock('../../../models/interviewsAccesses.db.queries', () => ({ - userOpenedInterview: jest.fn(), - userUpdatedInterview: jest.fn(), -})); -const mockUserOpenedQuery = interviewsAccessesDbQueries.userOpenedInterview as jest.MockedFunction; -const mockUserUpdatedQuery = interviewsAccessesDbQueries.userUpdatedInterview as jest.MockedFunction; +jest.mock('../../../models/interviewsAccesses.db.queries', () => ({ userOpenedInterview: jest.fn(), userUpdatedInterview: jest.fn() })); +const mockUserOpenedQuery = interviewsAccessesDbQueries.userOpenedInterview as jest.MockedFunction< + typeof interviewsAccessesDbQueries.userOpenedInterview +>; +const mockUserUpdatedQuery = interviewsAccessesDbQueries.userUpdatedInterview as jest.MockedFunction< + typeof interviewsAccessesDbQueries.userUpdatedInterview +>; let mockRequest: Partial; let mockResponse: Partial; -let nextFunction: NextFunction = jest.fn(); -let mockUser = { id: 3, username: 'notAdmin' }; +const nextFunction: NextFunction = jest.fn(); +const mockUser = { id: 3, username: 'notAdmin' }; const interviewId = uuidV4(); beforeEach(() => { mockRequest = {}; - mockResponse = { - json: jest.fn(), - status: jest.fn().mockReturnThis() - }; + mockResponse = { json: jest.fn(), status: jest.fn().mockReturnThis() }; (nextFunction as any).mockClear(); mockUserOpenedQuery.mockClear(); mockUserUpdatedQuery.mockClear(); @@ -38,10 +36,9 @@ beforeEach(() => { each([ ['Interview uuid in params', mockUser, { interviewUuid: interviewId }, true], ['Interview id in params', mockUser, { interviewUuid: interviewId }, true], - ['No interview ID', mockUser, { }, false], - ['No user, should not happen, but not cause exception either', undefined, { interviewUuid: interviewId }, false], + ['No interview ID', mockUser, {}, false], + ['No user, should not happen, but not cause exception either', undefined, { interviewUuid: interviewId }, false] ]).describe('log opening the interview: %s', (_title, user, reqParams, expectedCalled) => { - test('log user access: edit mode', async () => { mockRequest.user = user; const request = { ...mockRequest, params: reqParams }; @@ -87,9 +84,8 @@ each([ ['Interview uuid in params', mockUser, { interviewUuid: interviewId }, {}, true], ['Interview id in body', mockUser, {}, { interviewId: interviewId }, true], ['No interview ID', mockUser, {}, {}, false], - ['No user, should not happen, but not cause exception either', undefined, { interviewUuid: interviewId }, {}, false], + ['No user, should not happen, but not cause exception either', undefined, { interviewUuid: interviewId }, {}, false] ]).describe('log updating the interview: %s', (_title, user, reqParams, body, expectedCalled) => { - test('edit mode', async () => { mockRequest.user = user; const request = { ...mockRequest, params: reqParams, body: body }; diff --git a/packages/evolution-backend/src/services/logging/__tests__/supportRequest.test.ts b/packages/evolution-backend/src/services/logging/__tests__/supportRequest.test.ts index 7a01f6b6a..549a8d3ab 100644 --- a/packages/evolution-backend/src/services/logging/__tests__/supportRequest.test.ts +++ b/packages/evolution-backend/src/services/logging/__tests__/supportRequest.test.ts @@ -13,11 +13,10 @@ import { registerTranslationDir } from 'chaire-lib-backend/lib/config/i18next'; registerTranslationDir(__dirname + '/../../../../../../locales/'); // Mock email transport -jest.mock('chaire-lib-backend/lib/services/mailer/transport', () => (nodemailerMock.createTransport({ - sendmail: true, - newline: 'unix', - path: '/usr/sbin/sendmail' -}) as any as Transporter)); +jest.mock( + 'chaire-lib-backend/lib/services/mailer/transport', + () => nodemailerMock.createTransport({ sendmail: true, newline: 'unix', path: '/usr/sbin/sendmail' }) as any as Transporter +); const fromEmail = 'test@support.org'; const originalEnv = process.env; @@ -40,14 +39,10 @@ describe('sendSupportRequestEmail function', () => { const message = 'Help me with my survey'; const userEmail = 'user@example.com'; const interviewId = 12345; - + // Execute - await sendSupportRequestEmail({ - message, - userEmail, - interviewId - }); - + await sendSupportRequestEmail({ message, userEmail, interviewId }); + // Verify const sentEmails = nodemailerMock.mock.getSentMail(); expect(sentEmails.length).toBe(1); @@ -64,12 +59,10 @@ describe('sendSupportRequestEmail function', () => { const supportEmails = 'support1@example.com,support2@example.com'; process.env.SUPPORT_REQUEST_EMAILS = supportEmails; const message = 'Help me with my survey'; - + // Execute - await sendSupportRequestEmail({ - message - }); - + await sendSupportRequestEmail({ message }); + // Verify const sentEmails = nodemailerMock.mock.getSentMail(); expect(sentEmails.length).toBe(1); @@ -85,21 +78,18 @@ describe('sendSupportRequestEmail function', () => { process.env.SUPPORT_REQUEST_EMAILS = 'en:support-en@example.com;fr:support-fr@example.com'; const message = 'Help me with my survey'; const userEmail = 'user@example.com'; - + // Execute - await sendSupportRequestEmail({ - message, - userEmail - }); - + await sendSupportRequestEmail({ message, userEmail }); + // Verify const sentEmails = nodemailerMock.mock.getSentMail(); expect(sentEmails.length).toBe(2); - + // Check both emails were sent to appropriate addresses with correct language content - const enEmail = sentEmails.find(email => email.to === 'support-en@example.com'); - const frEmail = sentEmails.find(email => email.to === 'support-fr@example.com'); - + const enEmail = sentEmails.find((email) => email.to === 'support-en@example.com'); + const frEmail = sentEmails.find((email) => email.to === 'support-fr@example.com'); + expect(enEmail).toBeDefined(); expect(frEmail).toBeDefined(); expect(enEmail?.subject).not.toEqual(frEmail?.subject); @@ -113,20 +103,18 @@ describe('sendSupportRequestEmail function', () => { // Setup process.env.SUPPORT_REQUEST_EMAILS = 'en:support-en1@example.com,support-en2@example.com;fr:support-fr@example.com'; const message = 'Help me with my survey'; - + // Execute - await sendSupportRequestEmail({ - message - }); - + await sendSupportRequestEmail({ message }); + // Verify const sentEmails = nodemailerMock.mock.getSentMail(); expect(sentEmails.length).toBe(2); // One email per language group, not per recipient - + // Check emails were sent with multiple recipients for English - const enEmail = sentEmails.find(email => email.to === 'support-en1@example.com,support-en2@example.com'); - const frEmail = sentEmails.find(email => email.to === 'support-fr@example.com'); - + const enEmail = sentEmails.find((email) => email.to === 'support-en1@example.com,support-en2@example.com'); + const frEmail = sentEmails.find((email) => email.to === 'support-fr@example.com'); + expect(enEmail).toBeDefined(); expect(frEmail).toBeDefined(); }); @@ -134,25 +122,17 @@ describe('sendSupportRequestEmail function', () => { test('throws error when no support emails configured', async () => { // Setup delete process.env.SUPPORT_REQUEST_EMAILS; - + // Execute and verify - await expect(sendSupportRequestEmail({ - message: 'Test message' - })) - .rejects - .toThrow('No support email addresses provided'); + await expect(sendSupportRequestEmail({ message: 'Test message' })).rejects.toThrow('No support email addresses provided'); }); test('throws error when empty support emails string', async () => { // Setup process.env.SUPPORT_REQUEST_EMAILS = ''; - + // Execute and verify - await expect(sendSupportRequestEmail({ - message: 'Test message' - })) - .rejects - .toThrow('No support email addresses provided'); + await expect(sendSupportRequestEmail({ message: 'Test message' })).rejects.toThrow('No support email addresses provided'); }); test('handles no interview ID gracefully', async () => { @@ -160,13 +140,10 @@ describe('sendSupportRequestEmail function', () => { process.env.SUPPORT_REQUEST_EMAILS = 'support@example.com'; const message = 'Help me with my survey'; const userEmail = 'user@example.com'; - + // Execute - await sendSupportRequestEmail({ - message, - userEmail - }); - + await sendSupportRequestEmail({ message, userEmail }); + // Verify const sentEmails = nodemailerMock.mock.getSentMail(); expect(sentEmails.length).toBe(1); @@ -180,13 +157,10 @@ describe('sendSupportRequestEmail function', () => { process.env.SUPPORT_REQUEST_EMAILS = 'support@example.com'; const message = 'Help me with my survey'; const interviewId = 12345; - + // Execute - await sendSupportRequestEmail({ - message, - interviewId - }); - + await sendSupportRequestEmail({ message, interviewId }); + // Verify const sentEmails = nodemailerMock.mock.getSentMail(); expect(sentEmails.length).toBe(1); @@ -194,18 +168,16 @@ describe('sendSupportRequestEmail function', () => { expect(sentEmails[0].text).toContain('Unknown User'); expect(sentEmails[0].text).toContain(interviewId.toString()); }); - + test('properly formats the survey URL', async () => { // Setup process.env.SUPPORT_REQUEST_EMAILS = 'support@example.com'; process.env.HOST = 'https://survey.example.com'; const message = 'Help with survey'; - + // Execute - await sendSupportRequestEmail({ - message - }); - + await sendSupportRequestEmail({ message }); + // Verify const sentEmails = nodemailerMock.mock.getSentMail(); expect(sentEmails.length).toBe(1); @@ -218,45 +190,39 @@ describe('sendSupportRequestEmail function', () => { process.env.SUPPORT_REQUEST_EMAILS = 'support@example.com'; const message = 'Help with this page'; const currentUrl = 'https://survey.example.com/survey/page5'; - + // Execute - await sendSupportRequestEmail({ - message, - currentUrl - }); - + await sendSupportRequestEmail({ message, currentUrl }); + // Verify const sentEmails = nodemailerMock.mock.getSentMail(); expect(sentEmails.length).toBe(1); expect(sentEmails[0].text).toContain(currentUrl); expect(sentEmails[0].html).toContain(currentUrl); }); - + test('includes currentUrl in emails to different language support teams', async () => { // Setup process.env.SUPPORT_REQUEST_EMAILS = 'en:support-en@example.com;fr:support-fr@example.com'; const message = 'Help with this page'; const currentUrl = 'https://survey.example.com/survey/page5'; - + // Execute - await sendSupportRequestEmail({ - message, - currentUrl - }); - + await sendSupportRequestEmail({ message, currentUrl }); + // Verify const sentEmails = nodemailerMock.mock.getSentMail(); expect(sentEmails.length).toBe(2); - - const enEmail = sentEmails.find(email => email.to === 'support-en@example.com'); - const frEmail = sentEmails.find(email => email.to === 'support-fr@example.com'); - + + const enEmail = sentEmails.find((email) => email.to === 'support-en@example.com'); + const frEmail = sentEmails.find((email) => email.to === 'support-fr@example.com'); + expect(enEmail?.text).toContain(currentUrl); expect(frEmail?.text).toContain(currentUrl); expect(enEmail?.html).toContain(currentUrl); expect(frEmail?.html).toContain(currentUrl); }); - + test('includes all parameters when provided', async () => { // Setup process.env.SUPPORT_REQUEST_EMAILS = 'support@example.com'; @@ -264,15 +230,10 @@ describe('sendSupportRequestEmail function', () => { const userEmail = 'user@example.com'; const interviewId = 12345; const currentUrl = 'https://survey.example.com/survey/page5'; - + // Execute - await sendSupportRequestEmail({ - message, - userEmail, - interviewId, - currentUrl - }); - + await sendSupportRequestEmail({ message, userEmail, interviewId, currentUrl }); + // Verify const sentEmails = nodemailerMock.mock.getSentMail(); expect(sentEmails.length).toBe(1); diff --git a/packages/evolution-backend/src/services/paradata/__tests__/respondentBehavior.test.ts b/packages/evolution-backend/src/services/paradata/__tests__/respondentBehavior.test.ts index c40c570d5..8f816300c 100644 --- a/packages/evolution-backend/src/services/paradata/__tests__/respondentBehavior.test.ts +++ b/packages/evolution-backend/src/services/paradata/__tests__/respondentBehavior.test.ts @@ -14,13 +14,15 @@ jest.mock('../../../models/paradataEvents.db.queries', () => ({ createParadataWithWidgetPathTable: jest.fn(), getIncompleteInterviewsLastEventCounts: jest.fn() })); -const createParadataTblMock = paradataQueries.createParadataWithWidgetPathTable as jest.MockedFunction; -const getIncompleteInterviewsLastEventCountsMock = paradataQueries.getIncompleteInterviewsLastEventCounts as jest.MockedFunction; +const createParadataTblMock = paradataQueries.createParadataWithWidgetPathTable as jest.MockedFunction< + typeof paradataQueries.createParadataWithWidgetPathTable +>; +const getIncompleteInterviewsLastEventCountsMock = paradataQueries.getIncompleteInterviewsLastEventCounts as jest.MockedFunction< + typeof paradataQueries.getIncompleteInterviewsLastEventCounts +>; // Mock the knex transaction function used by the service -jest.mock('chaire-lib-backend/lib/config/shared/db.config', () => ({ - transaction: jest.fn() -})); +jest.mock('chaire-lib-backend/lib/config/shared/db.config', () => ({ transaction: jest.fn() })); const transactionMock = knex.transaction as jest.MockedFunction; beforeEach(() => { @@ -38,9 +40,7 @@ describe('RespondentBehaviorService', () => { }); // Mock the paradata queries to return one row - getIncompleteInterviewsLastEventCountsMock.mockResolvedValueOnce([ - { event_type: 'button_click', count: '7' } - ]); + getIncompleteInterviewsLastEventCountsMock.mockResolvedValueOnce([{ event_type: 'button_click', count: '7' }]); // Call the service const metrics = await RespondentBehaviorService.getRespondentBehaviorMetrics(); diff --git a/packages/evolution-backend/src/services/routing/__tests__/RouteCalculation.test.ts b/packages/evolution-backend/src/services/routing/__tests__/RouteCalculation.test.ts index 0ff45e6db..f045e943d 100644 --- a/packages/evolution-backend/src/services/routing/__tests__/RouteCalculation.test.ts +++ b/packages/evolution-backend/src/services/routing/__tests__/RouteCalculation.test.ts @@ -7,7 +7,11 @@ import { calculateTimeDistanceByMode, getTransitSummary, getTransitAccessibilityMap } from '../'; import each from 'jest-each'; -import { getTimeAndDistanceFromTransitionApi, summaryFromTransitionApi, transitAccessibilityMapFromTransitionApi } from '../RouteCalculationFromTransition'; +import { + getTimeAndDistanceFromTransitionApi, + summaryFromTransitionApi, + transitAccessibilityMapFromTransitionApi +} from '../RouteCalculationFromTransition'; import projectConfig from '../../../config/projectConfig'; jest.mock('../RouteCalculationFromTransition', () => ({ @@ -17,20 +21,43 @@ jest.mock('../RouteCalculationFromTransition', () => ({ })); const mockedRouteFromTransition = getTimeAndDistanceFromTransitionApi as jest.MockedFunction; const mockedSummaryFromTransition = summaryFromTransitionApi as jest.MockedFunction; -const mockedAccessibilityMapFromTransition = transitAccessibilityMapFromTransitionApi as jest.MockedFunction; +const mockedAccessibilityMapFromTransition = transitAccessibilityMapFromTransitionApi as jest.MockedFunction< + typeof transitAccessibilityMapFromTransitionApi +>; beforeEach(() => { jest.clearAllMocks(); -}) +}); describe('calculateTimeDistanceByMode: invalid parameters', () => { - each([ - ['Invalid origin', 'origin', { type: 'LineString' as const, coordinates: [[0, 0], [1, 1]] }, 'Invalid origin or destination'], - ['Invalid destination', 'destination', { type: 'LineString' as const, coordinates: [[0, 0], [1, 1]] }, 'Invalid origin or destination'], + [ + 'Invalid origin', + 'origin', + { + type: 'LineString' as const, + coordinates: [ + [0, 0], + [1, 1] + ] + }, + 'Invalid origin or destination' + ], + [ + 'Invalid destination', + 'destination', + { + type: 'LineString' as const, + coordinates: [ + [0, 0], + [1, 1] + ] + }, + 'Invalid origin or destination' + ], ['Negative trip time', 'departureSecondsSinceMidnight', -1, 'Invalid departure time'], ['Mixed day/month in date', 'departureDateString', '2024-23-05', 'Invalid trip date'], - ['Invalid date string', 'departureDateString', 'not a date', 'Invalid trip date'], + ['Invalid date string', 'departureDateString', 'not a date', 'Invalid trip date'] ]).test('Invalid parameters: %s', async (_, testedParam, value, expected) => { const validParameters = { origin: { type: 'Point' as const, coordinates: [0, 0] }, @@ -49,21 +76,17 @@ describe('calculateTimeDistanceByMode: invalid parameters', () => { departureSecondsSinceMidnight: 10000, departureDateString: '2024-05-23' }; - await expect(calculateTimeDistanceByMode(['walking', 'transit'], validParameters)).rejects.toThrow('Transit mode requested without a scenario'); + await expect(calculateTimeDistanceByMode(['walking', 'transit'], validParameters)).rejects.toThrow( + 'Transit mode requested without a scenario' + ); }); - }); describe('calculateTimeDistanceByMode with Transition', () => { - beforeEach(() => { - projectConfig.transitionApi = { - url: 'http://transition', - username: 'user', - password: 'password' - }; + projectConfig.transitionApi = { url: 'http://transition', username: 'user', password: 'password' }; }); - + test('Throw an error', async () => { const validParameters = { origin: { type: 'Point' as const, coordinates: [0, 0] }, @@ -84,27 +107,45 @@ describe('calculateTimeDistanceByMode with Transition', () => { departureSecondsSinceMidnight: 10000, departureDateString: '2024-05-23' }; - const routingResults = { - walking: { status: 'success' as const, distanceM: 1000, travelTimeS: 600, source: 'transitionApi' } - } + const routingResults = { walking: { status: 'success' as const, distanceM: 1000, travelTimeS: 600, source: 'transitionApi' } }; mockedRouteFromTransition.mockResolvedValueOnce(routingResults); const result = await calculateTimeDistanceByMode(['walking'], validParameters); expect(mockedRouteFromTransition).toHaveBeenCalledTimes(1); expect(mockedRouteFromTransition).toHaveBeenCalledWith(['walking'], validParameters); expect(result).toEqual(routingResults); }); - }); describe('getTransitSummary: invalid parameters', () => { - each([ - ['Invalid origin', 'origin', { type: 'LineString' as const, coordinates: [[0, 0], [1, 1]] }, 'Invalid origin or destination'], - ['Invalid destination', 'destination', { type: 'LineString' as const, coordinates: [[0, 0], [1, 1]] }, 'Invalid origin or destination'], + [ + 'Invalid origin', + 'origin', + { + type: 'LineString' as const, + coordinates: [ + [0, 0], + [1, 1] + ] + }, + 'Invalid origin or destination' + ], + [ + 'Invalid destination', + 'destination', + { + type: 'LineString' as const, + coordinates: [ + [0, 0], + [1, 1] + ] + }, + 'Invalid origin or destination' + ], ['Negative trip time', 'departureSecondsSinceMidnight', -1, 'Invalid departure time'], ['Mixed day/month in date', 'departureDateString', '2024-23-05', 'Invalid trip date'], ['Invalid date string', 'departureDateString', 'not a date', 'Invalid trip date'], - ['No scenario', 'transitScenario', undefined, 'Transit summary requires a scenario'], + ['No scenario', 'transitScenario', undefined, 'Transit summary requires a scenario'] ]).test('Invalid parameters: %s', async (_, testedParam, value, expected) => { const validParameters = { origin: { type: 'Point' as const, coordinates: [0, 0] }, @@ -116,16 +157,11 @@ describe('getTransitSummary: invalid parameters', () => { const parameters = { ...validParameters, [testedParam]: value }; await expect(getTransitSummary(parameters)).rejects.toThrow(expected); }); - }); describe('getTransitSummary: with Transition', () => { beforeEach(() => { - projectConfig.transitionApi = { - url: 'http://transition', - username: 'user', - password: 'password' - }; + projectConfig.transitionApi = { url: 'http://transition', username: 'user', password: 'password' }; }); test('Throw an error', async () => { @@ -150,23 +186,16 @@ describe('getTransitSummary: with Transition', () => { departureDateString: '2024-05-23', transitScenario: 'scenarioId' }; - const routingResults = { - status: 'success' as const, - nbRoutes: 0, - lines: [], - source: 'transitionApi' - }; + const routingResults = { status: 'success' as const, nbRoutes: 0, lines: [], source: 'transitionApi' }; mockedSummaryFromTransition.mockResolvedValueOnce(routingResults); const result = await getTransitSummary(validParameters); expect(mockedSummaryFromTransition).toHaveBeenCalledTimes(1); expect(mockedSummaryFromTransition).toHaveBeenCalledWith(validParameters); expect(result).toEqual(routingResults); }); - }); describe('getTransitAccessibilityMap: invalid parameters', () => { - const validParameters = { point: { type: 'Point' as const, coordinates: [0, 0] }, departureSecondsSinceMidnight: 10000, @@ -176,7 +205,18 @@ describe('getTransitAccessibilityMap: invalid parameters', () => { }; each([ - ['Invalid point', 'point', { type: 'LineString' as const, coordinates: [[0, 0], [1, 1]] }, 'Invalid point'], + [ + 'Invalid point', + 'point', + { + type: 'LineString' as const, + coordinates: [ + [0, 0], + [1, 1] + ] + }, + 'Invalid point' + ], ['Negative trip time', 'departureSecondsSinceMidnight', -1, 'Invalid departure time'], ['Trip time too large', 'departureSecondsSinceMidnight', 29 * 3600, 'Invalid departure time'], ['No scenario', 'transitScenario', undefined, 'Transit accessibility map requires a scenario'], @@ -184,21 +224,16 @@ describe('getTransitAccessibilityMap: invalid parameters', () => { ['Zero max travel time', 'maxTotalTravelTimeMinutes', 0, 'Invalid max total travel time'], ['Negative max travel time', 'maxTotalTravelTimeMinutes', -10, 'Invalid max total travel time'], ['Zero number of polygons', 'numberOfPolygons', 0, 'Invalid number of polygons'], - ['Negative number of polygons', 'numberOfPolygons', -5, 'Invalid number of polygons'], + ['Negative number of polygons', 'numberOfPolygons', -5, 'Invalid number of polygons'] ]).test('Invalid parameters: %s', async (_, testedParam, value, expected) => { const parameters = { ...validParameters, [testedParam]: value }; await expect(getTransitAccessibilityMap(parameters)).rejects.toThrow(expected); }); - }); describe('getTransitAccessibilityMap: with Transition', () => { beforeEach(() => { - projectConfig.transitionApi = { - url: 'http://transition', - username: 'user', - password: 'password' - }; + projectConfig.transitionApi = { url: 'http://transition', username: 'user', password: 'password' }; }); test('Throw an error', async () => { @@ -232,12 +267,19 @@ describe('getTransitAccessibilityMap: with Transition', () => { type: 'Feature' as const, geometry: { type: 'MultiPolygon' as const, - coordinates: [[[[-73.5, 45.5], [-73.4, 45.5], [-73.4, 45.4], [-73.5, 45.4], [-73.5, 45.5]]]] + coordinates: [ + [ + [ + [-73.5, 45.5], + [-73.4, 45.5], + [-73.4, 45.4], + [-73.5, 45.4], + [-73.5, 45.5] + ] + ] + ] }, - properties: { - durationSeconds: 600, - areaSqM: 1000000 - } + properties: { durationSeconds: 600, areaSqM: 1000000 } } ] }, @@ -267,7 +309,7 @@ describe('No method specified', () => { expect(mockedRouteFromTransition).not.toHaveBeenCalled(); }); - test('getTransitSummary', async () => { + test('getTransitSummary', async () => { const validParameters = { origin: { type: 'Point' as const, coordinates: [0, 0] }, destination: { type: 'Point' as const, coordinates: [0, 0] }, diff --git a/packages/evolution-backend/src/services/routing/__tests__/RouteCalculationFromTransition.test.ts b/packages/evolution-backend/src/services/routing/__tests__/RouteCalculationFromTransition.test.ts index 4146562ad..a39aa0f8e 100644 --- a/packages/evolution-backend/src/services/routing/__tests__/RouteCalculationFromTransition.test.ts +++ b/packages/evolution-backend/src/services/routing/__tests__/RouteCalculationFromTransition.test.ts @@ -4,51 +4,34 @@ * This file is licensed under the MIT License. * License text available at https://opensource.org/licenses/MIT */ -import { getTimeAndDistanceFromTransitionApi, summaryFromTransitionApi, transitAccessibilityMapFromTransitionApi } from '../RouteCalculationFromTransition'; +import { + getTimeAndDistanceFromTransitionApi, + summaryFromTransitionApi, + transitAccessibilityMapFromTransitionApi +} from '../RouteCalculationFromTransition'; import fetchMock from 'jest-fetch-mock'; import projectConfig from '../../../config/projectConfig'; beforeEach(() => { jest.clearAllMocks(); // Default value for transition URL - projectConfig.transitionApi = { - url: 'https://transition.url', - username: 'username', - password: 'password' - }; + projectConfig.transitionApi = { url: 'https://transition.url', username: 'username', password: 'password' }; }); const bearerToken = 'tokenDataFromTransition'; describe('Test various values for the Transition URL', () => { const params = { - origin: { - type: 'Point' as const, - coordinates: [0, 0] - }, - destination: { - type: 'Point' as const, - coordinates: [1, 1] - }, + origin: { type: 'Point' as const, coordinates: [0, 0] }, + destination: { type: 'Point' as const, coordinates: [1, 1] }, departureSecondsSinceMidnight: 0, departureDateString: '2022-01-01' - } - const defaultResponse = { - result: { - 'walking': { - paths: [{ - distanceMeters: 100, - travelTimeSeconds: 120 - }] - } - } - } + }; + const defaultResponse = { result: { walking: { paths: [{ distanceMeters: 100, travelTimeSeconds: 120 }] } } }; test('Undefined URL', async () => { projectConfig.transitionApi = undefined; - await expect(getTimeAndDistanceFromTransitionApi(['walking'], params)) - .rejects - .toThrow('Transition URL not set in project config'); + await expect(getTimeAndDistanceFromTransitionApi(['walking'], params)).rejects.toThrow('Transition URL not set in project config'); expect(fetchMock).not.toHaveBeenCalled(); }); @@ -64,19 +47,13 @@ describe('Test various values for the Transition URL', () => { // Fetch mock should be called twice, once for the token, once for the route expect(fetchMock).toHaveBeenCalledTimes(2); expect(fetchMock).toHaveBeenCalledWith( - 'https://transition.url/token', - expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ usernameOrEmail: 'username', password: 'password' }) - }) + 'https://transition.url/token', + expect.objectContaining({ method: 'POST', body: JSON.stringify({ usernameOrEmail: 'username', password: 'password' }) }) + ); + expect(fetchMock).toHaveBeenCalledWith( + 'https://transition.url/api/v1/route?withGeojson=false', + expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` } }) ); - expect(fetchMock).toHaveBeenCalledWith('https://transition.url/api/v1/route?withGeojson=false', expect.objectContaining({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - } - })); }); test('No HTTP', async () => { @@ -91,19 +68,13 @@ describe('Test various values for the Transition URL', () => { // Fetch mock should be called twice, once for the token, once for the route expect(fetchMock).toHaveBeenCalledTimes(2); expect(fetchMock).toHaveBeenCalledWith( - 'http://transition.url/token', - expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ usernameOrEmail: 'username', password: 'password' }) - }) + 'http://transition.url/token', + expect.objectContaining({ method: 'POST', body: JSON.stringify({ usernameOrEmail: 'username', password: 'password' }) }) + ); + expect(fetchMock).toHaveBeenCalledWith( + 'http://transition.url/api/v1/route?withGeojson=false', + expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` } }) ); - expect(fetchMock).toHaveBeenCalledWith('http://transition.url/api/v1/route?withGeojson=false', expect.objectContaining({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - } - })); }); test('With port', async () => { @@ -118,19 +89,13 @@ describe('Test various values for the Transition URL', () => { // Fetch mock should be called twice, once for the token, once for the route expect(fetchMock).toHaveBeenCalledTimes(2); expect(fetchMock).toHaveBeenCalledWith( - 'https://localhost:8080/token', - expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ usernameOrEmail: 'username', password: 'password' }) - }) + 'https://localhost:8080/token', + expect.objectContaining({ method: 'POST', body: JSON.stringify({ usernameOrEmail: 'username', password: 'password' }) }) + ); + expect(fetchMock).toHaveBeenCalledWith( + 'https://localhost:8080/api/v1/route?withGeojson=false', + expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` } }) ); - expect(fetchMock).toHaveBeenCalledWith('https://localhost:8080/api/v1/route?withGeojson=false', expect.objectContaining({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - } - })); }); test('Expired token', async () => { @@ -149,42 +114,27 @@ describe('Test various values for the Transition URL', () => { // Fetch mock should be called 3 times, once for the token and twice for the route, with different tokens expect(fetchMock).toHaveBeenCalledTimes(3); expect(fetchMock).toHaveBeenCalledWith( - 'http://transition.url/token', - expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ usernameOrEmail: 'username', password: 'password' }) - }) + 'http://transition.url/token', + expect.objectContaining({ method: 'POST', body: JSON.stringify({ usernameOrEmail: 'username', password: 'password' }) }) + ); + expect(fetchMock).toHaveBeenCalledWith( + 'http://transition.url/api/v1/route?withGeojson=false', + expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` } }) + ); + expect(fetchMock).toHaveBeenCalledWith( + 'http://transition.url/api/v1/route?withGeojson=false', + expect.objectContaining({ method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${newBearerToken}` } }) ); - expect(fetchMock).toHaveBeenCalledWith('http://transition.url/api/v1/route?withGeojson=false', expect.objectContaining({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - } - })); - expect(fetchMock).toHaveBeenCalledWith('http://transition.url/api/v1/route?withGeojson=false', expect.objectContaining({ - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${newBearerToken}` - } - })); }); }); describe('getTimeAndDistanceFromTransitionApi: Test various Transition calls', () => { const params = { - origin: { - type: 'Point' as const, - coordinates: [0, 0] - }, - destination: { - type: 'Point' as const, - coordinates: [1, 1] - }, + origin: { type: 'Point' as const, coordinates: [0, 0] }, + destination: { type: 'Point' as const, coordinates: [1, 1] }, departureSecondsSinceMidnight: 0, departureDateString: '2022-01-01' - } + }; test('fetch failing', async () => { fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); @@ -192,12 +142,9 @@ describe('getTimeAndDistanceFromTransitionApi: Test various Transition calls', ( expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith( 'https://transition.url/api/v1/route?withGeojson=false', - expect.objectContaining({ + expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ routingModes: ['walking'], originGeojson: params.origin, @@ -207,9 +154,7 @@ describe('getTimeAndDistanceFromTransitionApi: Test various Transition calls', ( }) }) ); - expect(result).toEqual({ - walking: { status: 'error', error: 'Error: Failed to fetch', source: 'transitionApi' } - }); + expect(result).toEqual({ walking: { status: 'error', error: 'Error: Failed to fetch', source: 'transitionApi' } }); }); test('Bad request response', async () => { @@ -219,12 +164,9 @@ describe('getTimeAndDistanceFromTransitionApi: Test various Transition calls', ( expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith( 'https://transition.url/api/v1/route?withGeojson=false', - expect.objectContaining({ + expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ routingModes: ['walking'], originGeojson: params.origin, @@ -235,7 +177,7 @@ describe('getTimeAndDistanceFromTransitionApi: Test various Transition calls', ( }) ); expect(result).toEqual({ - walking: { status: 'error', error: `Error: Unsuccessful response code from transition: 400`, source: 'transitionApi' } + walking: { status: 'error', error: 'Error: Unsuccessful response code from transition: 400', source: 'transitionApi' } }); }); @@ -245,12 +187,9 @@ describe('getTimeAndDistanceFromTransitionApi: Test various Transition calls', ( expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith( 'https://transition.url/api/v1/route?withGeojson=false', - expect.objectContaining({ + expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ routingModes: ['walking'], originGeojson: params.origin, @@ -261,39 +200,26 @@ describe('getTimeAndDistanceFromTransitionApi: Test various Transition calls', ( }) ); expect(result).toEqual({ - walking: { status: 'error', error: `Error: Unsuccessful response code from transition: 500`, source: 'transitionApi' } + walking: { status: 'error', error: 'Error: Unsuccessful response code from transition: 500', source: 'transitionApi' } }); }); test('Correct response, but somes modes unresponded', async () => { const response = { result: { - 'walking': { - paths: [{ - distanceMeters: 100, - travelTimeSeconds: 120 - }] - }, - 'cycling': { - paths: [{ - distanceMeters: 105, - travelTimeSeconds: 60 - }] - } + walking: { paths: [{ distanceMeters: 100, travelTimeSeconds: 120 }] }, + cycling: { paths: [{ distanceMeters: 105, travelTimeSeconds: 60 }] } } - } + }; fetchMock.mockResponseOnce(JSON.stringify(response)); const result = await getTimeAndDistanceFromTransitionApi(['walking', 'transit', 'cycling'], params); expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith( 'https://transition.url/api/v1/route?withGeojson=false', - expect.objectContaining({ + expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ routingModes: ['walking', 'transit', 'cycling'], originGeojson: params.origin, @@ -313,42 +239,21 @@ describe('getTimeAndDistanceFromTransitionApi: Test various Transition calls', ( test('Correct and complete response', async () => { const response = { result: { - walking: { - paths: [{ - distanceMeters: 100, - travelTimeSeconds: 120 - }] - }, - cycling: { - paths: [{ - distanceMeters: 105, - travelTimeSeconds: 60 - }] - }, - transit: { - paths: [{ - totalTravelTime: 300, - totalDistance: 200, - departureTime: 5 - }] - }, - driving: { - paths: [] - } + walking: { paths: [{ distanceMeters: 100, travelTimeSeconds: 120 }] }, + cycling: { paths: [{ distanceMeters: 105, travelTimeSeconds: 60 }] }, + transit: { paths: [{ totalTravelTime: 300, totalDistance: 200, departureTime: 5 }] }, + driving: { paths: [] } } - } + }; fetchMock.mockResponseOnce(JSON.stringify(response)); const result = await getTimeAndDistanceFromTransitionApi(['walking', 'transit', 'cycling', 'driving'], params); expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith( 'https://transition.url/api/v1/route?withGeojson=false', - expect.objectContaining({ + expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ routingModes: ['walking', 'transit', 'cycling', 'driving'], originGeojson: params.origin, @@ -365,23 +270,16 @@ describe('getTimeAndDistanceFromTransitionApi: Test various Transition calls', ( driving: { status: 'no_routing_found', source: 'transitionApi' } }); }); - }); describe('summaryFromTransitionApi', () => { const params = { - origin: { - type: 'Point' as const, - coordinates: [0, 0] - }, - destination: { - type: 'Point' as const, - coordinates: [1, 1] - }, + origin: { type: 'Point' as const, coordinates: [0, 0] }, + destination: { type: 'Point' as const, coordinates: [1, 1] }, departureSecondsSinceMidnight: 0, departureDateString: '2022-01-01', transitScenario: 'scenarioId' - } + }; test('fetch failing', async () => { fetchMock.mockRejectedValueOnce(new Error('Failed to fetch')); @@ -389,12 +287,9 @@ describe('summaryFromTransitionApi', () => { expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith( 'https://transition.url/api/v1/summary', - expect.objectContaining({ + expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ originGeojson: params.origin, destinationGeojson: params.destination, @@ -413,12 +308,9 @@ describe('summaryFromTransitionApi', () => { expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith( 'https://transition.url/api/v1/summary', - expect.objectContaining({ + expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ originGeojson: params.origin, destinationGeojson: params.destination, @@ -427,7 +319,7 @@ describe('summaryFromTransitionApi', () => { }) }) ); - expect(result).toEqual({ status: 'error', error: `Error: Unsuccessful response code from transition: 400`, source: 'transitionApi' }); + expect(result).toEqual({ status: 'error', error: 'Error: Unsuccessful response code from transition: 400', source: 'transitionApi' }); }); test('Server error response', async () => { @@ -436,12 +328,9 @@ describe('summaryFromTransitionApi', () => { expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith( 'https://transition.url/api/v1/summary', - expect.objectContaining({ + expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ originGeojson: params.origin, destinationGeojson: params.destination, @@ -450,18 +339,13 @@ describe('summaryFromTransitionApi', () => { }) }) ); - expect(result).toEqual({ status: 'error', error: `Error: Unsuccessful response code from transition: 500`, source: 'transitionApi' }); + expect(result).toEqual({ status: 'error', error: 'Error: Unsuccessful response code from transition: 500', source: 'transitionApi' }); }); test('Correct and complete response', async () => { const response = { status: 'success', - query: { - origin: params.origin, - destination: params.destination, - timeOfTrip: params.departureSecondsSinceMidnight, - timeType: 0 - }, + query: { origin: params.origin, destination: params.destination, timeOfTrip: params.departureSecondsSinceMidnight, timeType: 0 }, result: { nbRoutes: 3, lines: [ @@ -485,19 +369,16 @@ describe('summaryFromTransitionApi', () => { } ] } - } + }; fetchMock.mockResponseOnce(JSON.stringify(response)); const result = await summaryFromTransitionApi(params); expect(fetchMock).toHaveBeenCalledTimes(1); expect(fetchMock).toHaveBeenCalledWith( 'https://transition.url/api/v1/summary', - expect.objectContaining({ + expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ originGeojson: params.origin, destinationGeojson: params.destination, @@ -506,22 +387,13 @@ describe('summaryFromTransitionApi', () => { }) }) ); - expect(result).toEqual({ - status: 'success', - nbRoutes: response.result.nbRoutes, - lines: response.result.lines, - source: 'transitionApi' - }); + expect(result).toEqual({ status: 'success', nbRoutes: response.result.nbRoutes, lines: response.result.lines, source: 'transitionApi' }); }); - }); describe('transitAccessibilityMapFromTransitionApi', () => { const params = { - point: { - type: 'Point' as const, - coordinates: [-73.5, 45.5] - }, + point: { type: 'Point' as const, coordinates: [-73.5, 45.5] }, departureSecondsSinceMidnight: 28800, maxTotalTravelTimeMinutes: 30, numberOfPolygons: 3, @@ -530,9 +402,7 @@ describe('transitAccessibilityMapFromTransitionApi', () => { test('Undefined URL', async () => { projectConfig.transitionApi = undefined; - await expect(transitAccessibilityMapFromTransitionApi(params)) - .rejects - .toThrow('Transition URL not set in project config'); + await expect(transitAccessibilityMapFromTransitionApi(params)).rejects.toThrow('Transition URL not set in project config'); expect(fetchMock).not.toHaveBeenCalled(); }); @@ -544,10 +414,7 @@ describe('transitAccessibilityMapFromTransitionApi', () => { 'https://transition.url/api/v1/accessibility?withGeojson=true', expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ locationGeojson: params.point, departureTimeSecondsSinceMidnight: params.departureSecondsSinceMidnight, @@ -570,10 +437,7 @@ describe('transitAccessibilityMapFromTransitionApi', () => { 'https://transition.url/api/v1/accessibility?withGeojson=true', expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ locationGeojson: params.point, departureTimeSecondsSinceMidnight: params.departureSecondsSinceMidnight, @@ -595,10 +459,7 @@ describe('transitAccessibilityMapFromTransitionApi', () => { 'https://transition.url/api/v1/accessibility?withGeojson=true', expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify({ locationGeojson: params.point, departureTimeSecondsSinceMidnight: params.departureSecondsSinceMidnight, @@ -609,27 +470,26 @@ describe('transitAccessibilityMapFromTransitionApi', () => { }) }) ); - expect(result).toEqual({ status: 'error', error: `Error: Unsuccessful response code from transition: 500`, source: 'transitionApi' }); + expect(result).toEqual({ status: 'error', error: 'Error: Unsuccessful response code from transition: 500', source: 'transitionApi' }); }); test.each([ { description: 'without POIs', testParams: params, - expectedParams: { locationGeojson: params.point, departureTimeSecondsSinceMidnight: params.departureSecondsSinceMidnight, scenarioId: params.transitScenario, numberOfPolygons: params.numberOfPolygons, maxTotalTravelTimeSeconds: params.maxTotalTravelTimeMinutes * 60, calculatePois: false }, + expectedParams: { + locationGeojson: params.point, + departureTimeSecondsSinceMidnight: params.departureSecondsSinceMidnight, + scenarioId: params.transitScenario, + numberOfPolygons: params.numberOfPolygons, + maxTotalTravelTimeSeconds: params.maxTotalTravelTimeMinutes * 60, + calculatePois: false + }, response: { result: { nodes: [ - { - id: 'node1', - code: '001', - name: 'Station A' - }, - { - id: 'node2', - code: '002', - name: 'Station B' - } + { id: 'node1', code: '001', name: 'Station A' }, + { id: 'node2', code: '002', name: 'Station B' } ], polygons: { type: 'FeatureCollection' as const, @@ -638,23 +498,37 @@ describe('transitAccessibilityMapFromTransitionApi', () => { type: 'Feature' as const, geometry: { type: 'MultiPolygon' as const, - coordinates: [[[[-73.5, 45.5], [-73.4, 45.5], [-73.4, 45.4], [-73.5, 45.4], [-73.5, 45.5]]]] + coordinates: [ + [ + [ + [-73.5, 45.5], + [-73.4, 45.5], + [-73.4, 45.4], + [-73.5, 45.4], + [-73.5, 45.5] + ] + ] + ] }, - properties: { - durationSeconds: 600, - areaSqM: 1000000 - } + properties: { durationSeconds: 600, areaSqM: 1000000 } }, { type: 'Feature' as const, geometry: { type: 'MultiPolygon' as const, - coordinates: [[[[-73.6, 45.6], [-73.5, 45.6], [-73.5, 45.5], [-73.6, 45.5], [-73.6, 45.6]]]] + coordinates: [ + [ + [ + [-73.6, 45.6], + [-73.5, 45.6], + [-73.5, 45.5], + [-73.6, 45.5], + [-73.6, 45.6] + ] + ] + ] }, - properties: { - durationSeconds: 1200, - areaSqM: 2000000 - } + properties: { durationSeconds: 1200, areaSqM: 2000000 } } ] } @@ -665,16 +539,17 @@ describe('transitAccessibilityMapFromTransitionApi', () => { { description: 'with POIs', testParams: { ...params, calculatePois: true }, - expectedParams: { locationGeojson: params.point, departureTimeSecondsSinceMidnight: params.departureSecondsSinceMidnight, scenarioId: params.transitScenario, numberOfPolygons: params.numberOfPolygons, maxTotalTravelTimeSeconds: params.maxTotalTravelTimeMinutes * 60, calculatePois: true }, + expectedParams: { + locationGeojson: params.point, + departureTimeSecondsSinceMidnight: params.departureSecondsSinceMidnight, + scenarioId: params.transitScenario, + numberOfPolygons: params.numberOfPolygons, + maxTotalTravelTimeSeconds: params.maxTotalTravelTimeMinutes * 60, + calculatePois: true + }, response: { result: { - nodes: [ - { - id: 'node1', - code: '001', - name: 'Station A' - } - ], + nodes: [{ id: 'node1', code: '001', name: 'Station A' }], polygons: { type: 'FeatureCollection' as const, features: [ @@ -682,20 +557,23 @@ describe('transitAccessibilityMapFromTransitionApi', () => { type: 'Feature' as const, geometry: { type: 'MultiPolygon' as const, - coordinates: [[[[-73.5, 45.5], [-73.4, 45.5], [-73.4, 45.4], [-73.5, 45.4], [-73.5, 45.5]]]] + coordinates: [ + [ + [ + [-73.5, 45.5], + [-73.4, 45.5], + [-73.4, 45.4], + [-73.5, 45.4], + [-73.5, 45.5] + ] + ] + ] }, properties: { durationSeconds: 600, areaSqM: 1000000, - accessiblePlacesCountByCategory: { - 'restaurant': 25, - 'cafe': 15 - }, - accessiblePlacesCountByDetailedCategory: { - 'italian_restaurant': 10, - 'french_restaurant': 15, - 'coffee_shop': 15 - } + accessiblePlacesCountByCategory: { restaurant: 25, cafe: 15 }, + accessiblePlacesCountByDetailedCategory: { italian_restaurant: 10, french_restaurant: 15, coffee_shop: 15 } } } ] @@ -703,7 +581,8 @@ describe('transitAccessibilityMapFromTransitionApi', () => { } }, expectPoiVerification: true - }, { + }, + { description: 'with optional parameters', testParams: { ...params, calculatePois: false, maxAccessEgressTravelTimeMinutes: 10, walkingSpeedKmPerHour: 3.6 }, expectedParams: { @@ -719,16 +598,8 @@ describe('transitAccessibilityMapFromTransitionApi', () => { response: { result: { nodes: [ - { - id: 'node1', - code: '001', - name: 'Station A' - }, - { - id: 'node2', - code: '002', - name: 'Station B' - } + { id: 'node1', code: '001', name: 'Station A' }, + { id: 'node2', code: '002', name: 'Station B' } ], polygons: { type: 'FeatureCollection' as const, @@ -737,30 +608,44 @@ describe('transitAccessibilityMapFromTransitionApi', () => { type: 'Feature' as const, geometry: { type: 'MultiPolygon' as const, - coordinates: [[[[-73.5, 45.5], [-73.4, 45.5], [-73.4, 45.4], [-73.5, 45.4], [-73.5, 45.5]]]] + coordinates: [ + [ + [ + [-73.5, 45.5], + [-73.4, 45.5], + [-73.4, 45.4], + [-73.5, 45.4], + [-73.5, 45.5] + ] + ] + ] }, - properties: { - durationSeconds: 600, - areaSqM: 1000000 - } + properties: { durationSeconds: 600, areaSqM: 1000000 } }, { type: 'Feature' as const, geometry: { type: 'MultiPolygon' as const, - coordinates: [[[[-73.6, 45.6], [-73.5, 45.6], [-73.5, 45.5], [-73.6, 45.5], [-73.6, 45.6]]]] + coordinates: [ + [ + [ + [-73.6, 45.6], + [-73.5, 45.6], + [-73.5, 45.5], + [-73.6, 45.5], + [-73.6, 45.6] + ] + ] + ] }, - properties: { - durationSeconds: 1200, - areaSqM: 2000000 - } + properties: { durationSeconds: 1200, areaSqM: 2000000 } } ] } } }, expectPoiVerification: false - }, + } ])('Correct and complete response $description', async ({ testParams, expectedParams, response, expectPoiVerification }) => { fetchMock.mockResponseOnce(JSON.stringify(response)); const result = await transitAccessibilityMapFromTransitionApi(testParams); @@ -769,26 +654,15 @@ describe('transitAccessibilityMapFromTransitionApi', () => { 'https://transition.url/api/v1/accessibility?withGeojson=true', expect.objectContaining({ method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${bearerToken}` - }, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${bearerToken}` }, body: JSON.stringify(expectedParams) }) ); - expect(result).toEqual({ - status: 'success', - polygons: response.result.polygons, - source: 'transitionApi' - }); - + expect(result).toEqual({ status: 'success', polygons: response.result.polygons, source: 'transitionApi' }); + // Verify POI data is preserved when expected if (expectPoiVerification && result.status === 'success' && result.polygons.features[0].properties) { - expect(result.polygons.features[0].properties.accessiblePlacesCountByCategory).toEqual({ - 'restaurant': 25, - 'cafe': 15 - }); + expect(result.polygons.features[0].properties.accessiblePlacesCountByCategory).toEqual({ restaurant: 25, cafe: 15 }); } }); - -}); \ No newline at end of file +}); diff --git a/packages/evolution-backend/src/services/surveyObjects/__tests__/JourneyFactory.test.ts b/packages/evolution-backend/src/services/surveyObjects/__tests__/JourneyFactory.test.ts index 89462442d..3039743b0 100644 --- a/packages/evolution-backend/src/services/surveyObjects/__tests__/JourneyFactory.test.ts +++ b/packages/evolution-backend/src/services/surveyObjects/__tests__/JourneyFactory.test.ts @@ -16,11 +16,7 @@ import { createOk, createErrors } from 'evolution-common/lib/types/Result.type'; import { SurveyObjectsRegistry } from 'evolution-common/lib/services/baseObjects/SurveyObjectsRegistry'; // Mock dependencies -jest.mock('evolution-common/lib/services/baseObjects/Journey', () => ({ - Journey: { - create: jest.fn() - } -})); +jest.mock('evolution-common/lib/services/baseObjects/Journey', () => ({ Journey: { create: jest.fn() } })); jest.mock('../VisitedPlaceFactory'); jest.mock('../TripFactory'); @@ -56,35 +52,17 @@ describe('JourneyFactory', () => { } }; - person = { - _uuid: 'person-uuid', - addJourney: jest.fn() - } as unknown as Person; + person = { _uuid: 'person-uuid', addJourney: jest.fn() } as unknown as Person; personAttributes = { _uuid: 'person-uuid', journeys: { - 'journey-1': { - _uuid: 'journey-1', - _sequence: 1, - name: 'Work Day', - visitedPlaces: {}, - trips: {} - }, - 'journey-2': { - _uuid: 'journey-2', - _sequence: 2, - name: 'Weekend', - visitedPlaces: {}, - trips: {} - } + 'journey-1': { _uuid: 'journey-1', _sequence: 1, name: 'Work Day', visitedPlaces: {}, trips: {} }, + 'journey-2': { _uuid: 'journey-2', _sequence: 2, name: 'Weekend', visitedPlaces: {}, trips: {} } } } as unknown as ExtendedPersonAttributes; - home = { - _uuid: 'home-uuid', - geography: { type: 'Point', coordinates: [0, 0] } - } as unknown as Home; + home = { _uuid: 'home-uuid', geography: { type: 'Point', coordinates: [0, 0] } } as unknown as Home; // Clear all mocks jest.clearAllMocks(); @@ -92,19 +70,11 @@ describe('JourneyFactory', () => { describe('populateJourneysForPerson', () => { it('should create journeys successfully and add them to person', async () => { - const mockJourney1 = { - _uuid: 'journey-1', - name: 'Work Day' - } as Journey; + const mockJourney1 = { _uuid: 'journey-1', name: 'Work Day' } as Journey; - const mockJourney2 = { - _uuid: 'journey-2', - name: 'Weekend' - } as Journey; + const mockJourney2 = { _uuid: 'journey-2', name: 'Weekend' } as Journey; - (MockedJourney.create as jest.Mock) - .mockReturnValueOnce(createOk(mockJourney1)) - .mockReturnValueOnce(createOk(mockJourney2)); + (MockedJourney.create as jest.Mock).mockReturnValueOnce(createOk(mockJourney1)).mockReturnValueOnce(createOk(mockJourney2)); mockedpopulateVisitedPlacesForJourney.mockResolvedValue(); mockedpopulateTripsForJourney.mockResolvedValue(); @@ -114,19 +84,11 @@ describe('JourneyFactory', () => { // Verify Journey.create was called with correct attributes (visitedPlaces and trips are omitted) expect(MockedJourney.create).toHaveBeenCalledTimes(2); expect(MockedJourney.create).toHaveBeenCalledWith( - expect.objectContaining({ - _uuid: 'journey-1', - _sequence: 1, - name: 'Work Day' - }), + expect.objectContaining({ _uuid: 'journey-1', _sequence: 1, name: 'Work Day' }), surveyObjectsRegistry ); expect(MockedJourney.create).toHaveBeenCalledWith( - expect.objectContaining({ - _uuid: 'journey-2', - _sequence: 2, - name: 'Weekend' - }), + expect.objectContaining({ _uuid: 'journey-2', _sequence: 2, name: 'Weekend' }), surveyObjectsRegistry ); @@ -146,10 +108,9 @@ describe('JourneyFactory', () => { it('should handle journey creation errors', async () => { const errors = [new Error('Invalid journey data')]; - (MockedJourney.create as jest.Mock).mockReturnValueOnce(createErrors(errors)) - .mockReturnValueOnce(createOk({ - _uuid: 'journey-2' - } as Journey)); + (MockedJourney.create as jest.Mock) + .mockReturnValueOnce(createErrors(errors)) + .mockReturnValueOnce(createOk({ _uuid: 'journey-2' } as Journey)); mockedpopulateVisitedPlacesForJourney.mockResolvedValue(); mockedpopulateTripsForJourney.mockResolvedValue(); @@ -169,19 +130,11 @@ describe('JourneyFactory', () => { it('should skip journeys with undefined uuid', async () => { personAttributes.journeys = { - 'undefined': { - _uuid: 'undefined', - name: 'Invalid' - }, - 'journey-1': { - _uuid: 'journey-1', - name: 'Valid' - } + undefined: { _uuid: 'undefined', name: 'Invalid' }, + 'journey-1': { _uuid: 'journey-1', name: 'Valid' } } as any; - (MockedJourney.create as jest.Mock).mockReturnValue(createOk({ - _uuid: 'journey-1' - } as Journey)); + (MockedJourney.create as jest.Mock).mockReturnValue(createOk({ _uuid: 'journey-1' } as Journey)); mockedpopulateVisitedPlacesForJourney.mockResolvedValue(); mockedpopulateTripsForJourney.mockResolvedValue(); @@ -205,24 +158,13 @@ describe('JourneyFactory', () => { it('should sort journeys by sequence', async () => { // Create journeys with mixed sequence order personAttributes.journeys = { - 'journey-3': { - _uuid: 'journey-3', - _sequence: 3, - name: 'Third' - }, - 'journey-1': { - _uuid: 'journey-1', - _sequence: 1, - name: 'First' - }, - 'journey-2': { - _uuid: 'journey-2', - _sequence: 2, - name: 'Second' - } + 'journey-3': { _uuid: 'journey-3', _sequence: 3, name: 'Third' }, + 'journey-1': { _uuid: 'journey-1', _sequence: 1, name: 'First' }, + 'journey-2': { _uuid: 'journey-2', _sequence: 2, name: 'Second' } } as any; - (MockedJourney.create as jest.Mock).mockReturnValueOnce(createOk({ _uuid: 'journey-1' } as Journey)) + (MockedJourney.create as jest.Mock) + .mockReturnValueOnce(createOk({ _uuid: 'journey-1' } as Journey)) .mockReturnValueOnce(createOk({ _uuid: 'journey-2' } as Journey)) .mockReturnValueOnce(createOk({ _uuid: 'journey-3' } as Journey)); @@ -238,9 +180,7 @@ describe('JourneyFactory', () => { }); it('should pass correct parameters to nested factory functions', async () => { - const mockJourney = { - _uuid: 'journey-1' - } as Journey; + const mockJourney = { _uuid: 'journey-1' } as Journey; (MockedJourney.create as jest.Mock).mockReturnValue(createOk(mockJourney)); mockedpopulateVisitedPlacesForJourney.mockResolvedValue(); diff --git a/packages/evolution-backend/src/services/surveyObjects/__tests__/PersonFactory.test.ts b/packages/evolution-backend/src/services/surveyObjects/__tests__/PersonFactory.test.ts index 4385cc377..312b10036 100644 --- a/packages/evolution-backend/src/services/surveyObjects/__tests__/PersonFactory.test.ts +++ b/packages/evolution-backend/src/services/surveyObjects/__tests__/PersonFactory.test.ts @@ -15,11 +15,7 @@ import { createOk, createErrors } from 'evolution-common/lib/types/Result.type'; import { SurveyObjectsRegistry } from 'evolution-common/lib/services/baseObjects/SurveyObjectsRegistry'; // Mock Person.create -jest.mock('evolution-common/lib/services/baseObjects/Person', () => ({ - Person: { - create: jest.fn() - } -})); +jest.mock('evolution-common/lib/services/baseObjects/Person', () => ({ Person: { create: jest.fn() } })); const MockedPerson = Person as jest.MockedClass; describe('PersonFactory', () => { @@ -49,27 +45,14 @@ describe('PersonFactory', () => { } }; - household = new Household({ - _uuid: uuidV4(), - size: 2 - }, surveyObjectsRegistry); + household = new Household({ _uuid: uuidV4(), size: 2 }, surveyObjectsRegistry); household.members = []; correctedResponse = { household: { persons: { - 'person-1': { - _uuid: 'person-1', - _sequence: 1, - age: 30, - gender: 'male' - }, - 'person-2': { - _uuid: 'person-2', - _sequence: 2, - age: 25, - gender: 'female' - } + 'person-1': { _uuid: 'person-1', _sequence: 1, age: 30, gender: 'male' }, + 'person-2': { _uuid: 'person-2', _sequence: 2, age: 25, gender: 'female' } } } } as unknown as CorrectedResponse; @@ -83,42 +66,22 @@ describe('PersonFactory', () => { describe('populatePersonsForHousehold', () => { it('should create persons successfully and add them to household', async () => { // Mock successful person creation - const mockPerson1 = { - _uuid: 'person-1', - age: 30, - assignColor: jest.fn() - } as unknown as Person; - - const mockPerson2 = { - _uuid: 'person-2', - age: 25, - assignColor: jest.fn() - } as unknown as Person; + const mockPerson1 = { _uuid: 'person-1', age: 30, assignColor: jest.fn() } as unknown as Person; - (MockedPerson.create as jest.Mock) - .mockReturnValueOnce(createOk(mockPerson1)) - .mockReturnValueOnce(createOk(mockPerson2)); + const mockPerson2 = { _uuid: 'person-2', age: 25, assignColor: jest.fn() } as unknown as Person; + + (MockedPerson.create as jest.Mock).mockReturnValueOnce(createOk(mockPerson1)).mockReturnValueOnce(createOk(mockPerson2)); await populatePersonsForHousehold(surveyObjectsWithErrors, household, correctedResponse, surveyObjectsRegistry); // Verify Person.create was called with correct attributes expect(MockedPerson.create).toHaveBeenCalledTimes(2); expect(MockedPerson.create).toHaveBeenCalledWith( - expect.objectContaining({ - _uuid: 'person-1', - _sequence: 1, - age: 30, - gender: 'male' - }), + expect.objectContaining({ _uuid: 'person-1', _sequence: 1, age: 30, gender: 'male' }), surveyObjectsRegistry ); expect(MockedPerson.create).toHaveBeenCalledWith( - expect.objectContaining({ - _uuid: 'person-2', - _sequence: 2, - age: 25, - gender: 'female' - }), + expect.objectContaining({ _uuid: 'person-2', _sequence: 2, age: 25, gender: 'female' }), surveyObjectsRegistry ); @@ -140,10 +103,7 @@ describe('PersonFactory', () => { (MockedPerson.create as jest.Mock) .mockReturnValueOnce(createErrors(errors)) - .mockReturnValueOnce(createOk({ - _uuid: 'person-2', - assignColor: jest.fn() - } as unknown as Person)); + .mockReturnValueOnce(createOk({ _uuid: 'person-2', assignColor: jest.fn() } as unknown as Person)); await populatePersonsForHousehold(surveyObjectsWithErrors, household, correctedResponse, surveyObjectsRegistry); @@ -157,22 +117,11 @@ describe('PersonFactory', () => { it('should skip persons with undefined uuid', async () => { correctedResponse.household!.persons = { - 'undefined': { - _sequence: 1, - _uuid: 'undefined', - age: 30 - }, - 'person-1': { - _sequence: 1, - _uuid: 'person-1', - age: 25 - } + undefined: { _sequence: 1, _uuid: 'undefined', age: 30 }, + 'person-1': { _sequence: 1, _uuid: 'person-1', age: 25 } }; - (MockedPerson.create as jest.Mock).mockReturnValue(createOk({ - _uuid: 'person-1', - assignColor: jest.fn() - } as unknown as Person)); + (MockedPerson.create as jest.Mock).mockReturnValue(createOk({ _uuid: 'person-1', assignColor: jest.fn() } as unknown as Person)); await populatePersonsForHousehold(surveyObjectsWithErrors, household, correctedResponse, surveyObjectsRegistry); @@ -201,21 +150,9 @@ describe('PersonFactory', () => { it('should sort persons by sequence', async () => { // Create persons with mixed sequence order correctedResponse.household!.persons = { - 'person-3': { - _uuid: 'person-3', - _sequence: 3, - age: 40 - }, - 'person-1': { - _uuid: 'person-1', - _sequence: 1, - age: 30 - }, - 'person-2': { - _uuid: 'person-2', - _sequence: 2, - age: 25 - } + 'person-3': { _uuid: 'person-3', _sequence: 3, age: 40 }, + 'person-1': { _uuid: 'person-1', _sequence: 1, age: 30 }, + 'person-2': { _uuid: 'person-2', _sequence: 2, age: 25 } }; (MockedPerson.create as jest.Mock) diff --git a/packages/evolution-backend/src/services/surveyObjects/__tests__/SegmentFactory.test.ts b/packages/evolution-backend/src/services/surveyObjects/__tests__/SegmentFactory.test.ts index 87fc9f9d2..be9151742 100644 --- a/packages/evolution-backend/src/services/surveyObjects/__tests__/SegmentFactory.test.ts +++ b/packages/evolution-backend/src/services/surveyObjects/__tests__/SegmentFactory.test.ts @@ -13,11 +13,7 @@ import { createOk, createErrors } from 'evolution-common/lib/types/Result.type'; import { SurveyObjectsRegistry } from 'evolution-common/lib/services/baseObjects/SurveyObjectsRegistry'; // Mock Segment.create -jest.mock('evolution-common/lib/services/baseObjects/Segment', () => ({ - Segment: { - create: jest.fn() - } -})); +jest.mock('evolution-common/lib/services/baseObjects/Segment', () => ({ Segment: { create: jest.fn() } })); const MockedSegment = Segment as jest.MockedClass; describe('SegmentFactory', () => { @@ -47,26 +43,13 @@ describe('SegmentFactory', () => { } }; - trip = { - _uuid: 'trip-uuid', - addSegment: jest.fn() - } as unknown as Trip; + trip = { _uuid: 'trip-uuid', addSegment: jest.fn() } as unknown as Trip; tripAttributes = { _uuid: 'trip-uuid', segments: { - 'segment-1': { - _uuid: 'segment-1', - _sequence: 1, - mode: 'walk', - distanceMeters: 500 - }, - 'segment-2': { - _uuid: 'segment-2', - _sequence: 2, - mode: 'bus', - distanceMeters: 2000 - } + 'segment-1': { _uuid: 'segment-1', _sequence: 1, mode: 'walk', distanceMeters: 500 }, + 'segment-2': { _uuid: 'segment-2', _sequence: 2, mode: 'bus', distanceMeters: 2000 } } } as unknown as ExtendedTripAttributes; @@ -76,39 +59,22 @@ describe('SegmentFactory', () => { describe('populateSegmentsForTrip', () => { it('should create segments successfully and add them to trip', async () => { - const mockSegment1 = { - _uuid: 'segment-1', - mode: 'walk' - } as Segment; + const mockSegment1 = { _uuid: 'segment-1', mode: 'walk' } as Segment; - const mockSegment2 = { - _uuid: 'segment-2', - mode: 'bus' - } as unknown as Segment; + const mockSegment2 = { _uuid: 'segment-2', mode: 'bus' } as unknown as Segment; - (MockedSegment.create as jest.Mock).mockReturnValueOnce(createOk(mockSegment1)) - .mockReturnValueOnce(createOk(mockSegment2)); + (MockedSegment.create as jest.Mock).mockReturnValueOnce(createOk(mockSegment1)).mockReturnValueOnce(createOk(mockSegment2)); await populateSegmentsForTrip(surveyObjectsWithErrors, trip, tripAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); // Verify Segment.create was called with correct attributes expect(MockedSegment.create).toHaveBeenCalledTimes(2); expect(MockedSegment.create).toHaveBeenCalledWith( - expect.objectContaining({ - _uuid: 'segment-1', - _sequence: 1, - mode: 'walk', - distanceMeters: 500 - }), + expect.objectContaining({ _uuid: 'segment-1', _sequence: 1, mode: 'walk', distanceMeters: 500 }), surveyObjectsRegistry ); expect(MockedSegment.create).toHaveBeenCalledWith( - expect.objectContaining({ - _uuid: 'segment-2', - _sequence: 2, - mode: 'bus', - distanceMeters: 2000 - }), + expect.objectContaining({ _uuid: 'segment-2', _sequence: 2, mode: 'bus', distanceMeters: 2000 }), surveyObjectsRegistry ); @@ -124,10 +90,9 @@ describe('SegmentFactory', () => { it('should handle segment creation errors', async () => { const errors = [new Error('Invalid segment data')]; - (MockedSegment.create as jest.Mock).mockReturnValueOnce(createErrors(errors)) - .mockReturnValueOnce(createOk({ - _uuid: 'segment-2' - } as unknown as Segment)); + (MockedSegment.create as jest.Mock) + .mockReturnValueOnce(createErrors(errors)) + .mockReturnValueOnce(createOk({ _uuid: 'segment-2' } as unknown as Segment)); await populateSegmentsForTrip(surveyObjectsWithErrors, trip, tripAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); @@ -139,20 +104,9 @@ describe('SegmentFactory', () => { }); it('should skip segments with undefined uuid', async () => { - tripAttributes.segments = { - 'undefined': { - _uuid: 'undefined', - mode: 'walk' - }, - 'segment-1': { - _uuid: 'segment-1', - mode: 'bus' - } - } as any; + tripAttributes.segments = { undefined: { _uuid: 'undefined', mode: 'walk' }, 'segment-1': { _uuid: 'segment-1', mode: 'bus' } } as any; - (MockedSegment.create as jest.Mock).mockReturnValue(createOk({ - _uuid: 'segment-1' - } as Segment)); + (MockedSegment.create as jest.Mock).mockReturnValue(createOk({ _uuid: 'segment-1' } as Segment)); await populateSegmentsForTrip(surveyObjectsWithErrors, trip, tripAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); @@ -182,24 +136,13 @@ describe('SegmentFactory', () => { it('should sort segments by sequence', async () => { // Create segments with mixed sequence order tripAttributes.segments = { - 'segment-3': { - _uuid: 'segment-3', - _sequence: 3, - mode: 'car' - }, - 'segment-1': { - _uuid: 'segment-1', - _sequence: 1, - mode: 'walk' - }, - 'segment-2': { - _uuid: 'segment-2', - _sequence: 2, - mode: 'bus' - } + 'segment-3': { _uuid: 'segment-3', _sequence: 3, mode: 'car' }, + 'segment-1': { _uuid: 'segment-1', _sequence: 1, mode: 'walk' }, + 'segment-2': { _uuid: 'segment-2', _sequence: 2, mode: 'bus' } } as any; - (MockedSegment.create as jest.Mock).mockReturnValueOnce(createOk({ _uuid: 'segment-1' } as Segment)) + (MockedSegment.create as jest.Mock) + .mockReturnValueOnce(createOk({ _uuid: 'segment-1' } as Segment)) .mockReturnValueOnce(createOk({ _uuid: 'segment-2' } as Segment)) .mockReturnValueOnce(createOk({ _uuid: 'segment-3' } as Segment)); @@ -218,19 +161,12 @@ describe('SegmentFactory', () => { mode: 'walk' // No _sequence property }, - 'segment-zero': { - _uuid: 'segment-zero', - _sequence: 0, - mode: 'bus' - }, - 'segment-1': { - _uuid: 'segment-1', - _sequence: 1, - mode: 'car' - } + 'segment-zero': { _uuid: 'segment-zero', _sequence: 0, mode: 'bus' }, + 'segment-1': { _uuid: 'segment-1', _sequence: 1, mode: 'car' } } as any; - (MockedSegment.create as jest.Mock).mockReturnValueOnce(createOk({ _uuid: 'segment-no-seq' } as Segment)) + (MockedSegment.create as jest.Mock) + .mockReturnValueOnce(createOk({ _uuid: 'segment-no-seq' } as Segment)) .mockReturnValueOnce(createOk({ _uuid: 'segment-zero' } as Segment)) .mockReturnValueOnce(createOk({ _uuid: 'segment-1' } as Segment)); @@ -243,26 +179,10 @@ describe('SegmentFactory', () => { it('should handle segments with various transport modes', async () => { tripAttributes.segments = { - 'walk-segment': { - _uuid: 'walk-segment', - _sequence: 1, - mode: 'walk' - }, - 'transit-segment': { - _uuid: 'transit-segment', - _sequence: 2, - mode: 'transit' - }, - 'car-segment': { - _uuid: 'car-segment', - _sequence: 3, - mode: 'carDriver' - }, - 'bike-segment': { - _uuid: 'bike-segment', - _sequence: 4, - mode: 'bicycle' - } + 'walk-segment': { _uuid: 'walk-segment', _sequence: 1, mode: 'walk' }, + 'transit-segment': { _uuid: 'transit-segment', _sequence: 2, mode: 'transit' }, + 'car-segment': { _uuid: 'car-segment', _sequence: 3, mode: 'carDriver' }, + 'bike-segment': { _uuid: 'bike-segment', _sequence: 4, mode: 'bicycle' } } as any; (MockedSegment.create as jest.Mock).mockReturnValue(createOk({} as Segment)); diff --git a/packages/evolution-backend/src/services/surveyObjects/__tests__/SurveyObjectsFactory.test.ts b/packages/evolution-backend/src/services/surveyObjects/__tests__/SurveyObjectsFactory.test.ts index a87661310..c6424e833 100644 --- a/packages/evolution-backend/src/services/surveyObjects/__tests__/SurveyObjectsFactory.test.ts +++ b/packages/evolution-backend/src/services/surveyObjects/__tests__/SurveyObjectsFactory.test.ts @@ -19,16 +19,8 @@ import { SurveyObjectsRegistry } from 'evolution-common/lib/services/baseObjects jest.mock('../PersonFactory'); jest.mock('../JourneyFactory'); jest.mock('evolution-common/lib/services/baseObjects/interview/InterviewUnserializer'); -jest.mock('evolution-common/lib/services/baseObjects/Home', () => ({ - Home: { - create: jest.fn() - } -})); -jest.mock('evolution-common/lib/services/baseObjects/Household', () => ({ - Household: { - create: jest.fn() - } -})); +jest.mock('evolution-common/lib/services/baseObjects/Home', () => ({ Home: { create: jest.fn() } })); +jest.mock('evolution-common/lib/services/baseObjects/Household', () => ({ Household: { create: jest.fn() } })); const mockedpopulatePersonsForHousehold = populatePersonsForHousehold as jest.MockedFunction; const mockedPopulateJourneysForPerson = populateJourneysForPerson as jest.MockedFunction; @@ -46,23 +38,9 @@ describe('SurveyObjectsFactory', () => { interviewAttributes = { uuid: 'interview-uuid', corrected_response: { - interview: { - _uuid: 'interview-uuid' - }, - home: { - _uuid: 'home-uuid', - geography: { type: 'Point', coordinates: [-73.5, 45.5] } - }, - household: { - _uuid: 'household-uuid', - size: 2, - persons: { - 'person-1': { - _uuid: 'person-1', - age: 30 - } - } - } + interview: { _uuid: 'interview-uuid' }, + home: { _uuid: 'home-uuid', geography: { type: 'Point', coordinates: [-73.5, 45.5] } }, + household: { _uuid: 'household-uuid', size: 2, persons: { 'person-1': { _uuid: 'person-1', age: 30 } } } } as unknown as CorrectedResponse } as unknown as InterviewAttributes; @@ -73,14 +51,8 @@ describe('SurveyObjectsFactory', () => { describe('createAllObjectsWithErrors', () => { it('should create all objects successfully', async () => { const mockInterview = { _uuid: 'interview-uuid' }; - const mockHome = { - _uuid: 'home-uuid', - geography: { type: 'Point', coordinates: [-73.5, 45.5] } - }; - const mockHousehold = { - _uuid: 'household-uuid', - members: [{ _uuid: 'person-1', setupWorkAndSchoolPlaces: jest.fn() }] - }; + const mockHome = { _uuid: 'home-uuid', geography: { type: 'Point', coordinates: [-73.5, 45.5] } }; + const mockHousehold = { _uuid: 'household-uuid', members: [{ _uuid: 'person-1', setupWorkAndSchoolPlaces: jest.fn() }] }; mockedCreateInterviewObject.mockReturnValue(createOk(mockInterview as any)); (MockedHome.create as jest.Mock).mockReturnValue(createOk(mockHome as any)); @@ -266,10 +238,7 @@ describe('SurveyObjectsFactory', () => { }); it('should handle empty persons object', async () => { - const mockHousehold = { - _uuid: 'household-uuid', - members: [] - }; + const mockHousehold = { _uuid: 'household-uuid', members: [] }; interviewAttributes.corrected_response!.household!.persons = {}; @@ -285,10 +254,7 @@ describe('SurveyObjectsFactory', () => { }); it('should initialize with correct factory options', () => { - const customOptions = { - calculateRouting: false, - forceUpdateRouting: true - }; + const customOptions = { calculateRouting: false, forceUpdateRouting: true }; const customFactory = new SurveyObjectsFactory(customOptions); diff --git a/packages/evolution-backend/src/services/surveyObjects/__tests__/TripFactory.test.ts b/packages/evolution-backend/src/services/surveyObjects/__tests__/TripFactory.test.ts index 0ff6750ea..896e16db7 100644 --- a/packages/evolution-backend/src/services/surveyObjects/__tests__/TripFactory.test.ts +++ b/packages/evolution-backend/src/services/surveyObjects/__tests__/TripFactory.test.ts @@ -16,18 +16,13 @@ import { createOk, createErrors } from 'evolution-common/lib/types/Result.type'; import { SurveyObjectsRegistry } from 'evolution-common/lib/services/baseObjects/SurveyObjectsRegistry'; // Mock dependencies -jest.mock('evolution-common/lib/services/baseObjects/Trip', () => ({ - Trip: { - create: jest.fn() - } -})); +jest.mock('evolution-common/lib/services/baseObjects/Trip', () => ({ Trip: { create: jest.fn() } })); jest.mock('../SegmentFactory'); const MockedTrip = Trip as jest.MockedClass; const mockedpopulateSegmentsForTrip = populateSegmentsForTrip as jest.MockedFunction; describe('TripFactory', () => { - let surveyObjectsRegistry: SurveyObjectsRegistry; let surveyObjectsWithErrors: SurveyObjectsWithErrors; let person: Person; @@ -55,28 +50,20 @@ describe('TripFactory', () => { } }; - const mockOrigin = { - _uuid: 'origin-vp-uuid' - } as VisitedPlace; + const mockOrigin = { _uuid: 'origin-vp-uuid' } as VisitedPlace; - const mockDestination = { - _uuid: 'destination-vp-uuid' - } as VisitedPlace; + const mockDestination = { _uuid: 'destination-vp-uuid' } as VisitedPlace; person = { _uuid: 'person-uuid', - findVisitedPlaceByUuid: jest.fn() - .mockImplementation((uuid: string) => { - if (uuid === 'origin-vp-uuid') return mockOrigin; - if (uuid === 'destination-vp-uuid') return mockDestination; - return undefined; - }) + findVisitedPlaceByUuid: jest.fn().mockImplementation((uuid: string) => { + if (uuid === 'origin-vp-uuid') return mockOrigin; + if (uuid === 'destination-vp-uuid') return mockDestination; + return undefined; + }) } as unknown as Person; - journey = { - _uuid: 'journey-uuid', - addTrip: jest.fn() - } as unknown as Journey; + journey = { _uuid: 'journey-uuid', addTrip: jest.fn() } as unknown as Journey; journeyAttributes = { _uuid: 'journey-uuid', @@ -122,12 +109,18 @@ describe('TripFactory', () => { setupStartAndEndTimes: jest.fn() } as unknown as Trip; - (MockedTrip.create as jest.Mock).mockReturnValueOnce(createOk(mockTrip1)) - .mockReturnValueOnce(createOk(mockTrip2)); + (MockedTrip.create as jest.Mock).mockReturnValueOnce(createOk(mockTrip1)).mockReturnValueOnce(createOk(mockTrip2)); mockedpopulateSegmentsForTrip.mockResolvedValue(); - await populateTripsForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateTripsForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify Trip.create was called with correct attributes (segments are omitted) expect(MockedTrip.create).toHaveBeenCalledTimes(2); @@ -174,7 +167,14 @@ describe('TripFactory', () => { (MockedTrip.create as jest.Mock).mockReturnValue(createOk(mockTrip)); mockedpopulateSegmentsForTrip.mockResolvedValue(); - await populateTripsForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateTripsForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify findVisitedPlaceByUuid was called for origin and destination expect(person.findVisitedPlaceByUuid).toHaveBeenCalledWith('origin-vp-uuid'); @@ -200,7 +200,14 @@ describe('TripFactory', () => { (MockedTrip.create as jest.Mock).mockReturnValue(createOk(mockTrip)); mockedpopulateSegmentsForTrip.mockResolvedValue(); - await populateTripsForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateTripsForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify origin and destination remain null expect(mockTrip.origin).toBeNull(); @@ -223,7 +230,14 @@ describe('TripFactory', () => { (MockedTrip.create as jest.Mock).mockReturnValue(createOk(mockTrip)); mockedpopulateSegmentsForTrip.mockResolvedValue(); - await populateTripsForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateTripsForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify the method was called expect(mockTrip.getSegmentsWithoutWalkingInMultimode).toHaveBeenCalled(); @@ -235,19 +249,29 @@ describe('TripFactory', () => { it('should handle trip creation errors', async () => { const errors = [new Error('Invalid trip data')]; - (MockedTrip.create as jest.Mock).mockReturnValueOnce(createErrors(errors)) - .mockReturnValueOnce(createOk({ - _uuid: 'trip-2', - origin: null, - destination: null, - segments: [], - getSegmentsWithoutWalkingInMultimode: jest.fn().mockReturnValue([]), - setupStartAndEndTimes: jest.fn() - } as unknown as Trip)); + (MockedTrip.create as jest.Mock) + .mockReturnValueOnce(createErrors(errors)) + .mockReturnValueOnce( + createOk({ + _uuid: 'trip-2', + origin: null, + destination: null, + segments: [], + getSegmentsWithoutWalkingInMultimode: jest.fn().mockReturnValue([]), + setupStartAndEndTimes: jest.fn() + } as unknown as Trip) + ); mockedpopulateSegmentsForTrip.mockResolvedValue(); - await populateTripsForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateTripsForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify error was stored expect(surveyObjectsWithErrors.errorsByObject.tripsByUuid['trip-1']).toEqual(errors); @@ -260,26 +284,26 @@ describe('TripFactory', () => { }); it('should skip trips with undefined uuid', async () => { - journeyAttributes.trips = { - 'undefined': { - _uuid: 'undefined', - _sequence: 1 - }, - 'trip-1': { - _uuid: 'trip-1', - _sequence: 2 - } - } as any; + journeyAttributes.trips = { undefined: { _uuid: 'undefined', _sequence: 1 }, 'trip-1': { _uuid: 'trip-1', _sequence: 2 } } as any; - (MockedTrip.create as jest.Mock).mockReturnValue(createOk({ - _uuid: 'trip-1', - getSegmentsWithoutWalkingInMultimode: jest.fn().mockReturnValue([]), - setupStartAndEndTimes: jest.fn() - } as unknown as Trip)); + (MockedTrip.create as jest.Mock).mockReturnValue( + createOk({ + _uuid: 'trip-1', + getSegmentsWithoutWalkingInMultimode: jest.fn().mockReturnValue([]), + setupStartAndEndTimes: jest.fn() + } as unknown as Trip) + ); mockedpopulateSegmentsForTrip.mockResolvedValue(); - await populateTripsForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateTripsForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Should only create one trip (skip undefined) expect(MockedTrip.create).toHaveBeenCalledTimes(1); @@ -289,7 +313,14 @@ describe('TripFactory', () => { it('should handle missing trips attributes', async () => { journeyAttributes.trips = undefined; - await populateTripsForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateTripsForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); expect(MockedTrip.create).not.toHaveBeenCalled(); expect(journey.addTrip).not.toHaveBeenCalled(); @@ -298,39 +329,44 @@ describe('TripFactory', () => { it('should sort trips by sequence', async () => { // Create trips with mixed sequence order journeyAttributes.trips = { - 'trip-3': { - _uuid: 'trip-3', - _sequence: 3 - }, - 'trip-1': { - _uuid: 'trip-1', - _sequence: 1 - }, - 'trip-2': { - _uuid: 'trip-2', - _sequence: 2 - } + 'trip-3': { _uuid: 'trip-3', _sequence: 3 }, + 'trip-1': { _uuid: 'trip-1', _sequence: 1 }, + 'trip-2': { _uuid: 'trip-2', _sequence: 2 } } as any; - (MockedTrip.create as jest.Mock).mockReturnValueOnce(createOk({ - _uuid: 'trip-1', - getSegmentsWithoutWalkingInMultimode: jest.fn().mockReturnValue([]), - setupStartAndEndTimes: jest.fn() - } as unknown as Trip)) - .mockReturnValueOnce(createOk({ - _uuid: 'trip-2', - getSegmentsWithoutWalkingInMultimode: jest.fn().mockReturnValue([]), - setupStartAndEndTimes: jest.fn() - } as unknown as Trip)) - .mockReturnValueOnce(createOk({ - _uuid: 'trip-3', - getSegmentsWithoutWalkingInMultimode: jest.fn().mockReturnValue([]), - setupStartAndEndTimes: jest.fn() - } as unknown as Trip)); + (MockedTrip.create as jest.Mock) + .mockReturnValueOnce( + createOk({ + _uuid: 'trip-1', + getSegmentsWithoutWalkingInMultimode: jest.fn().mockReturnValue([]), + setupStartAndEndTimes: jest.fn() + } as unknown as Trip) + ) + .mockReturnValueOnce( + createOk({ + _uuid: 'trip-2', + getSegmentsWithoutWalkingInMultimode: jest.fn().mockReturnValue([]), + setupStartAndEndTimes: jest.fn() + } as unknown as Trip) + ) + .mockReturnValueOnce( + createOk({ + _uuid: 'trip-3', + getSegmentsWithoutWalkingInMultimode: jest.fn().mockReturnValue([]), + setupStartAndEndTimes: jest.fn() + } as unknown as Trip) + ); mockedpopulateSegmentsForTrip.mockResolvedValue(); - await populateTripsForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateTripsForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify trips were created in sequence order (1, 2, 3) expect(MockedTrip.create).toHaveBeenNthCalledWith(1, expect.objectContaining({ _sequence: 1 }), surveyObjectsRegistry); @@ -351,7 +387,14 @@ describe('TripFactory', () => { (MockedTrip.create as jest.Mock).mockReturnValue(createOk(mockTrip)); mockedpopulateSegmentsForTrip.mockResolvedValue(); - await populateTripsForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateTripsForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify segment factory was called with correct parameters expect(mockedpopulateSegmentsForTrip).toHaveBeenCalledWith( diff --git a/packages/evolution-backend/src/services/surveyObjects/__tests__/VisitedPlaceFactory.test.ts b/packages/evolution-backend/src/services/surveyObjects/__tests__/VisitedPlaceFactory.test.ts index bb8956adc..344954fde 100644 --- a/packages/evolution-backend/src/services/surveyObjects/__tests__/VisitedPlaceFactory.test.ts +++ b/packages/evolution-backend/src/services/surveyObjects/__tests__/VisitedPlaceFactory.test.ts @@ -15,11 +15,7 @@ import { createOk, createErrors } from 'evolution-common/lib/types/Result.type'; import { SurveyObjectsRegistry } from 'evolution-common/lib/services/baseObjects/SurveyObjectsRegistry'; // Mock VisitedPlace.create -jest.mock('evolution-common/lib/services/baseObjects/VisitedPlace', () => ({ - VisitedPlace: { - create: jest.fn() - } -})); +jest.mock('evolution-common/lib/services/baseObjects/VisitedPlace', () => ({ VisitedPlace: { create: jest.fn() } })); const MockedVisitedPlace = VisitedPlace as jest.MockedClass; describe('VisitedPlaceFactory', () => { @@ -52,38 +48,19 @@ describe('VisitedPlaceFactory', () => { } }; - journey = { - _uuid: 'journey-uuid', - addVisitedPlace: jest.fn() - } as unknown as Journey; + journey = { _uuid: 'journey-uuid', addVisitedPlace: jest.fn() } as unknown as Journey; - person = { - _uuid: 'person-uuid', - addVisitedPlace: jest.fn() - } as unknown as Person; + person = { _uuid: 'person-uuid', addVisitedPlace: jest.fn() } as unknown as Person; journeyAttributes = { _uuid: 'journey-uuid', visitedPlaces: { - 'vp-1': { - _uuid: 'vp-1', - _sequence: 1, - activity: 'work', - name: 'Office' - }, - 'vp-2': { - _uuid: 'vp-2', - _sequence: 2, - activity: 'home', - name: 'Home' - } + 'vp-1': { _uuid: 'vp-1', _sequence: 1, activity: 'work', name: 'Office' }, + 'vp-2': { _uuid: 'vp-2', _sequence: 2, activity: 'home', name: 'Home' } } } as unknown as ExtendedJourneyAttributes; - home = { - _uuid: 'home-uuid', - geography: { type: 'Point', coordinates: [-73.5, 45.5] } - } as unknown as Home; + home = { _uuid: 'home-uuid', geography: { type: 'Point', coordinates: [-73.5, 45.5] } } as unknown as Home; // Clear all mocks jest.clearAllMocks(); @@ -97,35 +74,30 @@ describe('VisitedPlaceFactory', () => { place: { geography: { type: 'Point', coordinates: [-73.6, 45.6] } } } as unknown as VisitedPlace; - const mockVisitedPlace2 = { - _uuid: 'vp-2', - activity: 'home', - place: { geography: null } - } as unknown as VisitedPlace; + const mockVisitedPlace2 = { _uuid: 'vp-2', activity: 'home', place: { geography: null } } as unknown as VisitedPlace; - (MockedVisitedPlace.create as jest.Mock).mockReturnValueOnce(createOk(mockVisitedPlace1)) + (MockedVisitedPlace.create as jest.Mock) + .mockReturnValueOnce(createOk(mockVisitedPlace1)) .mockReturnValueOnce(createOk(mockVisitedPlace2)); - await populateVisitedPlacesForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, home, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateVisitedPlacesForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + home, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify VisitedPlace.create was called with correct attributes expect(MockedVisitedPlace.create).toHaveBeenCalledTimes(2); expect(MockedVisitedPlace.create).toHaveBeenCalledWith( - expect.objectContaining({ - _uuid: 'vp-1', - _sequence: 1, - activity: 'work', - name: 'Office' - }), + expect.objectContaining({ _uuid: 'vp-1', _sequence: 1, activity: 'work', name: 'Office' }), surveyObjectsRegistry ); expect(MockedVisitedPlace.create).toHaveBeenCalledWith( - expect.objectContaining({ - _uuid: 'vp-2', - _sequence: 2, - activity: 'home', - name: 'Home' - }), + expect.objectContaining({ _uuid: 'vp-2', _sequence: 2, activity: 'home', name: 'Home' }), surveyObjectsRegistry ); @@ -147,15 +119,17 @@ describe('VisitedPlaceFactory', () => { (MockedVisitedPlace.create as jest.Mock).mockReturnValue(createOk(mockVisitedPlace)); - journeyAttributes.visitedPlaces = { - 'vp-home': { - _sequence: 1, - _uuid: 'vp-home', - activity: 'home' - } - } as any; + journeyAttributes.visitedPlaces = { 'vp-home': { _sequence: 1, _uuid: 'vp-home', activity: 'home' } } as any; - await populateVisitedPlacesForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, home, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateVisitedPlacesForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + home, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify home was assigned as the place expect(mockVisitedPlace.place).toBe(home); @@ -171,15 +145,17 @@ describe('VisitedPlaceFactory', () => { const originalGeography = mockVisitedPlace.place!.geography; (MockedVisitedPlace.create as jest.Mock).mockReturnValue(createOk(mockVisitedPlace)); - journeyAttributes.visitedPlaces = { - 'vp-work': { - _sequence: 1, - _uuid: 'vp-work', - activity: 'work' - } - } as any; + journeyAttributes.visitedPlaces = { 'vp-work': { _sequence: 1, _uuid: 'vp-work', activity: 'work' } } as any; - await populateVisitedPlacesForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, home, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateVisitedPlacesForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + home, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify geography was not changed expect(mockVisitedPlace.place!.geography).toBe(originalGeography); @@ -188,12 +164,19 @@ describe('VisitedPlaceFactory', () => { it('should handle visited place creation errors', async () => { const errors = [new Error('Invalid visited place data')]; - (MockedVisitedPlace.create as jest.Mock).mockReturnValueOnce(createErrors(errors)) - .mockReturnValueOnce(createOk({ - _uuid: 'vp-2' - } as VisitedPlace)); - - await populateVisitedPlacesForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, home, { uuid: 'test' } as any, surveyObjectsRegistry); + (MockedVisitedPlace.create as jest.Mock) + .mockReturnValueOnce(createErrors(errors)) + .mockReturnValueOnce(createOk({ _uuid: 'vp-2' } as VisitedPlace)); + + await populateVisitedPlacesForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + home, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify error was stored expect(surveyObjectsWithErrors.errorsByObject.visitedPlacesByUuid['vp-1']).toEqual(errors); @@ -204,21 +187,21 @@ describe('VisitedPlaceFactory', () => { it('should skip visited places with undefined uuid', async () => { journeyAttributes.visitedPlaces = { - 'undefined': { - _uuid: 'undefined', - activity: 'work' - }, - 'vp-1': { - _uuid: 'vp-1', - activity: 'home' - } + undefined: { _uuid: 'undefined', activity: 'work' }, + 'vp-1': { _uuid: 'vp-1', activity: 'home' } } as any; - (MockedVisitedPlace.create as jest.Mock).mockReturnValue(createOk({ - _uuid: 'vp-1' - } as VisitedPlace)); + (MockedVisitedPlace.create as jest.Mock).mockReturnValue(createOk({ _uuid: 'vp-1' } as VisitedPlace)); - await populateVisitedPlacesForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, home, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateVisitedPlacesForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + home, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Should only create one visited place (skip undefined) expect(MockedVisitedPlace.create).toHaveBeenCalledTimes(1); @@ -228,7 +211,15 @@ describe('VisitedPlaceFactory', () => { it('should handle missing visitedPlaces attributes', async () => { journeyAttributes.visitedPlaces = undefined; - await populateVisitedPlacesForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, home, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateVisitedPlacesForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + home, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); expect(MockedVisitedPlace.create).not.toHaveBeenCalled(); expect(journey.addVisitedPlace).not.toHaveBeenCalled(); @@ -237,28 +228,25 @@ describe('VisitedPlaceFactory', () => { it('should sort visited places by sequence', async () => { // Create visited places with mixed sequence order journeyAttributes.visitedPlaces = { - 'vp-3': { - _uuid: 'vp-3', - _sequence: 3, - activity: 'shopping' - }, - 'vp-1': { - _uuid: 'vp-1', - _sequence: 1, - activity: 'home' - }, - 'vp-2': { - _uuid: 'vp-2', - _sequence: 2, - activity: 'work' - } + 'vp-3': { _uuid: 'vp-3', _sequence: 3, activity: 'shopping' }, + 'vp-1': { _uuid: 'vp-1', _sequence: 1, activity: 'home' }, + 'vp-2': { _uuid: 'vp-2', _sequence: 2, activity: 'work' } } as any; - (MockedVisitedPlace.create as jest.Mock).mockReturnValueOnce(createOk({ _uuid: 'vp-1' } as VisitedPlace)) + (MockedVisitedPlace.create as jest.Mock) + .mockReturnValueOnce(createOk({ _uuid: 'vp-1' } as VisitedPlace)) .mockReturnValueOnce(createOk({ _uuid: 'vp-2' } as VisitedPlace)) .mockReturnValueOnce(createOk({ _uuid: 'vp-3' } as VisitedPlace)); - await populateVisitedPlacesForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, home, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateVisitedPlacesForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + home, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Verify visited places were created in sequence order (1, 2, 3) expect(MockedVisitedPlace.create).toHaveBeenNthCalledWith(1, expect.objectContaining({ _sequence: 1 }), surveyObjectsRegistry); @@ -267,22 +255,21 @@ describe('VisitedPlaceFactory', () => { }); it('should handle visited place without place property', async () => { - const mockVisitedPlace = { - _uuid: 'vp-home', - activity: 'home', - place: null - } as unknown as VisitedPlace; + const mockVisitedPlace = { _uuid: 'vp-home', activity: 'home', place: null } as unknown as VisitedPlace; (MockedVisitedPlace.create as jest.Mock).mockReturnValue(createOk(mockVisitedPlace)); - journeyAttributes.visitedPlaces = { - 'vp-home': { - _uuid: 'vp-home', - activity: 'home' - } - } as any; + journeyAttributes.visitedPlaces = { 'vp-home': { _uuid: 'vp-home', activity: 'home' } } as any; - await populateVisitedPlacesForJourney(surveyObjectsWithErrors, person, journey, journeyAttributes, home, { uuid: 'test' } as any, surveyObjectsRegistry); + await populateVisitedPlacesForJourney( + surveyObjectsWithErrors, + person, + journey, + journeyAttributes, + home, + { uuid: 'test' } as any, + surveyObjectsRegistry + ); // Should not throw error and should still add to journey expect(journey.addVisitedPlace).toHaveBeenCalledWith(mockVisitedPlace); diff --git a/packages/evolution-backend/src/services/surveyStatus/__tests__/surveyStatus.test.ts b/packages/evolution-backend/src/services/surveyStatus/__tests__/surveyStatus.test.ts index de0d5b514..49a0b2b5d 100644 --- a/packages/evolution-backend/src/services/surveyStatus/__tests__/surveyStatus.test.ts +++ b/packages/evolution-backend/src/services/surveyStatus/__tests__/surveyStatus.test.ts @@ -10,12 +10,7 @@ import { isSurveyEnded } from '../surveyStatus'; import projectConfig from 'evolution-common/lib/config/project.config'; // Mock the project config -jest.mock('evolution-common/lib/config/project.config', () => ({ - __esModule: true, - default: { - endDateTimeWithTimezoneOffset: undefined - } -})); +jest.mock('evolution-common/lib/config/project.config', () => ({ __esModule: true, default: { endDateTimeWithTimezoneOffset: undefined } })); describe('isSurveyEnded', () => { // Store original console methods @@ -52,27 +47,27 @@ describe('isSurveyEnded', () => { test('should return true for a date one day ago', () => { const yesterday = moment().subtract(1, 'day').format('YYYY-MM-DDTHH:mm:ssZ'); (projectConfig as any).endDateTimeWithTimezoneOffset = yesterday; - + expect(isSurveyEnded()).toBe(true); }); test('should return true for a date one hour ago', () => { const oneHourAgo = moment().subtract(1, 'hour').format('YYYY-MM-DDTHH:mm:ssZ'); (projectConfig as any).endDateTimeWithTimezoneOffset = oneHourAgo; - + expect(isSurveyEnded()).toBe(true); }); test('should return true for a date one minute ago', () => { const oneMinuteAgo = moment().subtract(1, 'minute').format('YYYY-MM-DDTHH:mm:ssZ'); (projectConfig as any).endDateTimeWithTimezoneOffset = oneMinuteAgo; - + expect(isSurveyEnded()).toBe(true); }); test('should return true for a specific past date with timezone', () => { (projectConfig as any).endDateTimeWithTimezoneOffset = '2024-12-31T23:59:59-05:00'; - + expect(isSurveyEnded()).toBe(true); }); }); @@ -81,21 +76,21 @@ describe('isSurveyEnded', () => { test('should return false for a date one day from now', () => { const tomorrow = moment().add(1, 'day').format('YYYY-MM-DDTHH:mm:ssZ'); (projectConfig as any).endDateTimeWithTimezoneOffset = tomorrow; - + expect(isSurveyEnded()).toBe(false); }); test('should return false for a date one hour from now', () => { const oneHourLater = moment().add(1, 'hour').format('YYYY-MM-DDTHH:mm:ssZ'); (projectConfig as any).endDateTimeWithTimezoneOffset = oneHourLater; - + expect(isSurveyEnded()).toBe(false); }); test('should return false for a date one minute from now', () => { const oneMinuteLater = moment().add(1, 'minute').format('YYYY-MM-DDTHH:mm:ssZ'); (projectConfig as any).endDateTimeWithTimezoneOffset = oneMinuteLater; - + expect(isSurveyEnded()).toBe(false); }); }); @@ -104,28 +99,28 @@ describe('isSurveyEnded', () => { test('should correctly handle positive timezone offset', () => { const pastDateWithPositiveOffset = moment().subtract(1, 'day').format('YYYY-MM-DDTHH:mm:ss+09:00'); (projectConfig as any).endDateTimeWithTimezoneOffset = pastDateWithPositiveOffset; - + expect(isSurveyEnded()).toBe(true); }); test('should correctly handle negative timezone offset', () => { const pastDateWithNegativeOffset = moment().subtract(1, 'day').format('YYYY-MM-DDTHH:mm:ss-08:00'); (projectConfig as any).endDateTimeWithTimezoneOffset = pastDateWithNegativeOffset; - + expect(isSurveyEnded()).toBe(true); }); test('should correctly handle UTC timezone', () => { const pastDateUTC = moment().subtract(1, 'day').utc().format('YYYY-MM-DDTHH:mm:ss+00:00'); (projectConfig as any).endDateTimeWithTimezoneOffset = pastDateUTC; - + expect(isSurveyEnded()).toBe(true); }); test('should correctly compare times across different timezones', () => { // Set end date to yesterday at 5 PM EST (UTC-5) (projectConfig as any).endDateTimeWithTimezoneOffset = '2024-12-15T17:00:00-05:00'; - + // This should be true as the date is in the past regardless of current timezone expect(isSurveyEnded()).toBe(true); }); @@ -135,9 +130,12 @@ describe('isSurveyEnded', () => { // This means the actual moment is still 1 hour in the past const oneHourAgo = moment().subtract(1, 'hour'); const currentOffset = moment().utcOffset(); - const endTimeInFutureTimezone = oneHourAgo.clone().utcOffset(currentOffset + 120).format('YYYY-MM-DDTHH:mm:ssZZ'); + const endTimeInFutureTimezone = oneHourAgo + .clone() + .utcOffset(currentOffset + 120) + .format('YYYY-MM-DDTHH:mm:ssZZ'); (projectConfig as any).endDateTimeWithTimezoneOffset = endTimeInFutureTimezone; - + expect(isSurveyEnded()).toBe(true); }); @@ -146,9 +144,12 @@ describe('isSurveyEnded', () => { // This means the actual moment is 1 hour in the past const oneHourLater = moment().add(1, 'hour'); const currentOffset = moment().utcOffset(); - const endTimeInPastTimezone = oneHourLater.clone().utcOffset(currentOffset - 120).format('YYYY-MM-DDTHH:mm:ssZZ'); + const endTimeInPastTimezone = oneHourLater + .clone() + .utcOffset(currentOffset - 120) + .format('YYYY-MM-DDTHH:mm:ssZZ'); (projectConfig as any).endDateTimeWithTimezoneOffset = endTimeInPastTimezone; - + expect(isSurveyEnded()).toBe(false); }); }); @@ -156,23 +157,21 @@ describe('isSurveyEnded', () => { describe('with invalid date formats', () => { test('should return false for invalid date string', () => { (projectConfig as any).endDateTimeWithTimezoneOffset = 'not-a-valid-date'; - + expect(isSurveyEnded()).toBe(false); }); test('should log an error for invalid date string', () => { (projectConfig as any).endDateTimeWithTimezoneOffset = 'invalid-date'; - + expect(isSurveyEnded()).toBe(false); - - expect(console.error).toHaveBeenCalledWith( - 'Invalid endDateTimeWithTimezoneOffset configured: invalid-date' - ); + + expect(console.error).toHaveBeenCalledWith('Invalid endDateTimeWithTimezoneOffset configured: invalid-date'); }); test('should return true for date without timezone', () => { (projectConfig as any).endDateTimeWithTimezoneOffset = '2024-12-31T23:59:59'; - + // Moment will still parse this, but it's not in the correct format // The function should still work but we're testing that it handles it const result = isSurveyEnded(); @@ -181,10 +180,9 @@ describe('isSurveyEnded', () => { test('should return false for empty string', () => { (projectConfig as any).endDateTimeWithTimezoneOffset = ''; - + // Empty string should be falsy and return false early expect(isSurveyEnded()).toBe(false); }); }); - }); diff --git a/packages/evolution-backend/src/services/validations/__tests__/serverValidation.test.ts b/packages/evolution-backend/src/services/validations/__tests__/serverValidation.test.ts index d7b2aa2f2..ce1915fbd 100644 --- a/packages/evolution-backend/src/services/validations/__tests__/serverValidation.test.ts +++ b/packages/evolution-backend/src/services/validations/__tests__/serverValidation.test.ts @@ -10,15 +10,7 @@ import { InterviewAttributes } from 'evolution-common/lib/services/questionnaire const validations = { testField: { - validations: [ - { - validation: (value: string) => (parseInt(value) % 2 === 0), - errorMessage: { - fr: 'Valeur paire', - en: 'Value is even' - } - } - ] + validations: [{ validation: (value: string) => parseInt(value) % 2 === 0, errorMessage: { fr: 'Valeur paire', en: 'Value is even' } }] } }; @@ -41,7 +33,7 @@ describe('With validation testField', () => { ['Valid testField', { 'response.testField': '121' }, true], ['Invalid testField', { 'response.testField': '122' }, { testField: validations.testField.validations[0].errorMessage }], ['Invalid client side', { 'validations.testField': false, 'response.testField': '122' }, true], - ['No data', { }, true], + ['No data', {}, true] ]).test('%s', async (_description, valuesByPath, result) => { expect(await serverValidation(interviewAttributes, validations, valuesByPath, [])).toEqual(result); }); diff --git a/packages/evolution-common/src/config/__tests__/project.config.initialized.test.ts b/packages/evolution-common/src/config/__tests__/project.config.initialized.test.ts index 166743170..3ae805e94 100644 --- a/packages/evolution-common/src/config/__tests__/project.config.initialized.test.ts +++ b/packages/evolution-common/src/config/__tests__/project.config.initialized.test.ts @@ -22,27 +22,29 @@ jest.mock('chaire-lib-common/lib/config/shared/project.config', () => ({ hideStartButtonOnHomePage: true, introductionTwoParagraph: true, introBanner: true, - bannerPaths: { 'en': 'banner-en.png', 'fr': 'banner-fr.png' }, + bannerPaths: { en: 'banner-en.png', fr: 'banner-fr.png' }, introLogoAfterStartButton: true, - logoPaths: { 'en': 'logo-en.png', 'fr': 'logo-fr.png' }, - languageNames: { 'en': 'English', 'fr': 'Français' } + logoPaths: { en: 'logo-en.png', fr: 'logo-fr.png' }, + languageNames: { en: 'English', fr: 'Français' } } })); test('test initialized values', () => { - expect(projectConfig).toEqual(expect.objectContaining({ - region: 'FR', - selfResponseMinimumAge: 18, - logDatabaseUpdates: true, - startDateTimeWithTimezoneOffset: '2025-03-01T08:00:00-05:00', - endDateTimeWithTimezoneOffset: '2025-11-30T23:59:59-05:00', - surveyAreaGeojsonPath: 'test.geojson', - hideStartButtonOnHomePage: true, - introductionTwoParagraph: true, - introBanner: true, - bannerPaths: { 'en': 'banner-en.png', 'fr': 'banner-fr.png' }, - introLogoAfterStartButton: true, - logoPaths: { 'en': 'logo-en.png', 'fr': 'logo-fr.png' }, - languageNames: { 'en': 'English', 'fr': 'Français' } - })); + expect(projectConfig).toEqual( + expect.objectContaining({ + region: 'FR', + selfResponseMinimumAge: 18, + logDatabaseUpdates: true, + startDateTimeWithTimezoneOffset: '2025-03-01T08:00:00-05:00', + endDateTimeWithTimezoneOffset: '2025-11-30T23:59:59-05:00', + surveyAreaGeojsonPath: 'test.geojson', + hideStartButtonOnHomePage: true, + introductionTwoParagraph: true, + introBanner: true, + bannerPaths: { en: 'banner-en.png', fr: 'banner-fr.png' }, + introLogoAfterStartButton: true, + logoPaths: { en: 'logo-en.png', fr: 'logo-fr.png' }, + languageNames: { en: 'English', fr: 'Français' } + }) + ); }); diff --git a/packages/evolution-common/src/config/__tests__/project.config.test.ts b/packages/evolution-common/src/config/__tests__/project.config.test.ts index 3d09affcf..dd92be30b 100644 --- a/packages/evolution-common/src/config/__tests__/project.config.test.ts +++ b/packages/evolution-common/src/config/__tests__/project.config.test.ts @@ -9,23 +9,25 @@ import projectConfig, { EvolutionProjectConfiguration } from '../project.config' import { ISODateTimeStringWithTimezoneOffset } from '../../utils/DateTimeUtils'; test('Expected default', () => { - expect(projectConfig).toEqual(expect.objectContaining({ - region: 'CA', - selfResponseMinimumAge: 14, - drivingLicenseAge: 16, - logDatabaseUpdates: false, - startDateTimeWithTimezoneOffset: undefined, - endDateTimeWithTimezoneOffset: undefined, - surveyAreaGeojsonPath: undefined, - hideStartButtonOnHomePage: false, - introductionTwoParagraph: false, - introBanner: false, - bannerPaths: {}, - introLogoAfterStartButton: false, - logoPaths: {}, - languageNames: { 'en': 'English', 'fr': 'Français' }, - title: { 'en': 'Survey', 'fr': 'Enquête' } - })); + expect(projectConfig).toEqual( + expect.objectContaining({ + region: 'CA', + selfResponseMinimumAge: 14, + drivingLicenseAge: 16, + logDatabaseUpdates: false, + startDateTimeWithTimezoneOffset: undefined, + endDateTimeWithTimezoneOffset: undefined, + surveyAreaGeojsonPath: undefined, + hideStartButtonOnHomePage: false, + introductionTwoParagraph: false, + introBanner: false, + bannerPaths: {}, + introLogoAfterStartButton: false, + logoPaths: {}, + languageNames: { en: 'English', fr: 'Français' }, + title: { en: 'Survey', fr: 'Enquête' } + }) + ); }); test('set project configuration', () => { @@ -40,11 +42,11 @@ test('set project configuration', () => { hideStartButtonOnHomePage: true, introductionTwoParagraph: true, introBanner: true, - bannerPaths: { 'en': 'banner-en.png', 'fr': 'banner-fr.png' }, + bannerPaths: { en: 'banner-en.png', fr: 'banner-fr.png' }, introLogoAfterStartButton: true, - logoPaths: { 'en': 'logo-en.png', 'fr': 'logo-fr.png' }, - languageNames: { 'en': 'English', 'fr': 'Français' }, - title: { 'en': 'Survey title', 'fr': 'Titre de l\'enquête' } + logoPaths: { en: 'logo-en.png', fr: 'logo-fr.png' }, + languageNames: { en: 'English', fr: 'Français' }, + title: { en: 'Survey title', fr: 'Titre de l\'enquête' } }; setProjectConfiguration(configToSet); expect(projectConfig).toEqual(expect.objectContaining(configToSet)); diff --git a/packages/evolution-common/src/services/audits/__tests__/AuditUtils.test.ts b/packages/evolution-common/src/services/audits/__tests__/AuditUtils.test.ts index a06a898bc..d438c51c5 100644 --- a/packages/evolution-common/src/services/audits/__tests__/AuditUtils.test.ts +++ b/packages/evolution-common/src/services/audits/__tests__/AuditUtils.test.ts @@ -75,14 +75,7 @@ describe('AuditUtils', () => { objectType: 'visitedPlace', objectUuid: 'visited-place-uuid-444' }, - { - version: 1, - level: 'error', - errorCode: 'TRIP_001', - message: 'Trip validation error', - objectType: 'trip', - objectUuid: 'trip-uuid-555' - }, + { version: 1, level: 'error', errorCode: 'TRIP_001', message: 'Trip validation error', objectType: 'trip', objectUuid: 'trip-uuid-555' }, { version: 1, level: 'info', @@ -288,12 +281,7 @@ describe('AuditUtils', () => { it('should handle audits with optional properties', () => { const auditsWithOptionalProps: AuditForObject[] = [ - { - version: 1, - errorCode: 'MINIMAL_001', - objectType: 'interview', - objectUuid: 'interview-uuid-minimal' - }, + { version: 1, errorCode: 'MINIMAL_001', objectType: 'interview', objectUuid: 'interview-uuid-minimal' }, { version: 2, level: 'info', @@ -373,19 +361,9 @@ describe('AuditUtils', () => { }); it('should test auditsToAuditArray function', () => { - const audits = { - TEST_001: { - version: 1, - level: 'error' as const, - errorCode: 'TEST_001', - message: 'Test audit' - } - }; + const audits = { TEST_001: { version: 1, level: 'error' as const, errorCode: 'TEST_001', message: 'Test audit' } }; - const objectData = { - objectType: 'interview', - objectUuid: 'interview-uuid-test' - }; + const objectData = { objectType: 'interview', objectUuid: 'interview-uuid-test' }; const result = auditsToAuditArray(audits, objectData); expect(result).toHaveLength(1); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Address.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Address.test.ts index 967febec4..207704a61 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Address.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Address.test.ts @@ -11,7 +11,6 @@ import { isOk, hasErrors, unwrap } from '../../../types/Result.type'; import { SurveyObjectsRegistry } from '../SurveyObjectsRegistry'; describe('Address', () => { - let registry: SurveyObjectsRegistry; beforeEach(() => { @@ -44,9 +43,11 @@ describe('Address', () => { */ test('should have a validateParams section for each attribute', () => { const validateParamsCode = Address.validateParams.toString(); - addressAttributes.filter((attribute) => attribute !== '_uuid').forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + addressAttributes + .filter((attribute) => attribute !== '_uuid') + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -64,7 +65,7 @@ describe('Address', () => { const invalidAttributes = 'foo' as any; const result = Address.create(invalidAttributes); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(1); + expect(unwrap(result) as Error[]).toHaveLength(1); }); test('should create an Address instance with valid attributes', () => { @@ -104,13 +105,8 @@ describe('Address', () => { }); test('should create an Address instance with custom attributes', () => { - const customAttributes: { [key: string]: unknown } = { - customAttribute: 'custom value', - }; - const addressAttributes: ExtendedAddressAttributes = { - ...validAddressAttributes, - ...customAttributes, - }; + const customAttributes: { [key: string]: unknown } = { customAttribute: 'custom value' }; + const addressAttributes: ExtendedAddressAttributes = { ...validAddressAttributes, ...customAttributes }; const address = new Address(addressAttributes); expect(address).toBeInstanceOf(Address); expect(address.attributes).toEqual(validAddressAttributes); @@ -152,13 +148,12 @@ describe('Address', () => { ['postalCode', 'X9Y 8Z7'], ['postalId', 'new-postal-id'], ['combinedAddressUuid', uuidV4()], - ['_isValid', false], + ['_isValid', false] ])('should set and get %s', (attribute, value) => { const address = new Address(validAddressAttributes); address[attribute] = value; expect(address[attribute]).toEqual(value); }); - }); describe('validateParams', () => { @@ -177,7 +172,7 @@ describe('Address', () => { ['streetNameHomogenized', 123], ['combinedStreetUuid', 'invalid-uuid'], ['municipalityCode', 123], - ['postalMunicipalityName', 123], + ['postalMunicipalityName', 123] ])('should return an error for invalid %s', (param, value) => { const invalidAttributes = { ...validAddressAttributes, [param]: value }; const errors = Address.validateParams(invalidAttributes); @@ -188,7 +183,7 @@ describe('Address', () => { describe('Getters for attributes with no setters', () => { test.each([ ['_uuid', validAddressAttributes._uuid], - ['attributes', validAddressAttributes], + ['attributes', validAddressAttributes] ])('should set and get %s', (attribute, value) => { const address = new Address(validAddressAttributes); expect(address[attribute]).toEqual(value); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseAddress.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseAddress.test.ts index 34264ab4c..3fe05a570 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseAddress.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseAddress.test.ts @@ -9,7 +9,6 @@ import { v4 as uuidV4 } from 'uuid'; import { BaseAddress } from '../BaseAddress'; describe('BaseAddress Class Tests', () => { - const _uuid = uuidV4(); const addressParams = { _uuid, @@ -64,5 +63,4 @@ describe('BaseAddress Class Tests', () => { expect(instance.region).toEqual(addressParams.region); expect(instance.country).toEqual(addressParams.country); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseHousehold.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseHousehold.test.ts index 055d1fc2b..e77a31747 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseHousehold.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseHousehold.test.ts @@ -15,7 +15,7 @@ const weightMethodAttributes = { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const baseHouseholdAttributes: BaseHouseholdAttributes = { @@ -25,13 +25,13 @@ const baseHouseholdAttributes: BaseHouseholdAttributes = { category: 'bar' as HAttr.HouseholdCategory, contactPhoneNumber: '123-456-7890', contactEmail: 'test@example.com', - _weights: [{ weight: 5.5, method: new WeightMethod(weightMethodAttributes) }, { weight: 6.3, method: new WeightMethod(weightMethodAttributes) }], + _weights: [ + { weight: 5.5, method: new WeightMethod(weightMethodAttributes) }, + { weight: 6.3, method: new WeightMethod(weightMethodAttributes) } + ] }; -const extendedHouseholdAttributes: ExtendedHouseholdAttributes = { - ...baseHouseholdAttributes, - additionalAttribute: 'additionalValue', -}; +const extendedHouseholdAttributes: ExtendedHouseholdAttributes = { ...baseHouseholdAttributes, additionalAttribute: 'additionalValue' }; const baseHouseholdAttributes2: BaseHouseholdAttributes = { _uuid: uuidV4(), @@ -47,7 +47,6 @@ const baseHouseholdAttributes2: BaseHouseholdAttributes = { }; describe('BaseHousehold Class Tests', () => { - it('should create a BaseHousehold instance', () => { const household = new BaseHousehold(baseHouseholdAttributes); expect(household).toBeInstanceOf(BaseHousehold); @@ -92,7 +91,6 @@ describe('BaseHousehold Class Tests', () => { }); describe('BaseHousehold Class Additional Tests', () => { - it('should correctly set category property if provided', () => { const householdInstance = new BaseHousehold(baseHouseholdAttributes2); expect(householdInstance.category).toEqual(baseHouseholdAttributes2.category); @@ -128,7 +126,7 @@ describe('BaseHousehold Class Additional Tests', () => { category: 'single-family', homeCarParkings: ['private-garage'], contactPhoneNumber: '1234567890', - contactEmail: 'valid@example.com', + contactEmail: 'valid@example.com' }; const errors = BaseHousehold.validateParams(validParams); @@ -151,7 +149,8 @@ describe('BaseHousehold Class Additional Tests', () => { _weights: [ { weight: 5.5, method: 'foo' }, { weight: -3.2, method: new WeightMethod(weightMethodAttributes) }, - { weight: 'bar', method: new WeightMethod(weightMethodAttributes) }] + { weight: 'bar', method: new WeightMethod(weightMethodAttributes) } + ] }; const errors = BaseHousehold.validateParams(invalidParams); @@ -169,7 +168,7 @@ describe('BaseHousehold Class Additional Tests', () => { new Error('BaseHousehold validateParams: category should be a string'), new Error('BaseHousehold validateParams: homeCarParkings index 0 should be a string'), new Error('BaseHousehold validateParams: contactPhoneNumber should be a string'), - new Error('BaseHousehold validateParams: contactEmail should be a string'), + new Error('BaseHousehold validateParams: contactEmail should be a string') ]); }); @@ -185,6 +184,4 @@ describe('BaseHousehold Class Additional Tests', () => { expect(instance.size).toEqual(baseHouseholdAttributes.size); expect(instance.carNumber).toEqual(baseHouseholdAttributes.carNumber); }); - }); - diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseInterview.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseInterview.test.ts index bdfadc860..0d696e8e4 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseInterview.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseInterview.test.ts @@ -14,23 +14,14 @@ import { SurveyableAttributes } from '../Surveyable'; const validUUID = uuidV4(); describe('BaseInterview', () => { + const surveyAttributes: SurveyAttributes = { name: 'Survey name', shortname: 'survey_shortname', startDate: '2023-10-01', endDate: '2023-10-31' }; - const surveyAttributes: SurveyAttributes = { - name: 'Survey name', - shortname: 'survey_shortname', - startDate: '2023-10-01', - endDate: '2023-10-31' - }; - - const sampleAttributes: SampleAttributes = { - name: 'Sample name', - shortname: 'sample_shortname' - }; + const sampleAttributes: SampleAttributes = { name: 'Sample name', shortname: 'sample_shortname' }; const surveyableAttributes: SurveyableAttributes = { survey: new Survey(surveyAttributes), sample: new Sample(sampleAttributes), - sampleBatchNumber: 123, + sampleBatchNumber: 123 }; const interviewAttributes: BaseInterviewAttributes = { @@ -47,10 +38,7 @@ describe('BaseInterview', () => { _isCompleted: true, _device: 'mobile' }; - const baseInterviewAttributes: BaseInterviewAttributes & SurveyableAttributes = { - ...surveyableAttributes, - ...interviewAttributes - }; + const baseInterviewAttributes: BaseInterviewAttributes & SurveyableAttributes = { ...surveyableAttributes, ...interviewAttributes }; it('should create a new BaseInterview instance', () => { const interview = new BaseInterview(baseInterviewAttributes); @@ -67,14 +55,13 @@ describe('BaseInterview', () => { expect(interview._source).toEqual('web'); expect(interview._isCompleted).toEqual(true); expect(interview._device).toEqual('mobile'); - }); it('should create a new BaseInterview instance with minimal attributes', () => { const minimalAttributes: BaseInterviewAttributes & SurveyableAttributes = { _uuid: validUUID, survey: surveyableAttributes.survey, - sample: surveyableAttributes.sample, + sample: surveyableAttributes.sample }; const interview = new BaseInterview(minimalAttributes); @@ -91,7 +78,6 @@ describe('BaseInterview', () => { expect(interview._source).toBeUndefined(); expect(interview._isCompleted).toBeUndefined(); expect(interview._device).toBeUndefined(); - }); it('should validate a BaseInterview instance', () => { @@ -115,7 +101,7 @@ describe('BaseInterview', () => { _language: 'fr', _source: 'postal', _isCompleted: false, - _device: 'unknown', + _device: 'unknown' }; const validErrors = BaseInterview.validateParams(validParams); @@ -133,7 +119,7 @@ describe('BaseInterview', () => { _language: 'aaa', _source: {}, _isCompleted: 'true', - _device: 'foo', + _device: 'foo' }; const invalidErrors = BaseInterview.validateParams(invalidParams); @@ -150,7 +136,7 @@ describe('BaseInterview', () => { new Error('BaseInterview validateParams: invalid _updatedAt'), new Error('BaseInterview validateParams: contactPhoneNumber should be a string'), new Error('BaseInterview validateParams: contactEmail should be a string'), - new Error('BaseInterview validateParams: _device is invalid'), + new Error('BaseInterview validateParams: _device is invalid') ]); }); @@ -168,5 +154,4 @@ describe('BaseInterview', () => { expect(instance.sample).toBeInstanceOf(Sample); expect(instance.sample.name).toEqual(sampleAttributes.name); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseJourney.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseJourney.test.ts index 3af79611f..661895822 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseJourney.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseJourney.test.ts @@ -19,12 +19,11 @@ import * as TCAttr from '../attributeTypes/TripChainAttributes'; const validUUID = uuidV4(); describe('BaseJourney', () => { - const weightMethodAttributes = { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const baseJourneyAttributes: ExtendedJourneyAttributes = { @@ -58,7 +57,7 @@ describe('BaseJourney', () => { startDate: '2023-10-07', endDate: '2023-10-08', startTime: 36100, // 10 hours in seconds - endTime: 72100, // 20 hours in seconds + endTime: 72100 // 20 hours in seconds }; const journey = new BaseJourney(minimalAttributes); @@ -92,7 +91,7 @@ describe('BaseJourney', () => { endDate: '2023-10-06', endTime: 7200, name: 'Valid Journey', - _weights: [], + _weights: [] }; const errors = BaseJourney.validateParams(validParams); @@ -106,7 +105,7 @@ describe('BaseJourney', () => { startTime: 'invalid-time', endDate: 'invalid-date', endTime: 'invalid-time', - name: 123, + name: 123 }; const errors = BaseJourney.validateParams(invalidParams); @@ -117,7 +116,7 @@ describe('BaseJourney', () => { new Error('BaseJourney validateParams: startTime is required and should be a non-negative number'), new Error('BaseJourney validateParams: endDate is required and should be a valid date string'), new Error('BaseJourney validateParams: endTime is required and should be a non-negative number'), - new Error('BaseJourney validateParams: name should be a string'), + new Error('BaseJourney validateParams: name should be a string') ]); }); @@ -126,6 +125,4 @@ describe('BaseJourney', () => { expect(instance).toBeInstanceOf(BaseJourney); expect(instance.name).toEqual(baseJourneyAttributes.name); }); - - }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseJunction.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseJunction.test.ts index 097da2932..5cb2b972c 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseJunction.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseJunction.test.ts @@ -12,24 +12,14 @@ import { BasePlace, BasePlaceAttributes } from '../BasePlace'; const validUUID = uuidV4(); describe('BaseJunction', () => { - const geojson = { type: 'Feature', id: 444, - geometry: { - type: 'Point', - coordinates: [23.5, -11.0033423], - }, - properties: { - foo: 'boo2', - bar: 'far2' - }, + geometry: { type: 'Point', coordinates: [23.5, -11.0033423] }, + properties: { foo: 'boo2', bar: 'far2' } } as GeoJSON.Feature; - const basePlaceAttributes: BasePlaceAttributes = { - geography: geojson, - _uuid: validUUID, - }; + const basePlaceAttributes: BasePlaceAttributes = { geography: geojson, _uuid: validUUID }; const baseJunctionAttributes: BaseJunctionAttributes = { _uuid: validUUID, @@ -37,7 +27,7 @@ describe('BaseJunction', () => { arrivalDate: '2023-10-05', departureDate: '2023-10-06', arrivalTime: 36000, // 10:00 AM in seconds since midnight - departureTime: 45000, // 12:30 PM in seconds since midnight + departureTime: 45000 // 12:30 PM in seconds since midnight }; it('should create a new BaseJunction instance', () => { @@ -57,16 +47,9 @@ describe('BaseJunction', () => { }); it('should create a new BaseJunction instance with minimal attributes', () => { - const minimalBasePlaceAttributes: BasePlaceAttributes = { - geography: undefined, - _uuid: validUUID, - name: 'Minimal Test Place', - }; + const minimalBasePlaceAttributes: BasePlaceAttributes = { geography: undefined, _uuid: validUUID, name: 'Minimal Test Place' }; - const minimalJunctionAttributes: BaseJunctionAttributes = { - _uuid: validUUID, - uuid: validUUID, - }; + const minimalJunctionAttributes: BaseJunctionAttributes = { _uuid: validUUID, uuid: validUUID }; const junction = new BaseJunction({ ...minimalJunctionAttributes, basePlace: new BasePlace(minimalBasePlaceAttributes) }); expect(junction).toBeInstanceOf(BaseJunction); @@ -89,10 +72,7 @@ describe('BaseJunction', () => { }); it('should accept extended attributes', () => { - const extendedJunctionAttributes: ExtendedJunctionAttributes = { - ...baseJunctionAttributes, - customAttribute: 'Custom Value', - }; + const extendedJunctionAttributes: ExtendedJunctionAttributes = { ...baseJunctionAttributes, customAttribute: 'Custom Value' }; const junction = new BaseJunction({ ...extendedJunctionAttributes, basePlace: new BasePlace(basePlaceAttributes) }); expect(junction).toBeInstanceOf(BaseJunction); @@ -105,7 +85,7 @@ describe('BaseJunction', () => { arrivalDate: '2023-01-15', departureDate: '2023-01-16', arrivalTime: 36000, // 10:00 AM in seconds since midnight - departureTime: 43200, // 12:00 PM in seconds since midnight + departureTime: 43200 // 12:00 PM in seconds since midnight }; const errors = BaseJunction.validateParams(validParams); expect(errors).toEqual([]); @@ -125,7 +105,7 @@ describe('BaseJunction', () => { departureDate: '2023-01-16', arrivalTime: 36000, departureTime: 43200, - additionalAttribute: 'additionalValue', + additionalAttribute: 'additionalValue' }; const errors = BaseJunction.validateParams(validParams); expect(errors).toEqual([]); @@ -140,7 +120,7 @@ describe('BaseJunction', () => { arrivalTime: -1, // Negative arrival time departureTime: '12:00 PM', // Invalid departure time string activityCategory: 44, // Invalid activity category - activity: -123283764.34534, // Invalid activity + activity: -123283764.34534 // Invalid activity }; const errors = BaseJunction.validateParams(invalidParams); @@ -161,7 +141,7 @@ describe('BaseJunction', () => { arrivalTime: -1, // Negative arrival time departureTime: '12:00 PM', // Invalid departure time string activityCategory: 44, // Invalid activity category - activity: -123283764.34534, // Invalid activity + activity: -123283764.34534 // Invalid activity }; const validParams = { _uuid: uuidV4(), // Invalid UUID @@ -170,7 +150,7 @@ describe('BaseJunction', () => { departureDate: '2023-01-16', arrivalTime: 36000, departureTime: 43200, - additionalAttribute: 'additionalValue', + additionalAttribute: 'additionalValue' }; const invalidResult = BaseJunction.create(invalidParams); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseOrganization.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseOrganization.test.ts index a98c9c14a..d9dae8edf 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseOrganization.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseOrganization.test.ts @@ -14,12 +14,11 @@ import { WeightMethod } from '../WeightMethod'; const validUUID = uuidV4(); describe('BaseOrganization', () => { - const weightMethodAttributes = { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const baseOrganizationAttributes: BaseOrganizationAttributes = { @@ -33,10 +32,9 @@ describe('BaseOrganization', () => { vehicleNumber: 23, pluginHybridVehicleNumber: 12, electricVehicleNumber: 0, - _weights: [{ weight: 0.1, method: new WeightMethod(weightMethodAttributes) }], + _weights: [{ weight: 0.1, method: new WeightMethod(weightMethodAttributes) }] }; - it('should create a new BaseOrganization instance', () => { const organization = new BaseOrganization(baseOrganizationAttributes); expect(organization).toBeInstanceOf(BaseOrganization); @@ -54,9 +52,7 @@ describe('BaseOrganization', () => { }); it('should create a new BaseOrganization instance with minimal attributes', () => { - const minimalAttributes: BaseOrganizationAttributes = { - _uuid: validUUID - }; + const minimalAttributes: BaseOrganizationAttributes = { _uuid: validUUID }; const organization = new BaseOrganization(minimalAttributes); expect(organization).toBeInstanceOf(BaseOrganization); @@ -76,10 +72,7 @@ describe('BaseOrganization', () => { }); it('should accept extended attributes', () => { - const extendedAttributes: ExtendedOrganizationAttributes = { - ...baseOrganizationAttributes, - customAttribute: 'Custom Value', - }; + const extendedAttributes: ExtendedOrganizationAttributes = { ...baseOrganizationAttributes, customAttribute: 'Custom Value' }; const organization = new BaseOrganization(extendedAttributes); expect(organization).toBeInstanceOf(BaseOrganization); @@ -107,7 +100,7 @@ describe('BaseOrganization', () => { electricVehicleNumber: 3, contactPhoneNumber: '123-456-7890', contactEmail: 'valid@example.com', - _weights: [{ weight: 2.333, method: new WeightMethod(weightMethodAttributes) }], + _weights: [{ weight: 2.333, method: new WeightMethod(weightMethodAttributes) }] }; const errors = BaseOrganization.validateParams(validParams); @@ -126,7 +119,7 @@ describe('BaseOrganization', () => { electricVehicleNumber: 'invalid', // Non-integer electricVehicleNumber contactPhoneNumber: 1234567890, // Invalid contactPhoneNumber contactEmail: 'invalid-email', // Invalid contactEmail - _weights: 'not-an-array', // Invalid _weights + _weights: 'not-an-array' // Invalid _weights }; const errors = BaseOrganization.validateParams(invalidParams); @@ -141,15 +134,12 @@ describe('BaseOrganization', () => { new Error('BaseOrganization validateParams: vehicleNumber should be a positive integer'), new Error('BaseOrganization validateParams: pluginHybridVehicleNumber should be a positive integer'), new Error('BaseOrganization validateParams: electricVehicleNumber should be a positive integer'), - new Error('BaseOrganization validateParams: contactPhoneNumber should be a string'), + new Error('BaseOrganization validateParams: contactPhoneNumber should be a string') ]); }); it('should validate params with missing optional values', () => { - const params = { - _uuid: uuidV4(), - name: 'Valid Org', - }; + const params = { _uuid: uuidV4(), name: 'Valid Org' }; const errors = BaseOrganization.validateParams(params); expect(errors.length).toBe(0); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BasePerson.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BasePerson.test.ts index 555cb269e..0b7bd2c25 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BasePerson.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BasePerson.test.ts @@ -11,14 +11,13 @@ import { Weight } from '../Weight'; import { WeightMethod } from '../WeightMethod'; describe('BasePerson', () => { - const validUuid = uuidV4(); // Generate a valid UUID const weightMethodAttributes = { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const personAttributes: ExtendedPersonAttributes = { @@ -41,12 +40,10 @@ describe('BasePerson', () => { isJobTelecommuteCompatible: 'yes' as PAttr.HasTelecommuteCompatibleJob, // Valid telecommute compatibility status, renamed from isJobTelecommuteCompatible to hasTelecommuteCompatibleJob in new Person class educationalAttainment: 'PhD' as PAttr.EducationalAttainment, // Valid educational attainment _weights: [{ weight: 1.5, method: new WeightMethod(weightMethodAttributes) }], - foo: 'bar', // extended attribute + foo: 'bar' // extended attribute }; it('should create a BasePerson instance with valid attributes', () => { - - const person = new BasePerson(personAttributes); expect(person).toBeInstanceOf(BasePerson); @@ -71,10 +68,9 @@ describe('BasePerson', () => { }); it('should allow empty arrays and home', () => { - const personAttributes2: ExtendedPersonAttributes = { _uuid: validUuid, - foo: 'bar', // extended attribute + foo: 'bar' // extended attribute }; const person2 = new BasePerson(personAttributes2); expect(person2).toBeInstanceOf(BasePerson); @@ -121,7 +117,7 @@ describe('BasePerson', () => { educationalAttainment: 'Ph.D', nickname: 'John Doe', contactPhoneNumber: '123-456-7890', - contactEmail: 'john.doe@example.com', + contactEmail: 'john.doe@example.com' }; const errors = BasePerson.validateParams(params); @@ -141,14 +137,14 @@ describe('BasePerson', () => { bikesharingUser: 324, ridesharingMember: new Date(), ridesharingUser: [], - occupation: [1,2,3], + occupation: [1, 2, 3], jobCategory: ['foo', 'bar'], jobName: 123, isOnTheRoadWorker: Infinity, isJobTelecommuteCompatible: 1234, educationalAttainment: 5678, contactPhoneNumber: 123, // Invalid type - contactEmail: 43.4, // Invalid email format + contactEmail: 43.4 // Invalid email format }; const errors = BasePerson.validateParams(params); @@ -172,40 +168,28 @@ describe('BasePerson', () => { new Error('BasePerson validateParams: isJobTelecommuteCompatible should be a boolean'), new Error('BasePerson validateParams: educationalAttainment is not a valid value'), new Error('BasePerson validateParams: contactPhoneNumber should be a string'), - new Error('BasePerson validateParams: contactEmail should be a string'), + new Error('BasePerson validateParams: contactEmail should be a string') ]); }); test('validateParams with invalid age', () => { - const params = { - age: -324 - }; + const params = { age: -324 }; const errors = BasePerson.validateParams(params); expect(errors.length).toBeGreaterThan(0); - expect(errors).toEqual([ - new Error('BasePerson validateParams: age must be a positive integer') - ]); + expect(errors).toEqual([new Error('BasePerson validateParams: age must be a positive integer')]); - const params2 = { - age: 34.5 - }; + const params2 = { age: 34.5 }; const errors2 = BasePerson.validateParams(params2); expect(errors2.length).toBeGreaterThan(0); - expect(errors).toEqual([ - new Error('BasePerson validateParams: age must be a positive integer') - ]); + expect(errors).toEqual([new Error('BasePerson validateParams: age must be a positive integer')]); - const params3 = { - age: -99.23434 - }; + const params3 = { age: -99.23434 }; const errors3 = BasePerson.validateParams(params3); expect(errors3.length).toBeGreaterThan(0); - expect(errors).toEqual([ - new Error('BasePerson validateParams: age must be a positive integer') - ]); + expect(errors).toEqual([new Error('BasePerson validateParams: age must be a positive integer')]); }); test('validateParams with empty parameters', () => { @@ -235,7 +219,7 @@ describe('BasePerson', () => { educationalAttainment: 'none', nickname: 'John Doe', contactPhoneNumber: '123-456-7890', - contactEmail: 'john.doe@example.com', + contactEmail: 'john.doe@example.com' }; const errors = BasePerson.validateParams(params); @@ -247,5 +231,4 @@ describe('BasePerson', () => { expect(instance).toBeInstanceOf(BasePerson); expect(instance.age).toEqual(personAttributes.age); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BasePlace.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BasePlace.test.ts index cc0d0b53d..a86a5f608 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BasePlace.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BasePlace.test.ts @@ -29,20 +29,14 @@ const baseAddressAttributes: BaseAddressAttributes = { country: 'Sample Country', postalCode: '12345', addressId: 'address123', - internalId: 'internal123', + internalId: 'internal123' }; const geojson = { type: 'Feature', id: 112223, - geometry: { - type: 'Point', - coordinates: [45.5, -89.0033423], - }, - properties: { - foo: 'boo', - bar: 'far' - }, + geometry: { type: 'Point', coordinates: [45.5, -89.0033423] }, + properties: { foo: 'boo', bar: 'far' } } as GeoJSON.Feature; const basePlaceAttributes: BasePlaceAttributes = { @@ -60,13 +54,12 @@ const basePlaceAttributes: BasePlaceAttributes = { geocodingQueryString: 'Sample query', lastAction: 'preGeocoded', deviceUsed: 'tablet', - zoom: 14, + zoom: 14 }; const baseAddress = new BaseAddress(baseAddressAttributes); describe('BasePlace', () => { - it('should create a new BasePlace instance', () => { const place = new BasePlace({ ...basePlaceAttributes, address: baseAddress }); expect(place).toBeInstanceOf(BasePlace); @@ -94,11 +87,7 @@ describe('BasePlace', () => { }); it('should create a new BasePlace instance with only _uuid and name', () => { - const minimalAttributes: BasePlaceAttributes = { - geography: undefined, - _uuid: validUUID2, - name: 'Sample Place', - }; + const minimalAttributes: BasePlaceAttributes = { geography: undefined, _uuid: validUUID2, name: 'Sample Place' }; const place = new BasePlace(minimalAttributes); expect(place).toBeInstanceOf(BasePlace); @@ -136,10 +125,7 @@ describe('validateParams', () => { shortname: 'SP', geography: { type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 2] - }, + geometry: { type: 'Point', coordinates: [1, 2] }, properties: { name: 'Sample Place', address: { @@ -162,7 +148,7 @@ describe('validateParams', () => { geocodingQueryString: 'Main St, City', lastAction: 'findPlace', deviceUsed: 'mobile', - zoom: 20, + zoom: 20 }; const errors = BasePlace.validateParams(validParams); @@ -184,7 +170,7 @@ describe('validateParams', () => { geocodingQueryString: 'Main St, City', lastAction: 'markerDragged', deviceUsed: 'desktop', - zoom: 18, + zoom: 18 }; const errors = BasePlace.validateParams(validParams); @@ -201,7 +187,13 @@ describe('validateParams', () => { geometry: { type: 'Polygon', // Invalid, should be Point coordinates: [ - [[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]] + [ + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0] + ] ] }, properties: { @@ -226,7 +218,7 @@ describe('validateParams', () => { geocodingQueryString: 'Main St, City', lastAction: 'mapClicked', deviceUsed: 'other', - zoom: 10, + zoom: 10 }; const errors = BasePlace.validateParams(invalidParams); @@ -249,7 +241,7 @@ describe('validateParams', () => { geocodingQueryString: 123, // Invalid type, lastAction: new Date(), // Invalid type, deviceUsed: {}, // Invalid type, - zoom: -1.23, // Invalid type, + zoom: -1.23 // Invalid type, }; const errors = BasePlace.validateParams(params); @@ -268,7 +260,7 @@ describe('validateParams', () => { new Error('BasePlace validateParams: geocodingQueryString should be a string'), new Error('BasePlace validateParams: lastAction should be a string'), new Error('BasePlace validateParams: deviceUsed should be a string'), - new Error('BasePlace validateParams: zoom should be a positive number'), + new Error('BasePlace validateParams: zoom should be a positive number') ]); }); @@ -285,5 +277,4 @@ describe('validateParams', () => { expect(instance.address).toBeInstanceOf(BaseAddress); expect(instance.address?.civicNumber).toEqual(baseAddressAttributes.civicNumber); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseSegment.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseSegment.test.ts index 7e3bb405a..7b2c44d17 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseSegment.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseSegment.test.ts @@ -14,11 +14,10 @@ import * as SAttr from '../attributeTypes/SegmentAttributes'; const validUUID = uuidV4(); describe('BaseSegment', () => { - const baseSegmentAttributes: BaseSegmentAttributes = { _uuid: validUUID, modeCategory: 'car' as SAttr.ModeCategory, - mode: 'carDriver' as SAttr.Mode, + mode: 'carDriver' as SAttr.Mode }; it('should create a new BaseSegment instance', () => { @@ -30,9 +29,7 @@ describe('BaseSegment', () => { }); it('should create a new BaseSegment instance with minimal attributes', () => { - const minimalAttributes: BaseSegmentAttributes = { - _uuid: validUUID, - }; + const minimalAttributes: BaseSegmentAttributes = { _uuid: validUUID }; const segment = new BaseSegment(minimalAttributes); expect(segment).toBeInstanceOf(BaseSegment); @@ -50,21 +47,14 @@ describe('BaseSegment', () => { }); it('should accept extended attributes', () => { - const extendedAttributes: ExtendedSegmentAttributes = { - ...baseSegmentAttributes, - customAttribute: 'Custom Value', - }; + const extendedAttributes: ExtendedSegmentAttributes = { ...baseSegmentAttributes, customAttribute: 'Custom Value' }; const segment = new BaseSegment(extendedAttributes); expect(segment).toBeInstanceOf(BaseSegment); }); it('should return an empty array for valid parameters', () => { - const params = { - modeCategory: 'walk', - mode: 'walk', - baseVehicle: new BaseVehicle({}), - }; + const params = { modeCategory: 'walk', mode: 'walk', baseVehicle: new BaseVehicle({}) }; const result = BaseSegment.validateParams(params); @@ -82,7 +72,7 @@ describe('BaseSegment', () => { it('should return an array of errors for invalid modeCategory', () => { const params = { modeCategory: 42, // Invalid type - mode: new Date(), // Invalid type + mode: new Date() // Invalid type }; const result = BaseSegment.validateParams(params); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseTrip.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseTrip.test.ts index b7d28b112..1adec83c5 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseTrip.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseTrip.test.ts @@ -16,17 +16,16 @@ import { WeightMethod } from '../WeightMethod'; const validUUID = uuidV4(); describe('BaseTrip', () => { - const weightMethodAttributes = { _uuid: uuidV4(), shortname: 'sample-shortname2', name: 'Sample Weight Method2', - description: 'Sample weight method description2', + description: 'Sample weight method description2' }; const baseTripAttributes: BaseTripAttributes = { _uuid: validUUID, - _weights: [{ weight: 34.444, method: new WeightMethod(weightMethodAttributes) }], + _weights: [{ weight: 34.444, method: new WeightMethod(weightMethodAttributes) }] }; it('should create a new BaseTrip instance', () => { @@ -36,9 +35,7 @@ describe('BaseTrip', () => { }); it('should create a new BaseTrip instance with minimal attributes', () => { - const minimalAttributes: BaseTripAttributes = { - _uuid: validUUID, - }; + const minimalAttributes: BaseTripAttributes = { _uuid: validUUID }; const trip = new BaseTrip(minimalAttributes); expect(trip).toBeInstanceOf(BaseTrip); @@ -54,10 +51,7 @@ describe('BaseTrip', () => { }); it('should accept extended attributes', () => { - const extendedAttributes: ExtendedTripAttributes = { - ...baseTripAttributes, - customAttribute: 'Custom Value', - }; + const extendedAttributes: ExtendedTripAttributes = { ...baseTripAttributes, customAttribute: 'Custom Value' }; const trip = new BaseTrip(extendedAttributes); expect(trip).toBeInstanceOf(BaseTrip); @@ -79,10 +73,7 @@ describe('BaseTrip', () => { baseOrigin: new BaseVisitedPlace({ basePlace: new BasePlace({} as BasePlaceAttributes) }), baseDestination: new BaseVisitedPlace({ basePlace: new BasePlace({} as BasePlaceAttributes) }), - baseSegments: [ - new BaseSegment({} as BaseSegmentAttributes), - new BaseSegment({} as BaseSegmentAttributes), - ], + baseSegments: [new BaseSegment({} as BaseSegmentAttributes), new BaseSegment({} as BaseSegmentAttributes)] }; const errors = BaseTrip.validateParams(params); @@ -90,15 +81,11 @@ describe('BaseTrip', () => { }); it('should return errors for invalid parameters', () => { - const params = { - _uuid: 'invalid-uuid', - }; + const params = { _uuid: 'invalid-uuid' }; const errors = BaseTrip.validateParams(params); expect(errors).toHaveLength(1); // Two errors expected - expect(errors).toEqual([ - new Error('Uuidable validateParams: _uuid should be a valid uuid'), - ]); + expect(errors).toEqual([new Error('Uuidable validateParams: _uuid should be a valid uuid')]); }); it('should accept empty params', () => { diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseTripChain.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseTripChain.test.ts index 54ecf2985..980957c03 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseTripChain.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseTripChain.test.ts @@ -16,15 +16,13 @@ import * as VPAttr from '../attributeTypes/VisitedPlaceAttributes'; const validUUID = uuidV4(); describe('BaseTripChain', () => { - const baseTripAttributes: BaseTripAttributes = { - _uuid: uuidV4() - }; + const baseTripAttributes: BaseTripAttributes = { _uuid: uuidV4() }; const weightMethodAttributes = { _uuid: uuidV4(), shortname: 'sample-shortname3', name: 'Sample Weight Method3', - description: 'Sample weight method description3', + description: 'Sample weight method description3' }; const baseTripChainAttributes: BaseTripChainAttributes = { @@ -34,7 +32,7 @@ describe('BaseTripChain', () => { isConstrained: true, category: 'simple' as TCAttr.TripChainCategory, mainActivityCategory: 'work' as VPAttr.ActivityCategory, - mainActivity: 'workOther' as VPAttr.Activity, + mainActivity: 'workOther' as VPAttr.Activity }; it('should create a new BaseTripChain instance', () => { @@ -53,7 +51,7 @@ describe('BaseTripChain', () => { _uuid: validUUID, isMultiloop: true, isConstrained: false, - category: 'complex' as TCAttr.TripChainCategory, + category: 'complex' as TCAttr.TripChainCategory }; const tripChain = new BaseTripChain(minimalAttributes); @@ -76,10 +74,7 @@ describe('BaseTripChain', () => { }); it('should accept extended attributes', () => { - const extendedAttributes: ExtendedTripChainAttributes = { - ...baseTripChainAttributes, - customAttribute: 'Custom Value', - }; + const extendedAttributes: ExtendedTripChainAttributes = { ...baseTripChainAttributes, customAttribute: 'Custom Value' }; const tripChain = new BaseTripChain(extendedAttributes); expect(tripChain).toBeInstanceOf(BaseTripChain); @@ -103,7 +98,7 @@ describe('BaseTripChain', () => { category: 'category', mainActivityCategory: 'work', mainActivity: 'workUsual', - _weights: [{ weight: 1, method: new WeightMethod(weightMethodAttributes) }], + _weights: [{ weight: 1, method: new WeightMethod(weightMethodAttributes) }] }; const errors = BaseTripChain.validateParams(params); @@ -118,7 +113,7 @@ describe('BaseTripChain', () => { category: 2355, mainActivityCategory: -324.5, mainActivity: {}, - _weights: [{ weight: 1, method: new WeightMethod(weightMethodAttributes) }], + _weights: [{ weight: 1, method: new WeightMethod(weightMethodAttributes) }] }; const errors = BaseTripChain.validateParams(params); @@ -128,7 +123,7 @@ describe('BaseTripChain', () => { new Error('BaseTripChain validateParams: isConstrained should be a boolean'), new Error('BaseTripChain validateParams: category should be a string'), new Error('BaseTripChain validateParams: mainActivityCategory should be a string'), - new Error('BaseTripChain validateParams: mainActivity should be a string'), + new Error('BaseTripChain validateParams: mainActivity should be a string') ]); }); @@ -143,5 +138,4 @@ describe('BaseTripChain', () => { expect(instance).toBeInstanceOf(BaseTripChain); expect(instance.category).toEqual(baseTripChainAttributes.category); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseVehicle.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseVehicle.test.ts index ae0fa3da1..b0e9df03e 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseVehicle.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseVehicle.test.ts @@ -14,12 +14,11 @@ import { WeightMethod } from '../WeightMethod'; const validUUID = uuidV4(); describe('BaseVehicle', () => { - const weightMethodAttributes = { _uuid: uuidV4(), shortname: 'sample-shortname4', name: 'Sample Weight Method4', - description: 'Sample weight method description4', + description: 'Sample weight method description4' }; const baseVehicleAttributes: BaseVehicleAttributes = { @@ -29,7 +28,7 @@ describe('BaseVehicle', () => { licensePlateNumber: 'ABC123', capacitySeated: 5, capacityStanding: 0, - _weights: [{ weight: 0.0001, method: new WeightMethod(weightMethodAttributes) }], + _weights: [{ weight: 0.0001, method: new WeightMethod(weightMethodAttributes) }] }; it('should create a new BaseVehicle instance', () => { @@ -44,11 +43,7 @@ describe('BaseVehicle', () => { }); it('should create a new BaseVehicle instance with minimal attributes', () => { - const minimalAttributes: BaseVehicleAttributes = { - _uuid: validUUID, - make: 'Honda' as VAttr.Make, - model: 'Civic' as VAttr.Model, - }; + const minimalAttributes: BaseVehicleAttributes = { _uuid: validUUID, make: 'Honda' as VAttr.Make, model: 'Civic' as VAttr.Model }; const vehicle = new BaseVehicle(minimalAttributes); expect(vehicle).toBeInstanceOf(BaseVehicle); @@ -69,10 +64,7 @@ describe('BaseVehicle', () => { }); it('should accept extended attributes', () => { - const extendedAttributes: ExtendedVehicleAttributes = { - ...baseVehicleAttributes, - customAttribute: 'Custom Value', - }; + const extendedAttributes: ExtendedVehicleAttributes = { ...baseVehicleAttributes, customAttribute: 'Custom Value' }; const vehicle = new BaseVehicle(extendedAttributes); expect(vehicle).toBeInstanceOf(BaseVehicle); @@ -81,7 +73,7 @@ describe('BaseVehicle', () => { it('should set weight and method correctly', () => { const vehicle = new BaseVehicle(baseVehicleAttributes); const weight: Weight = vehicle._weights?.[0] as Weight; - expect(weight.weight).toBe(.0001); + expect(weight.weight).toBe(0.0001); expect(weight.method).toBeInstanceOf(WeightMethod); expect(weight.method?.shortname).toEqual('sample-shortname4'); expect(weight.method?.name).toEqual('Sample Weight Method4'); @@ -89,28 +81,14 @@ describe('BaseVehicle', () => { }); it('should return an empty array for valid params', () => { - const params = { - _uuid: uuidV4(), - make: 'Toyota', - model: 'Camry', - licensePlateNumber: 'ABC123', - capacitySeated: 5, - capacityStanding: 0, - }; + const params = { _uuid: uuidV4(), make: 'Toyota', model: 'Camry', licensePlateNumber: 'ABC123', capacitySeated: 5, capacityStanding: 0 }; const errors = BaseVehicle.validateParams(params); expect(errors).toEqual([]); }); it('should return an error for invalid make', () => { - const params = { - _uuid: -34, - make: 123, - model: 456, - licensePlateNumber: {}, - capacitySeated: new Date(), - capacityStanding: -34.65, - }; + const params = { _uuid: -34, make: 123, model: 456, licensePlateNumber: {}, capacitySeated: new Date(), capacityStanding: -34.65 }; const errors = BaseVehicle.validateParams(params); expect(errors).toEqual([ @@ -119,7 +97,7 @@ describe('BaseVehicle', () => { new Error('BaseVehicle validateParams: model should be a string'), new Error('BaseVehicle validateParams: licensePlateNumber should be a string'), new Error('BaseVehicle validateParams: capacitySeated should be a positive integer'), - new Error('BaseVehicle validateParams: capacityStanding should be a positive integer'), + new Error('BaseVehicle validateParams: capacityStanding should be a positive integer') ]); }); @@ -134,5 +112,4 @@ describe('BaseVehicle', () => { expect(instance).toBeInstanceOf(BaseVehicle); expect(instance.make).toEqual(baseVehicleAttributes.make); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/BaseVisitedPlace.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/BaseVisitedPlace.test.ts index 335a8c937..9980c9e93 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/BaseVisitedPlace.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/BaseVisitedPlace.test.ts @@ -15,31 +15,21 @@ import { WeightMethod } from '../WeightMethod'; const validUUID = uuidV4(); describe('BaseVisitedPlace', () => { - const geojson = { type: 'Feature', id: 444, - geometry: { - type: 'Point', - coordinates: [23.5, -11.0033423], - }, - properties: { - foo: 'boo2', - bar: 'far2' - }, + geometry: { type: 'Point', coordinates: [23.5, -11.0033423] }, + properties: { foo: 'boo2', bar: 'far2' } } as GeoJSON.Feature; const weightMethodAttributes = { _uuid: uuidV4(), shortname: 'sample-shortname5', name: 'Sample Weight Method5', - description: 'Sample weight method description5', + description: 'Sample weight method description5' }; - const basePlaceAttributes: BasePlaceAttributes = { - geography: geojson, - _uuid: validUUID, - }; + const basePlaceAttributes: BasePlaceAttributes = { geography: geojson, _uuid: validUUID }; const baseVisitedPlaceAttributes: BaseVisitedPlaceAttributes = { _uuid: validUUID, @@ -49,7 +39,7 @@ describe('BaseVisitedPlace', () => { departureTime: 45000, // 12:30 PM in seconds since midnight activityCategory: 'school' as VPAttr.ActivityCategory, activity: 'schoolOther' as VPAttr.Activity, - _weights: [{ weight: 0.9911, method: new WeightMethod(weightMethodAttributes) }], + _weights: [{ weight: 0.9911, method: new WeightMethod(weightMethodAttributes) }] }; it('should create a new BaseVisitedPlace instance', () => { @@ -71,16 +61,12 @@ describe('BaseVisitedPlace', () => { }); it('should create a new BaseVisitedPlace instance with minimal attributes', () => { - const minimalBasePlaceAttributes: BasePlaceAttributes = { - geography: undefined, - _uuid: validUUID, - name: 'Minimal Test Place', - }; + const minimalBasePlaceAttributes: BasePlaceAttributes = { geography: undefined, _uuid: validUUID, name: 'Minimal Test Place' }; const minimalVisitedPlaceAttributes: BaseVisitedPlaceAttributes = { _uuid: validUUID, activityCategory: 'home' as VPAttr.ActivityCategory, - activity: 'home' as VPAttr.Activity, + activity: 'home' as VPAttr.Activity }; const visitedPlace = new BaseVisitedPlace({ ...minimalVisitedPlaceAttributes, basePlace: new BasePlace(minimalBasePlaceAttributes) }); @@ -106,10 +92,7 @@ describe('BaseVisitedPlace', () => { }); it('should accept extended attributes', () => { - const extendedVisitedPlaceAttributes: ExtendedVisitedPlaceAttributes = { - ...baseVisitedPlaceAttributes, - customAttribute: 'Custom Value', - }; + const extendedVisitedPlaceAttributes: ExtendedVisitedPlaceAttributes = { ...baseVisitedPlaceAttributes, customAttribute: 'Custom Value' }; const visitedPlace = new BaseVisitedPlace({ ...extendedVisitedPlaceAttributes, basePlace: new BasePlace(basePlaceAttributes) }); expect(visitedPlace).toBeInstanceOf(BaseVisitedPlace); @@ -118,7 +101,7 @@ describe('BaseVisitedPlace', () => { it('should set weight and method correctly', () => { const visitedPlace = new BaseVisitedPlace({ ...baseVisitedPlaceAttributes, basePlace: new BasePlace(basePlaceAttributes) }); const weight: Weight = visitedPlace._weights?.[0] as Weight; - expect(weight.weight).toBe(.9911); + expect(weight.weight).toBe(0.9911); expect(weight.method).toBeInstanceOf(WeightMethod); expect(weight.method?.shortname).toEqual('sample-shortname5'); expect(weight.method?.name).toEqual('Sample Weight Method5'); @@ -134,7 +117,7 @@ describe('BaseVisitedPlace', () => { arrivalTime: 36000, // 10:00 AM in seconds since midnight departureTime: 43200, // 12:00 PM in seconds since midnight activityCategory: 'work' as VPAttr.ActivityCategory, - activity: 'workUsual' as VPAttr.Activity, + activity: 'workUsual' as VPAttr.Activity }; const errors = BaseVisitedPlace.validateParams(validParams); expect(errors).toEqual([]); @@ -156,7 +139,7 @@ describe('BaseVisitedPlace', () => { departureTime: 43200, activityCategory: 'other' as VPAttr.ActivityCategory, activity: 'other' as VPAttr.Activity, - additionalAttribute: 'additionalValue', + additionalAttribute: 'additionalValue' }; const errors = BaseVisitedPlace.validateParams(validParams); expect(errors).toEqual([]); @@ -171,7 +154,7 @@ describe('BaseVisitedPlace', () => { arrivalTime: -1, // Negative arrival time departureTime: '12:00 PM', // Invalid departure time string activityCategory: 44, // Invalid activity category - activity: -123283764.34534, // Invalid activity + activity: -123283764.34534 // Invalid activity }; const errors = BaseVisitedPlace.validateParams(invalidParams); @@ -194,7 +177,7 @@ describe('BaseVisitedPlace', () => { arrivalTime: -1, // Negative arrival time departureTime: '12:00 PM', // Invalid departure time string activityCategory: 44, // Invalid activity category - activity: -123283764.34534, // Invalid activity + activity: -123283764.34534 // Invalid activity }; const validParams = { _uuid: uuidV4(), // Invalid UUID @@ -205,7 +188,7 @@ describe('BaseVisitedPlace', () => { departureTime: 43200, activityCategory: 'other' as VPAttr.ActivityCategory, activity: 'other' as VPAttr.Activity, - additionalAttribute: 'additionalValue', + additionalAttribute: 'additionalValue' }; const invalidResult = BaseVisitedPlace.create(invalidParams); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Home.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Home.test.ts index d62667369..b3d9c039d 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Home.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Home.test.ts @@ -24,7 +24,7 @@ describe('Home', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }); // Factory function to create fresh valid home attributes @@ -47,9 +47,9 @@ describe('Home', () => { type: 'Feature', geometry: { type: 'Point', - coordinates: [-73.561668, 45.508888], // Montreal coordinates + coordinates: [-73.561668, 45.508888] // Montreal coordinates }, - properties: {}, + properties: {} }, _weights: [{ weight: 1.5, method: new WeightMethod(createWeightMethodAttributes()) }], _isValid: true @@ -59,7 +59,7 @@ describe('Home', () => { const createExtendedHomeAttributes = (): { [key: string]: unknown } => ({ ...createValidHomeAttributes(), customAttribute1: 'home-value1', - customAttribute2: 'home-value2', + customAttribute2: 'home-value2' }); // Factory function to create extended attributes with address @@ -173,10 +173,7 @@ describe('Home', () => { it('should return errors for invalid geography', () => { const validHomeAttributes = createValidHomeAttributes(); - const invalidGeography = { - ...validHomeAttributes, - geography: { type: 'InvalidType' } - }; + const invalidGeography = { ...validHomeAttributes, geography: { type: 'InvalidType' } }; const result = Home.create(invalidGeography, registry); expect(hasErrors(result)).toBe(true); if (hasErrors(result)) { @@ -202,10 +199,7 @@ describe('Home', () => { it('should unserialize a Home instance with serialized data structure', () => { const validHomeAttributes = createValidHomeAttributes(); - const serializedData = { - _attributes: validHomeAttributes, - _customAttributes: { custom1: 'value1' } - }; + const serializedData = { _attributes: validHomeAttributes, _customAttributes: { custom1: 'value1' } }; const home = Home.unserialize(serializedData, registry); expect(home).toBeInstanceOf(Home); expect(home.name).toBe('Home Location'); @@ -229,10 +223,7 @@ describe('Home', () => { it('should invalidate invalid GeoJSON', () => { const validHomeAttributes = createValidHomeAttributes(); - const invalidGeographyParams = { - ...validHomeAttributes, - geography: { type: 'Invalid' } as unknown as GeoJSON.Feature - }; + const invalidGeographyParams = { ...validHomeAttributes, geography: { type: 'Invalid' } as unknown as GeoJSON.Feature }; const home = new Home(invalidGeographyParams, registry); expect(home.geographyIsValid()).toBe(false); }); @@ -245,9 +236,12 @@ describe('Home', () => { type: 'Feature', geometry: { type: 'LineString', - coordinates: [[0, 0], [1, 1]], + coordinates: [ + [0, 0], + [1, 1] + ] }, - properties: {}, + properties: {} } as unknown as GeoJSON.Feature }; const home = new Home(invalidGeometryParams, registry); @@ -276,14 +270,7 @@ describe('Home', () => { const validHomeAttributes = createValidHomeAttributes(); const home = new Home(validHomeAttributes, registry); expect(home.geography).toBeDefined(); - const newGeography = { - type: 'Feature' as const, - geometry: { - type: 'Point' as const, - coordinates: [-74.0, 40.7], - }, - properties: {}, - }; + const newGeography = { type: 'Feature' as const, geometry: { type: 'Point' as const, coordinates: [-74.0, 40.7] }, properties: {} }; home.geography = newGeography; expect(home.geography).toEqual(newGeography); }); @@ -310,14 +297,7 @@ describe('Home', () => { it('should get and set preGeography', () => { const validHomeAttributes = createValidHomeAttributes(); const home = new Home(validHomeAttributes, registry); - const preGeography = { - type: 'Feature' as const, - geometry: { - type: 'Point' as const, - coordinates: [-73.5, 45.5], - }, - properties: {}, - }; + const preGeography = { type: 'Feature' as const, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] }, properties: {} }; // This seems useless, but it is actually testing the getters and setters home.preGeography = preGeography; expect(home.preGeography).toEqual(preGeography); @@ -388,14 +368,7 @@ describe('Home', () => { test('should preserve preGeography through (un)serialize', () => { const validHomeAttributes = createValidHomeAttributes(); - const preGeography = { - type: 'Feature' as const, - geometry: { - type: 'Point' as const, - coordinates: [-73.5, 45.5], - }, - properties: {}, - }; + const preGeography = { type: 'Feature' as const, geometry: { type: 'Point' as const, coordinates: [-73.5, 45.5] }, properties: {} }; const attrs = { ...validHomeAttributes, preGeography }; const h1 = new Home(attrs, registry); const h2 = Home.unserialize(attrs, registry); @@ -404,4 +377,3 @@ describe('Home', () => { }); }); }); - diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Household.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Household.test.ts index 49008539a..7d91e0efa 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Household.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Household.test.ts @@ -24,7 +24,7 @@ describe('Household', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validAttributes: { [key: string]: unknown } = { @@ -45,43 +45,19 @@ describe('Household', () => { contactEmail: 'test@example.com', atLeastOnePersonWithDisability: 'yes', _weights: [{ weight: 1.5, method: new WeightMethod(weightMethodAttributes) }], - _isValid: true, + _isValid: true }; const extendedAttributes: { [key: string]: unknown } = { ...validAttributes, customAttribute: 'custom value', _members: [ - { - _uuid: uuidV4(), - age: 30, - ageGroup: 'adult', - gender: 'male', - _isValid: true, - }, - { - _uuid: uuidV4(), - age: 28, - ageGroup: 'adult', - gender: 'female', - _isValid: true, - }, + { _uuid: uuidV4(), age: 30, ageGroup: 'adult', gender: 'male', _isValid: true }, + { _uuid: uuidV4(), age: 28, ageGroup: 'adult', gender: 'female', _isValid: true } ], _vehicles: [ - { - _uuid: uuidV4(), - make: 'Toyota', - model: 'Corolla', - modelYear: 2020, - _isValid: true, - }, - { - _uuid: uuidV4(), - make: 'Honda', - model: 'Civic', - modelYear: 2021, - _isValid: true, - }, + { _uuid: uuidV4(), make: 'Toyota', model: 'Corolla', modelYear: 2020, _isValid: true }, + { _uuid: uuidV4(), make: 'Honda', model: 'Civic', modelYear: 2021, _isValid: true } ] }; @@ -93,9 +69,11 @@ describe('Household', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = Household.validateParams.toString(); - householdAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights').forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + householdAttributes + .filter((attribute) => attribute !== '_uuid' && attribute !== '_weights') + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -179,7 +157,7 @@ describe('Household', () => { electricBicycleNumber: 0, pluginHybridCarNumber: 0, electricCarNumber: 0, - hybridCarNumber: 0, + hybridCarNumber: 0 }; expect(Household.validateParams(ok)).toHaveLength(0); }); @@ -191,14 +169,8 @@ describe('Household', () => { }); test('should create a Household instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const householdAttributes = { - ...validAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const householdAttributes = { ...validAttributes, ...customAttributes }; const household = new Household(householdAttributes, registry); expect(household).toBeInstanceOf(Household); expect(household.attributes).toEqual(validAttributes); @@ -228,7 +200,7 @@ describe('Household', () => { ['contactPhoneNumber', '9876543210'], ['contactEmail', 'updated@example.com'], ['atLeastOnePersonWithDisability', 'no'], - ['preData', { importedHouseholdData: 'value', residents: 3 }], + ['preData', { importedHouseholdData: 'value', residents: 3 }] ])('should set and get %s', (attribute, value) => { const household = new Household(validAttributes, registry); household[attribute] = value; @@ -239,7 +211,7 @@ describe('Household', () => { test.each([ ['_uuid', extendedAttributes._uuid], ['customAttributes', { customAttribute: extendedAttributes.customAttribute }], - ['attributes', validAttributes], + ['attributes', validAttributes] ])('should set and get %s', (attribute, value) => { const household = new Household(extendedAttributes, registry); expect(household[attribute]).toEqual(value); @@ -250,7 +222,7 @@ describe('Household', () => { ['_isValid', false], ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]], ['_members', extendedAttributes._members], - ['_vehicles', extendedAttributes._vehicles], + ['_vehicles', extendedAttributes._vehicles] ])('should set and get %s', (attribute, value) => { const household = new Household(validAttributes, registry); household[attribute] = value; @@ -270,26 +242,11 @@ describe('Household', () => { test('should create Person instances for members when creating a Household instance', () => { const membersAttributes: { [key: string]: unknown }[] = [ - { - _uuid: uuidV4(), - age: 30, - ageGroup: 'adult', - gender: 'male', - _isValid: true, - }, - { - _uuid: uuidV4(), - age: 28, - ageGroup: 'adult', - gender: 'female', - _isValid: true, - }, + { _uuid: uuidV4(), age: 30, ageGroup: 'adult', gender: 'male', _isValid: true }, + { _uuid: uuidV4(), age: 28, ageGroup: 'adult', gender: 'female', _isValid: true } ]; - const householdAttributes: { [key: string]: unknown } = { - ...validAttributes, - _members: membersAttributes, - }; + const householdAttributes: { [key: string]: unknown } = { ...validAttributes, _members: membersAttributes }; const result = Household.create(householdAttributes, registry); expect(isOk(result)).toBe(true); @@ -304,19 +261,10 @@ describe('Household', () => { test('should create Vehicle instances for vehicles when creating a Household instance', () => { const vehiclesAttributes: { [key: string]: unknown }[] = [ - { - _uuid: uuidV4(), - make: 'Toyota', - model: 'Corolla', - modelYear: 2020, - _isValid: true, - }, + { _uuid: uuidV4(), make: 'Toyota', model: 'Corolla', modelYear: 2020, _isValid: true } ]; - const householdAttributes: { [key: string]: unknown } = { - ...validAttributes, - _vehicles: vehiclesAttributes, - }; + const householdAttributes: { [key: string]: unknown } = { ...validAttributes, _vehicles: vehiclesAttributes }; const result = Household.create(householdAttributes, registry); expect(isOk(result)).toBe(true); @@ -334,21 +282,18 @@ describe('Household', () => { age: 'invalid', // Invalid type, should be number ageGroup: 'adult', gender: 'male', - _isValid: true, + _isValid: true }, { _uuid: uuidV4(), age: 28, ageGroup: 123, // Invalid type, should be string gender: 'female', - _isValid: true, - }, + _isValid: true + } ]; - const householdAttributes: { [key: string]: unknown } = { - ...validAttributes, - _members: invalidMembersAttributes, - }; + const householdAttributes: { [key: string]: unknown } = { ...validAttributes, _members: invalidMembersAttributes }; const result = Household.create(householdAttributes, registry); expect(hasErrors(result)).toBe(true); @@ -365,14 +310,11 @@ describe('Household', () => { make: 123, // Invalid type, should be string model: 'Civic', modelYear: 2021, - _isValid: true, - }, + _isValid: true + } ]; - const householdAttributes: { [key: string]: unknown } = { - ...validAttributes, - _vehicles: invalidVehiclesAttributes, - }; + const householdAttributes: { [key: string]: unknown } = { ...validAttributes, _vehicles: invalidVehiclesAttributes }; const result = Household.create(householdAttributes, registry); expect(hasErrors(result)).toBe(true); @@ -380,5 +322,4 @@ describe('Household', () => { expect(errors.length).toBe(1); expect(errors[0].message).toContain('Vehicle 0 validateParams: make should be a string'); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Journey.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Journey.test.ts index 02a3718dd..b7c92ba0d 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Journey.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Journey.test.ts @@ -26,7 +26,7 @@ describe('Journey', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validAttributes: JourneyAttributes = { @@ -47,40 +47,17 @@ describe('Journey', () => { previousWeekRemoteWorkDays: { monday: true, tuesday: true, wednesday: true }, previousWeekTravelToWorkDays: { monday: true, tuesday: true }, _weights: [{ weight: 1.5, method: new WeightMethod(weightMethodAttributes) }], - _isValid: true, + _isValid: true }; const extendedAttributes: ExtendedJourneyAttributes = { ...validAttributes, customAttribute: 'Custom Value', _visitedPlaces: [ - { - _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - _isValid: true, - }, - ], - _trips: [ - { - _uuid: uuidV4(), - startDate: '2023-01-03', - _isValid: true, - }, - ], - _tripChains: [ - { - _uuid: uuidV4(), - category: 'simple', - _isValid: true, - }, + { _uuid: uuidV4(), geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, _isValid: true } ], + _trips: [{ _uuid: uuidV4(), startDate: '2023-01-03', _isValid: true }], + _tripChains: [{ _uuid: uuidV4(), category: 'simple', _isValid: true }] }; test('should instantiate a Journey instance', () => { @@ -91,9 +68,14 @@ describe('Journey', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = Journey.validateParams.toString(); - journeyAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute)).forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + journeyAttributes + .filter( + (attribute) => + attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute) + ) + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -151,14 +133,8 @@ describe('Journey', () => { }); test('should create a Journey instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const journeyAttributes = { - ...validAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const journeyAttributes = { ...validAttributes, ...customAttributes }; const journey = new Journey(journeyAttributes, registry); expect(journey).toBeInstanceOf(Journey); expect(journey.attributes).toEqual(validAttributes); @@ -216,7 +192,7 @@ describe('Journey', () => { ['didTrips', 'no'], ['previousWeekRemoteWorkDays', { friday: true, saturday: true, sunday: true }], ['previousWeekTravelToWorkDays', { thursday: true, friday: true }], - ['preData', { importedJourneyData: 'value', tripCount: 5 }], + ['preData', { importedJourneyData: 'value', tripCount: 5 }] ])('should set and get %s', (attribute, value) => { const journey = new Journey(validAttributes, registry); journey[attribute] = value; @@ -227,7 +203,7 @@ describe('Journey', () => { test.each([ ['_uuid', extendedAttributes._uuid], ['customAttributes', { customAttribute: extendedAttributes.customAttribute }], - ['attributes', validAttributes], + ['attributes', validAttributes] ])('should set and get %s', (attribute, value) => { const journey = new Journey(extendedAttributes, registry); expect(journey[attribute]).toEqual(value); @@ -240,7 +216,7 @@ describe('Journey', () => { ['_visitedPlaces', extendedAttributes._visitedPlaces], ['_trips', extendedAttributes._trips], ['_tripChains', extendedAttributes._tripChains], - ['personUuid', uuidV4()], + ['personUuid', uuidV4()] ])('should set and get %s', (attribute, value) => { const journey = new Journey(validAttributes, registry); journey[attribute] = value; @@ -505,23 +481,13 @@ describe('Journey', () => { _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - _isValid: true, - }, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, + _isValid: true + } + } ]; - const journeyAttributes: ExtendedJourneyAttributes = { - ...validAttributes, - _visitedPlaces: visitedPlaceAttributes, - }; + const journeyAttributes: ExtendedJourneyAttributes = { ...validAttributes, _visitedPlaces: visitedPlaceAttributes }; const result = Journey.create(journeyAttributes, registry); expect(isOk(result)).toBe(true); @@ -534,23 +500,14 @@ describe('Journey', () => { activity: visitedPlaceAttributes[0].activity, activityCategory: visitedPlaceAttributes[0].activityCategory, _weights: visitedPlaceAttributes[0]._weights, - _isValid: visitedPlaceAttributes[0]._isValid, + _isValid: visitedPlaceAttributes[0]._isValid }); }); test('should create Trip instances for trips when creating a Journey instance', () => { - const tripAttributes: TripAttributes[] = [ - { - _uuid: uuidV4(), - endDate: '2024-01-13', - _isValid: true, - }, - ]; + const tripAttributes: TripAttributes[] = [{ _uuid: uuidV4(), endDate: '2024-01-13', _isValid: true }]; - const journeyAttributes: ExtendedJourneyAttributes = { - ...validAttributes, - _trips: tripAttributes, - }; + const journeyAttributes: ExtendedJourneyAttributes = { ...validAttributes, _trips: tripAttributes }; const result = Journey.create(journeyAttributes, registry); expect(isOk(result)).toBe(true); @@ -561,18 +518,9 @@ describe('Journey', () => { }); test('should create TripChain instances for tripChains when creating a Journey instance', () => { - const tripChainAttributes: TripChainAttributes[] = [ - { - _uuid: uuidV4(), - category: 'simple', - _isValid: true, - }, - ]; + const tripChainAttributes: TripChainAttributes[] = [{ _uuid: uuidV4(), category: 'simple', _isValid: true }]; - const journeyAttributes: ExtendedJourneyAttributes = { - ...validAttributes, - _tripChains: tripChainAttributes, - }; + const journeyAttributes: ExtendedJourneyAttributes = { ...validAttributes, _tripChains: tripChainAttributes }; const result = Journey.create(journeyAttributes, registry); expect(isOk(result)).toBe(true); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Junction.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Junction.test.ts index b59556f07..c3472e9f3 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Junction.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Junction.test.ts @@ -24,7 +24,7 @@ describe('Junction', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validPlaceAttributes: ExtendedPlaceAttributes = { @@ -42,14 +42,7 @@ describe('Junction', () => { lastAction: 'findPlace', deviceUsed: 'tablet', zoom: 15, - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, _weights: [{ weight: 1.2, method: new WeightMethod(weightMethodAttributes) }], _isValid: true }; @@ -69,15 +62,12 @@ describe('Junction', () => { _isValid: true }; - const validJunctionAttributesWithPlace: { [key: string]: unknown } = { - ...validJunctionAttributes, - _place: validPlaceAttributes - }; + const validJunctionAttributesWithPlace: { [key: string]: unknown } = { ...validJunctionAttributes, _place: validPlaceAttributes }; const extendedJunctionAttributes: { [key: string]: unknown } = { ...validJunctionAttributesWithPlace, customAttribute1: 'value1', - customAttribute2: 'value2', + customAttribute2: 'value2' }; test('should create a Junction instance with valid attributes', () => { @@ -88,9 +78,14 @@ describe('Junction', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = Junction.validateParams.toString(); - junctionAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute)).forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + junctionAttributes + .filter( + (attribute) => + attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute) + ) + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -108,7 +103,7 @@ describe('Junction', () => { const invalidAttributes = 'foo' as any; const result = Junction.create(invalidAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(1); + expect(unwrap(result) as Error[]).toHaveLength(1); }); test('should create a Junction instance with extended attributes', () => { @@ -148,14 +143,8 @@ describe('Junction', () => { }); test('should create a Junction instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const junctionAttributesWithCustom = { - ...validJunctionAttributesWithPlace, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const junctionAttributesWithCustom = { ...validJunctionAttributesWithPlace, ...customAttributes }; const junction = new Junction(junctionAttributesWithCustom, registry); expect(junction).toBeInstanceOf(Junction); expect(junction.attributes).toEqual(validJunctionAttributes); @@ -176,7 +165,7 @@ describe('Junction', () => { ['preData', 'invalid'], ['preData', []], ['preData', new Date() as any], - ['preData', true as any], + ['preData', true as any] ])('should return an error for invalid %s', (param, value) => { const invalidAttributes = { ...validJunctionAttributesWithPlace, [param]: value }; const errors = Junction.validateParams(invalidAttributes); @@ -201,7 +190,7 @@ describe('Junction', () => { ['parkingType', 'interiorAssignedOrGuaranteed'], ['parkingFeeType', 'paidByEmployer'], ['transitPlaceType', 'busStation'], - ['preData', { importedJunctionData: 'value', waitTime: 5 }], + ['preData', { importedJunctionData: 'value', waitTime: 5 }] ])('should set and get %s', (attribute, value) => { const junction = new Junction(validJunctionAttributesWithPlace, registry); junction[attribute] = value; @@ -211,11 +200,11 @@ describe('Junction', () => { describe('Getters for attributes with no setters', () => { test.each([ ['_uuid', validJunctionAttributes._uuid], - ['customAttributes', { - customAttribute1: extendedJunctionAttributes.customAttribute1, - customAttribute2: extendedJunctionAttributes.customAttribute2 - }], - ['attributes', validJunctionAttributes], + [ + 'customAttributes', + { customAttribute1: extendedJunctionAttributes.customAttribute1, customAttribute2: extendedJunctionAttributes.customAttribute2 } + ], + ['attributes', validJunctionAttributes] ])('should set and get %s', (attribute, value) => { const junction = new Junction(extendedJunctionAttributes, registry); expect(junction[attribute]).toEqual(value); @@ -225,7 +214,7 @@ describe('Junction', () => { test.each([ ['_isValid', false], ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]], - ['_place', validPlaceAttributes], + ['_place', validPlaceAttributes] ])('should set and get %s', (attribute, value) => { const junction = new Junction(validJunctionAttributesWithPlace, registry); junction[attribute] = value; diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Organization.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Organization.test.ts index 425646710..96653a47f 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Organization.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Organization.test.ts @@ -24,7 +24,7 @@ describe('Organization', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validAttributes: OrganizationAttributes = { @@ -39,35 +39,21 @@ describe('Organization', () => { contactEmail: 'john.doe@example.com', revenueLevel: 'High', _weights: [{ weight: 1.5, method: new WeightMethod(weightMethodAttributes) }], - _isValid: true, + _isValid: true }; const extendedAttributes: ExtendedOrganizationAttributes = { ...validAttributes, customAttribute: 'Custom Value', - _vehicles: [ - { - _uuid: uuidV4(), - make: 'Toyota', - model: 'Camry', - _isValid: true, - }, - ], + _vehicles: [{ _uuid: uuidV4(), make: 'Toyota', model: 'Camry', _isValid: true }], _places: [ { _uuid: uuidV4(), name: 'Headquarters', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - _isValid: true, - }, - ], + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, + _isValid: true + } + ] }; test('should create an Organization instance with valid attributes', () => { @@ -78,9 +64,11 @@ describe('Organization', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = Organization.validateParams.toString(); - organizationAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights').forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + organizationAttributes + .filter((attribute) => attribute !== '_uuid' && attribute !== '_weights') + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -138,14 +126,8 @@ describe('Organization', () => { }); test('should create an Organization instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const organizationAttributes = { - ...validAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const organizationAttributes = { ...validAttributes, ...customAttributes }; const organization = new Organization(organizationAttributes, registry); expect(organization).toBeInstanceOf(Organization); expect(organization.attributes).toEqual(validAttributes); @@ -191,7 +173,7 @@ describe('Organization', () => { ['contactPhoneNumber', '9876543210'], ['contactEmail', 'jane.smith@example.com'], ['revenueLevel', 'Medium'], - ['preData', { importedOrgData: 'value', industry: 'tech' }], + ['preData', { importedOrgData: 'value', industry: 'tech' }] ])('should set and get %s', (attribute, value) => { const organization = new Organization(validAttributes, registry); organization[attribute] = value; @@ -202,7 +184,7 @@ describe('Organization', () => { test.each([ ['_uuid', extendedAttributes._uuid], ['customAttributes', { customAttribute: extendedAttributes.customAttribute }], - ['attributes', validAttributes], + ['attributes', validAttributes] ])('should set and get %s', (attribute, value) => { const organization = new Organization(extendedAttributes, registry); expect(organization[attribute]).toEqual(value); @@ -213,7 +195,7 @@ describe('Organization', () => { ['_isValid', false], ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]], ['_vehicles', extendedAttributes.vehicles], - ['_places', extendedAttributes.places], + ['_places', extendedAttributes.places] ])('should set and get %s', (attribute, value) => { const organization = new Organization(validAttributes, registry); organization[attribute] = value; @@ -233,19 +215,9 @@ describe('Organization', () => { describe('Composed Attributes', () => { test('should create Vehicle instances for vehicles when creating an Organization instance', () => { - const vehicleAttributes: VehicleAttributes[] = [ - { - _uuid: uuidV4(), - make: 'Honda', - model: 'Accord', - _isValid: true, - }, - ]; + const vehicleAttributes: VehicleAttributes[] = [{ _uuid: uuidV4(), make: 'Honda', model: 'Accord', _isValid: true }]; - const organizationAttributes: ExtendedOrganizationAttributes = { - ...validAttributes, - _vehicles: vehicleAttributes, - }; + const organizationAttributes: ExtendedOrganizationAttributes = { ...validAttributes, _vehicles: vehicleAttributes }; const result = Organization.create(organizationAttributes, registry); expect(isOk(result)).toBe(true); @@ -260,22 +232,12 @@ describe('Organization', () => { { _uuid: uuidV4(), name: 'Branch Office', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 1], - }, - properties: {}, - }, - _isValid: true, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 1] }, properties: {} }, + _isValid: true + } ]; - const organizationAttributes: ExtendedOrganizationAttributes = { - ...validAttributes, - _places: placeAttributes, - }; + const organizationAttributes: ExtendedOrganizationAttributes = { ...validAttributes, _places: placeAttributes }; const result = Organization.create(organizationAttributes, registry); expect(isOk(result)).toBe(true); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Person.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Person.test.ts index 742e5fa4a..ccd7cd51f 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Person.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Person.test.ts @@ -29,7 +29,7 @@ describe('Person', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validAttributes: { [key: string]: unknown } = { @@ -67,7 +67,7 @@ describe('Person', () => { contactPhoneNumber: '1234567890', contactEmail: 'john@example.com', _weights: [{ weight: 1.5, method: new WeightMethod(weightMethodAttributes) }], - _isValid: true, + _isValid: true }; const extendedAttributes: { [key: string]: unknown } = { @@ -76,40 +76,44 @@ describe('Person', () => { _workPlaces: [{ _uuid: uuidV4(), placeType: 'office', _isValid: true }], _schoolPlaces: [{ placeType: 'university', _isValid: true }], _vehicles: [{ model: 'foo', make: 'bar', _isValid: true }], - _journeys: [{ _uuid: uuidV4(), _isValid: true }], + _journeys: [{ _uuid: uuidV4(), _isValid: true }] }; const extendedInvalidWorkPlacesAttributes: { [key: string]: unknown } = { ...validAttributes, customAttribute: 'custom value', - _workPlaces: [{ custom: 123, _isValid: 123, parkingType: 123, parkingFeeType: 123 }], + _workPlaces: [{ custom: 123, _isValid: 123, parkingType: 123, parkingFeeType: 123 }] }; const extendedInvalidSchoolPlacesAttributes: { [key: string]: unknown } = { ...validAttributes, customAttribute: 'custom value', - _schoolPlaces: [{ custom: 333, _isValid: 111, parkingType: 123, parkingFeeType: 123 }], + _schoolPlaces: [{ custom: 333, _isValid: 111, parkingType: 123, parkingFeeType: 123 }] }; const extendedInvalidVehiclesAttributes: { [key: string]: unknown } = { ...validAttributes, customAttribute: 'custom value', - _vehicles: [{ custom: 333, _isValid: 111, model: 123, make: 234 }], + _vehicles: [{ custom: 333, _isValid: 111, model: 123, make: 234 }] }; const extendedInvalidJourneysAttributes: { [key: string]: unknown } = { ...validAttributes, customAttribute: 'custom value', - _journeys: [{ custom: 333, _isValid: 111 }, { _isValid: 111, _visitedPlaces: [{ _isValid: 111 }] }], + _journeys: [ + { custom: 333, _isValid: 111 }, + { _isValid: 111, _visitedPlaces: [{ _isValid: 111 }] } + ] }; - test('should have a validateParams section for each attribute', () => { const validateParamsCode = Person.validateParams.toString(); // exclude string attributes, since they are validated automatically in a loop: - nonStringAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights').forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + nonStringAttributes + .filter((attribute) => attribute !== '_uuid' && attribute !== '_weights') + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); nonStringAttributes.forEach((attribute) => { @@ -158,14 +162,8 @@ describe('Person', () => { }); test('should create a Person instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const placeAttributes = { - ...validAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const placeAttributes = { ...validAttributes, ...customAttributes }; const person = new Person(placeAttributes, registry); expect(person).toBeInstanceOf(Person); expect(person.attributes).toEqual(validAttributes); @@ -223,7 +221,7 @@ describe('Person', () => { ['_isValid', 'invalid'], ['_weights', 'invalid'], ['whoWillAnswerForThisPerson', 'invalid-uuid'], - ['isProxy', 'invalid'], + ['isProxy', 'invalid'] ])('should return an error for invalid %s', (param, value) => { const invalidAttributes = { ...validAttributes, [param]: value }; const result = Person.create(invalidAttributes, registry); @@ -255,7 +253,7 @@ describe('Person', () => { const invalidAttributes = 'foo' as any; const result = Person.create(invalidAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(1); + expect(unwrap(result) as Error[]).toHaveLength(1); }); test('should return no errors for valid attributes', () => { @@ -271,7 +269,7 @@ describe('Person', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const weightMethod = new WeightMethod(weightMethodAttributes); const weights: Weight[] = [{ weight: 1.5, method: weightMethod }]; @@ -290,15 +288,8 @@ describe('Person', () => { parkingType: 'interiorAssignedOrGuaranteed', parkingFeeType: 'paidByEmployee', name: 'testName', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - _isValid: true, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, + _isValid: true }; const workPlace = new WorkPlace(workPlaceAttributes, registry); const personAttributes: { [key: string]: unknown } = { ...validAttributes, _workPlaces: [workPlaceAttributes] }; @@ -312,7 +303,7 @@ describe('Person', () => { test('should return an error for invalid work places', () => { const result = Person.create(extendedInvalidWorkPlacesAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(3); + expect(unwrap(result) as Error[]).toHaveLength(3); }); }); @@ -323,15 +314,8 @@ describe('Person', () => { parkingType: 'streetside', parkingFeeType: 'paidByStudent', name: 'testName', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 1], - }, - properties: {}, - }, - _isValid: true, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 1] }, properties: {} }, + _isValid: true }; const schoolPlace = new SchoolPlace(schoolPlaceAttributes, registry); const personAttributes: { [key: string]: unknown } = { ...validAttributes, _schoolPlaces: [schoolPlaceAttributes] }; @@ -345,7 +329,7 @@ describe('Person', () => { test('should return an error for invalid school places', () => { const result = Person.create(extendedInvalidSchoolPlacesAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(3); + expect(unwrap(result) as Error[]).toHaveLength(3); }); }); @@ -380,7 +364,7 @@ describe('Person', () => { test('should return an error for invalid vehicles', () => { const result = Person.create(extendedInvalidVehiclesAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(3); + expect(unwrap(result) as Error[]).toHaveLength(3); }); }); @@ -399,7 +383,7 @@ describe('Person', () => { startTimePeriod: 'am', endTimePeriod: 'pm', name: 'testName', - type: 'testType', + type: 'testType' }; const journey = new Journey(journeyAttributes, registry); const personAttributes: { [key: string]: unknown } = { ...validAttributes, _journeys: [journeyAttributes] }; @@ -413,7 +397,7 @@ describe('Person', () => { test('should return an error for invalid journeys', () => { const result = Person.create(extendedInvalidJourneysAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(3); + expect(unwrap(result) as Error[]).toHaveLength(3); }); }); @@ -580,46 +564,38 @@ describe('Person', () => { const journey = new Journey({ _uuid: uuidV4(), _isValid: true }, registry); // Add a work visited place - const workVisitedPlace = new VisitedPlace({ - _uuid: uuidV4(), - activity: 'workUsual', - _sequence: 1, - _isValid: true, - _place: { + const workVisitedPlace = new VisitedPlace( + { _uuid: uuidV4(), - name: 'My Office', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-73.5, 45.5], - }, - properties: {}, - }, + activity: 'workUsual', + _sequence: 1, _isValid: true, + _place: { + _uuid: uuidV4(), + name: 'My Office', + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.5, 45.5] }, properties: {} }, + _isValid: true + } }, - }, registry); + registry + ); // Add a school visited place - const schoolVisitedPlace = new VisitedPlace({ - _uuid: uuidV4(), - activity: 'schoolUsual', - _sequence: 2, - _isValid: true, - _place: { + const schoolVisitedPlace = new VisitedPlace( + { _uuid: uuidV4(), - name: 'My University', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-73.6, 45.6], - }, - properties: {}, - }, + activity: 'schoolUsual', + _sequence: 2, _isValid: true, + _place: { + _uuid: uuidV4(), + name: 'My University', + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.6, 45.6] }, properties: {} }, + _isValid: true + } }, - }, registry); + registry + ); journey.addVisitedPlace(workVisitedPlace); journey.addVisitedPlace(schoolVisitedPlace); @@ -641,19 +617,15 @@ describe('Person', () => { const journey = new Journey({ _uuid: uuidV4(), _isValid: true }, registry); // Pre-populate work places - const existingWorkPlace = new WorkPlace({ - _uuid: uuidV4(), - name: 'Existing Office', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-73.5, 45.5], - }, - properties: {}, + const existingWorkPlace = new WorkPlace( + { + _uuid: uuidV4(), + name: 'Existing Office', + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.5, 45.5] }, properties: {} }, + _isValid: true }, - _isValid: true, - }, registry); + registry + ); person.workPlaces = [existingWorkPlace]; // Add a work visited place with same coordinates @@ -662,25 +634,13 @@ describe('Person', () => { activity: 'workUsual', _sequence: 1, _isValid: true, - attributes: { - _uuid: uuidV4(), - activity: 'workUsual', - _sequence: 1, - _isValid: true, - }, + attributes: { _uuid: uuidV4(), activity: 'workUsual', _sequence: 1, _isValid: true }, _place: { _uuid: uuidV4(), name: 'Same Office', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-73.5, 45.5], - }, - properties: {}, - }, - _isValid: true, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.5, 45.5] }, properties: {} }, + _isValid: true + } }; journey.addVisitedPlace(workVisitedPlace as any); @@ -697,19 +657,15 @@ describe('Person', () => { const journey = new Journey({ _uuid: uuidV4(), _isValid: true }, registry); // Pre-populate school places - const existingSchoolPlace = new SchoolPlace({ - _uuid: uuidV4(), - name: 'Existing School', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-73.6, 45.6], - }, - properties: {}, + const existingSchoolPlace = new SchoolPlace( + { + _uuid: uuidV4(), + name: 'Existing School', + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.6, 45.6] }, properties: {} }, + _isValid: true }, - _isValid: true, - }, registry); + registry + ); person.schoolPlaces = [existingSchoolPlace]; // Add a school visited place with same coordinates @@ -718,25 +674,13 @@ describe('Person', () => { activity: 'schoolUsual', _sequence: 1, _isValid: true, - attributes: { - _uuid: uuidV4(), - activity: 'schoolUsual', - _sequence: 1, - _isValid: true, - }, + attributes: { _uuid: uuidV4(), activity: 'schoolUsual', _sequence: 1, _isValid: true }, _place: { _uuid: uuidV4(), name: 'Same School', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-73.6, 45.6], - }, - properties: {}, - }, - _isValid: true, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.6, 45.6] }, properties: {} }, + _isValid: true + } }; journey.addVisitedPlace(schoolVisitedPlace as any); @@ -753,45 +697,37 @@ describe('Person', () => { const journey = new Journey({ _uuid: uuidV4(), _isValid: true }, registry); // Add different work activities - const workUsualPlace = new VisitedPlace({ - _uuid: uuidV4(), - activity: 'workUsual', - _sequence: 1, - _isValid: true, - _place: { + const workUsualPlace = new VisitedPlace( + { _uuid: uuidV4(), - name: 'Main Office', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-73.5, 45.5], - }, - properties: {}, - }, + activity: 'workUsual', + _sequence: 1, _isValid: true, + _place: { + _uuid: uuidV4(), + name: 'Main Office', + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.5, 45.5] }, properties: {} }, + _isValid: true + } }, - }, registry); + registry + ); - const workOtherPlace = new VisitedPlace({ - _uuid: uuidV4(), - activity: 'workOther', - _sequence: 2, - _isValid: true, - _place: { + const workOtherPlace = new VisitedPlace( + { _uuid: uuidV4(), - name: 'Client Office', - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-73.7, 45.7], - }, - properties: {}, - }, + activity: 'workOther', + _sequence: 2, _isValid: true, + _place: { + _uuid: uuidV4(), + name: 'Client Office', + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.7, 45.7] }, properties: {} }, + _isValid: true + } }, - }, registry); + registry + ); journey.addVisitedPlace(workUsualPlace); journey.addVisitedPlace(workOtherPlace); @@ -834,18 +770,8 @@ describe('Person', () => { activity: 'workUsual', _sequence: 1, _isValid: true, - attributes: { - _uuid: uuidV4(), - activity: 'workUsual', - _sequence: 1, - _isValid: true, - }, - _place: { - _uuid: uuidV4(), - name: 'Remote Work', - geography: undefined, - _isValid: true, - }, + attributes: { _uuid: uuidV4(), activity: 'workUsual', _sequence: 1, _isValid: true }, + _place: { _uuid: uuidV4(), name: 'Remote Work', geography: undefined, _isValid: true } }; journey.addVisitedPlace(workVisitedPlace as any); @@ -891,7 +817,7 @@ describe('Person', () => { ['nickname', 'Johnny'], ['contactPhoneNumber', '9876543210'], ['contactEmail', 'johnny@example.com'], - ['preData', { importedPersonData: 'value', age: 35 }], + ['preData', { importedPersonData: 'value', age: 35 }] ])('should set and get %s', (attribute, value) => { const person = new Person(validAttributes, registry); person[attribute] = value; @@ -902,7 +828,7 @@ describe('Person', () => { test.each([ ['_uuid', extendedAttributes._uuid], ['customAttributes', { customAttribute: extendedAttributes.customAttribute }], - ['attributes', validAttributes], + ['attributes', validAttributes] ])('should set and get %s', (attribute, value) => { const person = new Person(extendedAttributes, registry); expect(person[attribute]).toEqual(value); @@ -916,7 +842,7 @@ describe('Person', () => { ['_schoolPlaces', () => [new SchoolPlace({ placeType: 'college', _isValid: true }, registry)]], ['_vehicles', () => [new Vehicle({ modelYear: 2024, _isValid: true }, registry)]], ['_journeys', () => [new Journey({ name: 'test', _isValid: true }, registry)]], - ['householdUuid', () => uuidV4()], + ['householdUuid', () => uuidV4()] ])('should set and get %s', (attribute, valueFactory) => { const person = new Person(validAttributes, registry); const value = valueFactory(); @@ -934,7 +860,4 @@ describe('Person', () => { expect(p2.preData).toEqual({ importedPersonData: 'value', age: 35 }); }); }); - }); - - diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Place.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Place.test.ts index feca300c5..f9f942f97 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Place.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Place.test.ts @@ -24,7 +24,7 @@ describe('Place', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validPlaceAttributes: ExtendedPlaceAttributes = { @@ -42,31 +42,13 @@ describe('Place', () => { lastAction: 'findPlace', deviceUsed: 'tablet', zoom: 15, - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - preGeography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, + preGeography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, _weights: [{ weight: 1.2, method: new WeightMethod(weightMethodAttributes) }], _isValid: true }; - const extendedPlaceAttributes: { [key: string]: unknown } = { - ...validPlaceAttributes, - customAttribute1: 'value1', - customAttribute2: 'value2', - }; + const extendedPlaceAttributes: { [key: string]: unknown } = { ...validPlaceAttributes, customAttribute1: 'value1', customAttribute2: 'value2' }; const extendedAttributesWithAddress: { [key: string]: unknown } = { ...validPlaceAttributes, @@ -86,23 +68,16 @@ describe('Place', () => { const extendedInvalidAddressAttributes: { [key: string]: unknown } = { ...validPlaceAttributes, customAttribute: 'custom value', - _address: { - _isValid: 123, - fullAddress: 123, - civicNumber: 'foo', - streetName: 123, - municipalityName: 123, - region: 123, - country: 123 - } + _address: { _isValid: 123, fullAddress: 123, civicNumber: 'foo', streetName: 123, municipalityName: 123, region: 123, country: 123 } }; - test('should have a validateParams section for each attribute', () => { const validateParamsCode = Place.validateParams.toString(); - placeAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights').forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + placeAttributes + .filter((attribute) => attribute !== '_uuid' && attribute !== '_weights') + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should create a Place instance with valid attributes using constructor', () => { @@ -115,7 +90,7 @@ describe('Place', () => { const invalidAttributes = 'foo' as any; const result = Place.create(invalidAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(1); + expect(unwrap(result) as Error[]).toHaveLength(1); }); test('should create a Place instance with valid attributes', () => { @@ -175,14 +150,8 @@ describe('Place', () => { }); test('should create a Place instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const placeAttributes = { - ...validPlaceAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const placeAttributes = { ...validPlaceAttributes, ...customAttributes }; const place = new Place(placeAttributes, registry); expect(place).toBeInstanceOf(Place); expect(place.attributes).toEqual(validPlaceAttributes); @@ -195,7 +164,7 @@ describe('Place', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const weightMethod = new WeightMethod(weightMethodAttributes); const weights: Weight[] = [{ weight: 1.5, method: weightMethod }]; @@ -228,7 +197,7 @@ describe('Place', () => { ['preData', []], ['preData', new Date() as any], ['preData', true as any], - ['preGeography', 'invalid'], + ['preGeography', 'invalid'] ])('should return an error for invalid %s', (param, value) => { const invalidAttributes = { ...validPlaceAttributes, [param]: value }; const errors = Place.validateParams(invalidAttributes); @@ -244,7 +213,7 @@ describe('Place', () => { test('should return an error for invalid address', () => { const result = Place.create(extendedInvalidAddressAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(7); + expect(unwrap(result) as Error[]).toHaveLength(7); }); }); @@ -263,23 +232,9 @@ describe('Place', () => { ['lastAction', 'mapClicked'], ['deviceUsed', 'web'], ['zoom', 12], - ['geography', { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 1], - }, - properties: {}, - }], + ['geography', { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 1] }, properties: {} }], ['preData', { importedField: 'importedValue', anotherField: 123 }], - ['preGeography', { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [2, 2], - }, - properties: {}, - }], + ['preGeography', { type: 'Feature', geometry: { type: 'Point', coordinates: [2, 2] }, properties: {} }] ])('should set and get %s', (attribute, value) => { const place = new Place(validPlaceAttributes, registry); place[attribute] = value; @@ -289,11 +244,11 @@ describe('Place', () => { describe('Getters for attributes with no setters', () => { test.each([ ['_uuid', extendedPlaceAttributes._uuid], - ['customAttributes', { - customAttribute1: extendedPlaceAttributes.customAttribute1, - customAttribute2: extendedPlaceAttributes.customAttribute2 - }], - ['attributes', validPlaceAttributes], + [ + 'customAttributes', + { customAttribute1: extendedPlaceAttributes.customAttribute1, customAttribute2: extendedPlaceAttributes.customAttribute2 } + ], + ['attributes', validPlaceAttributes] ])('should set and get %s', (attribute, value) => { const place = new Place(extendedPlaceAttributes, registry); expect(place[attribute]).toEqual(value); @@ -302,7 +257,7 @@ describe('Place', () => { test.each([ ['_isValid', false], - ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]], + ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]] ])('should set and get %s', (attribute, value) => { const place = new Place(validPlaceAttributes, registry); place[attribute] = value; @@ -320,14 +275,7 @@ describe('Place', () => { }); test('should preserve preGeography through (un)serialize', () => { - const preGeography = { - type: 'Feature' as const, - geometry: { - type: 'Point' as const, - coordinates: [2, 2], - }, - properties: {}, - }; + const preGeography = { type: 'Feature' as const, geometry: { type: 'Point' as const, coordinates: [2, 2] }, properties: {} }; const attrs = { ...validPlaceAttributes, preGeography }; const p1 = new Place(attrs, registry); const p2 = Place.unserialize(attrs, registry); @@ -355,7 +303,7 @@ describe('Place', () => { postalCode: 'X9Y 8Z7', postalId: 'postal-12345', combinedAddressUuid: uuidV4(), - _isValid: true, + _isValid: true }; const address = new Address(addressAttributes); const placeAttributes: { [key: string]: unknown } = { ...validPlaceAttributes, _address: addressAttributes }; @@ -380,7 +328,7 @@ describe('Place', () => { postalCode: 'A1B 2C3', postalId: 'test-postal-id', combinedAddressUuid: uuidV4(), - _isValid: true, + _isValid: true }; const placeAttributes: { [key: string]: unknown } = { ...validPlaceAttributes, _address: addressAttributes }; const place = new Place(placeAttributes, registry); @@ -403,7 +351,7 @@ describe('Place', () => { region: 'Validation Region', country: 'Validation Country', combinedAddressUuid: uuidV4(), - _isValid: true, + _isValid: true }; const errors = Address.validateParams(addressAttributes); @@ -441,9 +389,17 @@ describe('Place', () => { type: 'Feature', geometry: { type: 'Polygon', - coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], + coordinates: [ + [ + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0] + ] + ] }, - properties: {}, + properties: {} } as any; expect(place.geographyIsValid()).toBe(false); }); @@ -461,7 +417,6 @@ describe('Place', () => { expect(place.preGeographyIsValid()).toBeUndefined(); }); - test('should return false for invalid preGeography', () => { const place = new Place(validPlaceAttributes, registry); place.preGeography = 'invalid' as any; @@ -480,9 +435,17 @@ describe('Place', () => { type: 'Feature', geometry: { type: 'Polygon', - coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]], + coordinates: [ + [ + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0] + ] + ] }, - properties: {}, + properties: {} } as any; expect(place.preGeographyIsValid()).toBe(false); }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Routing.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Routing.test.ts index 96f42572d..e993a7e98 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Routing.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Routing.test.ts @@ -11,7 +11,6 @@ import { isOk, hasErrors, unwrap } from '../../../types/Result.type'; import { SurveyObjectsRegistry } from '../SurveyObjectsRegistry'; describe('Routing', () => { - let registry: SurveyObjectsRegistry; beforeEach(() => { @@ -28,13 +27,10 @@ describe('Routing', () => { mode: 'walking', status: 'success', travelTimeS: 3600, - travelDistanceM: 5000, + travelDistanceM: 5000 }; - const extendedAttributes: { [key: string]: unknown } = { - ...validAttributes, - customAttribute: 'custom value', - }; + const extendedAttributes: { [key: string]: unknown } = { ...validAttributes, customAttribute: 'custom value' }; test('should create a Routing instance with valid attributes', () => { const routing = new Routing(validAttributes); @@ -64,7 +60,7 @@ describe('Routing', () => { const invalidAttributes = 'foo' as any; const result = Routing.create(invalidAttributes); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(1); + expect(unwrap(result) as Error[]).toHaveLength(1); }); test('should create a Routing instance with extended attributes', () => { @@ -112,7 +108,7 @@ describe('Routing', () => { ['mode', 123], ['status', 123], ['travelTimeS', -1], - ['travelDistanceM', -1], + ['travelDistanceM', -1] ])('should return an error for invalid %s', (param, value) => { const invalidAttributes = { ...validAttributes, [param]: value }; const errors = Routing.validateParams(invalidAttributes); @@ -135,7 +131,7 @@ describe('Routing', () => { ['mode', 'cycling'], ['status', 'noRoutingFound'], ['travelTimeS', 5400], - ['travelDistanceM', 7000], + ['travelDistanceM', 7000] ])('should set and get %s', (attribute, value) => { const routing = new Routing(validAttributes); routing[attribute] = value; @@ -147,7 +143,7 @@ describe('Routing', () => { test.each([ ['_uuid', extendedAttributes._uuid], ['customAttributes', { customAttribute: extendedAttributes.customAttribute }], - ['attributes', validAttributes], + ['attributes', validAttributes] ])('should set and get %s', (attribute, value) => { const routing = new Routing(extendedAttributes); expect(routing[attribute]).toEqual(value); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Sample.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Sample.test.ts index c9ee0d7bc..b21053784 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Sample.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Sample.test.ts @@ -9,7 +9,6 @@ import { Sample } from '../Sample'; import { v4 as uuidV4 } from 'uuid'; describe('Sample Class', () => { - const sampleName = 'Sample Name'; const sampleShortname = 'sample_name'; @@ -31,12 +30,7 @@ describe('Sample Class', () => { }); it('should validate params', () => { - const validSampleAttributes = { - _uuid: uuidV4(), - name: 'Sample Name', - shortname: 'SN', - description: 'Sample description', - }; + const validSampleAttributes = { _uuid: uuidV4(), name: 'Sample Name', shortname: 'SN', description: 'Sample description' }; // Valid attributes expect(Sample.validateParams(validSampleAttributes)).toEqual([]); @@ -70,5 +64,4 @@ describe('Sample Class', () => { expect(missingRequired.length).toEqual(1); expect(missingRequired[0].message).toEqual('Sample validateParams: shortname is required'); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/SchoolPlace.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/SchoolPlace.test.ts index 3cc52fa01..45d291d6f 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/SchoolPlace.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/SchoolPlace.test.ts @@ -23,7 +23,7 @@ describe('SchoolPlace', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validPlaceAttributes: { [key: string]: unknown } = { @@ -41,14 +41,7 @@ describe('SchoolPlace', () => { lastAction: 'findPlace', deviceUsed: 'tablet', zoom: 15, - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, _weights: [{ weight: 1.2, method: new WeightMethod(weightMethodAttributes) }], _isValid: true }; @@ -56,13 +49,13 @@ describe('SchoolPlace', () => { const validSchoolPlaceAttributes: { [key: string]: unknown } = { ...validPlaceAttributes, parkingType: 'interiorAssignedOrGuaranteed', - parkingFeeType: 'paidByEmployee', + parkingFeeType: 'paidByEmployee' }; const extendedSchoolPlaceAttributes: { [key: string]: unknown } = { ...validSchoolPlaceAttributes, customAttribute1: 'value1', - customAttribute2: 'value2', + customAttribute2: 'value2' }; test('should create a SchoolPlace instance with valid attributes', () => { @@ -73,13 +66,15 @@ describe('SchoolPlace', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = SchoolPlace.validateParams.toString(); - placeAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights').forEach((attributeName) => { - expect(validateParamsCode).toContain('\''+attributeName+'\''); - }); + placeAttributes + .filter((attribute) => attribute !== '_uuid' && attribute !== '_weights') + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { - const place = new SchoolPlace({ ...validSchoolPlaceAttributes, _uuid: '11b78eb3-a5d8-484d-805d-1f947160bb9e' }, registry ); + const place = new SchoolPlace({ ...validSchoolPlaceAttributes, _uuid: '11b78eb3-a5d8-484d-805d-1f947160bb9e' }, registry); expect(place._uuid).toBe('11b78eb3-a5d8-484d-805d-1f947160bb9e'); }); @@ -93,7 +88,7 @@ describe('SchoolPlace', () => { const invalidAttributes = 'foo' as any; const result = SchoolPlace.create(invalidAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(1); + expect(unwrap(result) as Error[]).toHaveLength(1); }); test('should create a SchoolPlace instance with extended attributes', () => { @@ -133,14 +128,8 @@ describe('SchoolPlace', () => { }); test('should create a SchoolPlace instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const placeAttributes = { - ...validPlaceAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const placeAttributes = { ...validPlaceAttributes, ...customAttributes }; const place = new SchoolPlace(placeAttributes, registry); expect(place).toBeInstanceOf(SchoolPlace); expect(place.attributes).toEqual(validPlaceAttributes); @@ -150,7 +139,7 @@ describe('SchoolPlace', () => { describe('validateParams', () => { test.each([ ['parkingType', 123], - ['parkingFeeType', 123], + ['parkingFeeType', 123] ])('should return an error for invalid %s', (param, value) => { const invalidAttributes = { ...validSchoolPlaceAttributes, [param]: value }; const errors = SchoolPlace.validateParams(invalidAttributes); @@ -169,14 +158,7 @@ describe('SchoolPlace', () => { ['parkingType', 'streetside'], ['parkingFeeType', 'paidByStudent'], ['preData', { importedSchoolPlaceData: 'value', studentCount: 250 }], - ['preGeography', { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-73.7, 45.7], - }, - properties: {}, - }], + ['preGeography', { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.7, 45.7] }, properties: {} }] ])('should set and get %s', (attribute, value) => { const schoolPlace = new SchoolPlace(validSchoolPlaceAttributes, registry); schoolPlace[attribute] = value; @@ -186,11 +168,14 @@ describe('SchoolPlace', () => { describe('Getters for attributes with no setters', () => { test.each([ ['_uuid', validSchoolPlaceAttributes._uuid], - ['customAttributes', { - customAttribute1: extendedSchoolPlaceAttributes.customAttribute1, - customAttribute2: extendedSchoolPlaceAttributes.customAttribute2 - }], - ['attributes', validSchoolPlaceAttributes], + [ + 'customAttributes', + { + customAttribute1: extendedSchoolPlaceAttributes.customAttribute1, + customAttribute2: extendedSchoolPlaceAttributes.customAttribute2 + } + ], + ['attributes', validSchoolPlaceAttributes] ])('should set and get %s', (attribute, value) => { const schoolPlace = new SchoolPlace(extendedSchoolPlaceAttributes, registry); expect(schoolPlace[attribute]).toEqual(value); @@ -199,7 +184,7 @@ describe('SchoolPlace', () => { test.each([ ['_isValid', false], - ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]], + ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]] ])('should set and get %s', (attribute, value) => { const schoolPlace = new SchoolPlace(validSchoolPlaceAttributes, registry); schoolPlace[attribute] = value; @@ -217,14 +202,7 @@ describe('SchoolPlace', () => { }); test('should preserve preGeography through (un)serialize', () => { - const preGeography = { - type: 'Feature' as const, - geometry: { - type: 'Point' as const, - coordinates: [-73.7, 45.7], - }, - properties: {}, - }; + const preGeography = { type: 'Feature' as const, geometry: { type: 'Point' as const, coordinates: [-73.7, 45.7] }, properties: {} }; const attrs = { ...validSchoolPlaceAttributes, preGeography }; const sp1 = new SchoolPlace(attrs, registry); const sp2 = SchoolPlace.unserialize(attrs, registry); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Segment.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Segment.test.ts index deebacaad..a810e343d 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Segment.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Segment.test.ts @@ -26,7 +26,7 @@ describe('Segment', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validAttributes: { [key: string]: unknown } = { @@ -62,7 +62,7 @@ describe('Segment', () => { _transitCalculatedRoutings: [{ mode: 'transit' }], _walkingCalculatedRoutings: [{ mode: 'walking' }], _cyclingCalculatedRoutings: [{ mode: 'cycling' }], - _drivingCalculatedRoutings: [{ mode: 'driving' }], + _drivingCalculatedRoutings: [{ mode: 'driving' }] }; test('should create a Segment instance with valid attributes', () => { @@ -73,9 +73,14 @@ describe('Segment', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = Segment.validateParams.toString(); - segmentAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute)).forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + segmentAttributes + .filter( + (attribute) => + attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute) + ) + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -93,7 +98,7 @@ describe('Segment', () => { const invalidAttributes = 'foo' as any; const result = Segment.create(invalidAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(1); + expect(unwrap(result) as Error[]).toHaveLength(1); }); test('should create a Segment instance with extended attributes', () => { @@ -126,14 +131,8 @@ describe('Segment', () => { }); test('should create a Segment instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const segmentAttributes = { - ...validAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const segmentAttributes = { ...validAttributes, ...customAttributes }; const segment = new Segment(segmentAttributes, registry); expect(segment).toBeInstanceOf(Segment); expect(segment.attributes).toEqual(validAttributes); @@ -191,7 +190,7 @@ describe('Segment', () => { ['paidForParking', false], ['onDemandType', 'pickupAtOrigin'], ['busLines', ['Line 3', 'Line 4']], - ['preData', { importedSegmentData: 'value', mode: 'bus' }], + ['preData', { importedSegmentData: 'value', mode: 'bus' }] ])('should set and get %s', (attribute, value) => { const segment = new Segment(validAttributes, registry); segment[attribute] = value; @@ -210,7 +209,7 @@ describe('Segment', () => { ['_transitCalculatedRoutings', () => [new Routing({ mode: 'transit' }), new Routing({ mode: 'transit' })]], ['_walkingCalculatedRoutings', () => [new Routing({ mode: 'walking' }), new Routing({ mode: 'walking' })]], ['_cyclingCalculatedRoutings', () => [new Routing({ mode: 'cycling' }), new Routing({ mode: 'cycling' })]], - ['_drivingCalculatedRoutings', () => [new Routing({ mode: 'driving' }), new Routing({ mode: 'driving' })]], + ['_drivingCalculatedRoutings', () => [new Routing({ mode: 'driving' }), new Routing({ mode: 'driving' })]] ])('should set and get %s', (attribute, valueFactory) => { const segment = new Segment(validAttributes, registry); const value = valueFactory(); @@ -223,7 +222,7 @@ describe('Segment', () => { ['_uuid', extendedAttributes._uuid], ['modeCategory', 'transit'], ['customAttributes', { customAttribute: extendedAttributes.customAttribute }], - ['attributes', validAttributes], + ['attributes', validAttributes] ])('should set and get %s', (attribute, value) => { const segment = new Segment(extendedAttributes, registry); expect(segment[attribute]).toEqual(value); @@ -266,12 +265,9 @@ describe('Segment', () => { }); describe('Invalid Routing Attributes', () => { - it('should report errors for invalid transitDeclaredRouting', () => { - const invalidRouting = { '_uuid': 'foo' }; - const segment = Segment.create({ - _transitDeclaredRouting: invalidRouting - }, registry); + const invalidRouting = { _uuid: 'foo' }; + const segment = Segment.create({ _transitDeclaredRouting: invalidRouting }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: Uuidable validateParams: _uuid should be a valid uuid'); @@ -279,19 +275,15 @@ describe('Segment', () => { it('should report errors for invalid transitDeclaredRouting', () => { const invalidRouting = 123; - const segment = Segment.create({ - _transitDeclaredRouting: invalidRouting - }, registry); + const segment = Segment.create({ _transitDeclaredRouting: invalidRouting }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: TransitRouting validateParams: params should be a plain object (Record)'); }); it('should report errors for invalid walkingDeclaredRouting', () => { - const invalidRouting = { '_uuid': 'foo' }; - const segment = Segment.create({ - _walkingDeclaredRouting: invalidRouting - }, registry); + const invalidRouting = { _uuid: 'foo' }; + const segment = Segment.create({ _walkingDeclaredRouting: invalidRouting }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: Uuidable validateParams: _uuid should be a valid uuid'); @@ -299,19 +291,15 @@ describe('Segment', () => { it('should report errors for invalid walkingDeclaredRouting', () => { const invalidRouting = 123; - const segment = Segment.create({ - _walkingDeclaredRouting: invalidRouting - }, registry); + const segment = Segment.create({ _walkingDeclaredRouting: invalidRouting }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: WalkingRouting validateParams: params should be a plain object (Record)'); }); it('should report errors for invalid cyclingDeclaredRouting', () => { - const invalidRouting = { '_uuid': 'foo' }; - const segment = Segment.create({ - _cyclingDeclaredRouting: invalidRouting - }, registry); + const invalidRouting = { _uuid: 'foo' }; + const segment = Segment.create({ _cyclingDeclaredRouting: invalidRouting }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: Uuidable validateParams: _uuid should be a valid uuid'); @@ -319,19 +307,15 @@ describe('Segment', () => { it('should report errors for invalid cyclingDeclaredRouting', () => { const invalidRouting = 123; - const segment = Segment.create({ - _cyclingDeclaredRouting: invalidRouting - }, registry); + const segment = Segment.create({ _cyclingDeclaredRouting: invalidRouting }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: CyclingRouting validateParams: params should be a plain object (Record)'); }); it('should report errors for invalid drivingDeclaredRouting', () => { - const invalidRouting = { '_uuid': 'foo' }; - const segment = Segment.create({ - _drivingDeclaredRouting: invalidRouting - }, registry); + const invalidRouting = { _uuid: 'foo' }; + const segment = Segment.create({ _drivingDeclaredRouting: invalidRouting }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: Uuidable validateParams: _uuid should be a valid uuid'); @@ -339,21 +323,16 @@ describe('Segment', () => { it('should report errors for invalid drivingDeclaredRouting', () => { const invalidRouting = 123; - const segment = Segment.create({ - _drivingDeclaredRouting: invalidRouting - }, registry); + const segment = Segment.create({ _drivingDeclaredRouting: invalidRouting }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: DrivingRouting validateParams: params should be a plain object (Record)'); }); - // Arrays: it('should report errors for invalid transitCalculatedRoutings', () => { - const invalidRouting = { '_uuid': 'bar' }; - const segment = Segment.create({ - _transitCalculatedRoutings: [invalidRouting] - }, registry); + const invalidRouting = { _uuid: 'bar' }; + const segment = Segment.create({ _transitCalculatedRoutings: [invalidRouting] }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: Uuidable validateParams: _uuid should be a valid uuid'); @@ -361,19 +340,15 @@ describe('Segment', () => { it('should report errors for invalid transitCalculatedRoutings', () => { const invalidRouting = 123; - const segment = Segment.create({ - _transitCalculatedRoutings: [invalidRouting] - }, registry); + const segment = Segment.create({ _transitCalculatedRoutings: [invalidRouting] }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: TransitRouting validateParams: params should be a plain object (Record)'); }); it('should report errors for invalid walkingCalculatedRoutings', () => { - const invalidRouting = { '_uuid': 'bar' }; - const segment = Segment.create({ - _walkingCalculatedRoutings: [invalidRouting] - }, registry); + const invalidRouting = { _uuid: 'bar' }; + const segment = Segment.create({ _walkingCalculatedRoutings: [invalidRouting] }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: Uuidable validateParams: _uuid should be a valid uuid'); @@ -381,19 +356,15 @@ describe('Segment', () => { it('should report errors for invalid walkingCalculatedRoutings', () => { const invalidRouting = 123; - const segment = Segment.create({ - _walkingCalculatedRoutings: [invalidRouting] - }, registry); + const segment = Segment.create({ _walkingCalculatedRoutings: [invalidRouting] }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: WalkingRouting validateParams: params should be a plain object (Record)'); }); it('should report errors for invalid cyclingCalculatedRoutings', () => { - const invalidRouting = { '_uuid': 'bar' }; - const segment = Segment.create({ - _cyclingCalculatedRoutings: [invalidRouting] - }, registry); + const invalidRouting = { _uuid: 'bar' }; + const segment = Segment.create({ _cyclingCalculatedRoutings: [invalidRouting] }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: Uuidable validateParams: _uuid should be a valid uuid'); @@ -401,19 +372,15 @@ describe('Segment', () => { it('should report errors for invalid cyclingCalculatedRoutings', () => { const invalidRouting = 123; - const segment = Segment.create({ - _cyclingCalculatedRoutings: [invalidRouting] - }, registry); + const segment = Segment.create({ _cyclingCalculatedRoutings: [invalidRouting] }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: CyclingRouting validateParams: params should be a plain object (Record)'); }); it('should report errors for invalid drivingCalculatedRoutings', () => { - const invalidRouting = { '_uuid': 'bar' }; - const segment = Segment.create({ - _drivingCalculatedRoutings: [invalidRouting] - }, registry); + const invalidRouting = { _uuid: 'bar' }; + const segment = Segment.create({ _drivingCalculatedRoutings: [invalidRouting] }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: Uuidable validateParams: _uuid should be a valid uuid'); @@ -421,14 +388,11 @@ describe('Segment', () => { it('should report errors for invalid drivingCalculatedRoutings', () => { const invalidRouting = 123; - const segment = Segment.create({ - _drivingCalculatedRoutings: [invalidRouting] - }, registry); + const segment = Segment.create({ _drivingCalculatedRoutings: [invalidRouting] }, registry); expect(hasErrors(segment)).toBe(true); expect(unwrap(segment)).toHaveLength(1); expect(unwrap(segment)[0].toString()).toEqual('Error: DrivingRouting validateParams: params should be a plain object (Record)'); }); - }); describe('Mode to Mode Category Mapping', () => { @@ -467,7 +431,7 @@ describe('Segment', () => { ['taxi', false], ['schoolBus', false], ['other', false], - ['dontKnow', false], + ['dontKnow', false] ])('isTransit("%s") should return %s', (mode, expected) => { const segment = new Segment({ mode: mode as Mode }, registry); expect(segment.isTransit()).toBe(expected); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/StartEndable.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/StartEndable.test.ts index 0e35a22d3..e5a88ecac 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/StartEndable.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/StartEndable.test.ts @@ -8,16 +8,17 @@ import { StartEndable } from '../StartEndable'; describe('StartEndable Class', () => { - it('should return errors for invalid params and accept empty or valid uuid', () => { - expect(StartEndable.validateParams({ - startDate: 'invalid-date', - endDate: 'invalid-date', - startTime: -1, - endTime: -1, - startTimePeriod: 123, - endTimePeriod: 123 - })).toEqual([ + expect( + StartEndable.validateParams({ + startDate: 'invalid-date', + endDate: 'invalid-date', + startTime: -1, + endTime: -1, + startTimePeriod: 123, + endTimePeriod: 123 + }) + ).toEqual([ new Error('StartEndable validateParams: startDate should be a valid date string'), new Error('StartEndable validateParams: startTime should be a positive integer'), new Error('StartEndable validateParams: startTimePeriod should be a string'), @@ -25,14 +26,12 @@ describe('StartEndable Class', () => { new Error('StartEndable validateParams: endTime should be a positive integer'), new Error('StartEndable validateParams: endTimePeriod should be a string') ]); - expect(StartEndable.validateParams({ - startDate: 'invalid-date', - endDate: 'invalid-date', - startTime: -1, - endTime: -1, - startTimePeriod: 123, - endTimePeriod: 123 - }, 'testName')).toEqual([ + expect( + StartEndable.validateParams( + { startDate: 'invalid-date', endDate: 'invalid-date', startTime: -1, endTime: -1, startTimePeriod: 123, endTimePeriod: 123 }, + 'testName' + ) + ).toEqual([ new Error('testName StartEndable validateParams: startDate should be a valid date string'), new Error('testName StartEndable validateParams: startTime should be a positive integer'), new Error('testName StartEndable validateParams: startTimePeriod should be a string'), @@ -41,225 +40,117 @@ describe('StartEndable Class', () => { new Error('testName StartEndable validateParams: endTimePeriod should be a string') ]); expect(StartEndable.validateParams({})).toEqual([]); - expect(StartEndable.validateParams({ - startDate: '2023-01-01', - endDate: '2023-01-01', - startTime: 10000, - endTime: 10000, - startTimePeriod: 'am', - endTimePeriod: 'pm' - })).toEqual([]); + expect( + StartEndable.validateParams({ + startDate: '2023-01-01', + endDate: '2023-01-01', + startTime: 10000, + endTime: 10000, + startTimePeriod: 'am', + endTimePeriod: 'pm' + }) + ).toEqual([]); }); describe('timesAreValid', () => { it('should return true for valid times with no dates (10000,10000)', () => { - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: 10000 - })).toBe(true); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: 10000 })).toBe(true); }); it('should return true for valid times with only start date (10000,10000)', () => { - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: 10000, - startDate: '2023-01-01' - })).toBe(true); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: 10000, startDate: '2023-01-01' })).toBe(true); }); it('should return true for valid times with only end date (10000,10000)', () => { - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: 10000, - endDate: '2023-01-01' - })).toBe(true); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: 10000, endDate: '2023-01-01' })).toBe(true); }); it('should return true for valid times with no dates (10000,11000)', () => { - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: 11000 - })).toBe(true); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: 11000 })).toBe(true); }); it('should return true for valid times with no dates (0,10000)', () => { - expect(StartEndable.timesAreValid({ - startTime: 0, - endTime: 10000 - })).toBe(true); + expect(StartEndable.timesAreValid({ startTime: 0, endTime: 10000 })).toBe(true); }); it('should return true for valid times with no dates (0,0)', () => { - expect(StartEndable.timesAreValid({ - startTime: 0, - endTime: 0 - })).toBe(true); + expect(StartEndable.timesAreValid({ startTime: 0, endTime: 0 })).toBe(true); }); it('should return false for invalid times with no dates (-1,10000)', () => { - expect(StartEndable.timesAreValid({ - startTime: -1, - endTime: 10000 - })).toBe(false); + expect(StartEndable.timesAreValid({ startTime: -1, endTime: 10000 })).toBe(false); }); it('should return false for invalid times with no dates (10000,-1)', () => { - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: -1 - })).toBe(false); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: -1 })).toBe(false); }); it('should return false for invalid times with no dates (-1,-1)', () => { - expect(StartEndable.timesAreValid({ - startTime: -1, - endTime: -1 - })).toBe(false); + expect(StartEndable.timesAreValid({ startTime: -1, endTime: -1 })).toBe(false); }); it('should return false for invalid times with dates (end date before start date)', () => { - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: 10000, - startDate: '2023-01-01', - endDate: '2022-12-31' - })).toBe(false); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: 10000, startDate: '2023-01-01', endDate: '2022-12-31' })).toBe(false); }); it('should return true for valid times with dates (end date after start date, different times)', () => { - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: 5000, - startDate: '2023-01-01', - endDate: '2023-01-02' - })).toBe(true); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: 5000, startDate: '2023-01-01', endDate: '2023-01-02' })).toBe(true); }); it('should return true for valid times with dates (end date after start date, different times 2)', () => { - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: 10000, - startDate: '2023-01-01', - endDate: '2023-01-02' - })).toBe(true); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: 10000, startDate: '2023-01-01', endDate: '2023-01-02' })).toBe(true); }); it('should return true for valid times with dates (end date after start date, same time)', () => { - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: 10000, - startDate: '2023-01-01', - endDate: '2023-01-02' - })).toBe(true); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: 10000, startDate: '2023-01-01', endDate: '2023-01-02' })).toBe(true); }); it('should return true for valid times with dates (same date)', () => { - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: 15000, - startDate: '2023-01-01', - endDate: '2023-01-01' - })).toBe(true); - }); - it ('should return undefined for undefined times or dates', () => { - expect(StartEndable.timesAreValid({ - startTime: undefined, - endTime: 10000, - startDate: '2023-01-01', - endDate: '2022-12-31' - })).toBe(undefined); - expect(StartEndable.timesAreValid({ - startTime: 10000, - endTime: undefined, - startDate: '2023-01-01', - endDate: '2022-12-31' - })).toBe(undefined); - expect(StartEndable.timesAreValid({ - startTime: undefined, - endTime: undefined, - startDate: '2023-01-01', - endDate: '2022-12-31' - })).toBe(undefined); - expect(StartEndable.timesAreValid({ - startTime: undefined, - endTime: undefined, - startDate: undefined, - endDate: '2022-12-31' - })).toBe(undefined); - expect(StartEndable.timesAreValid({ - startTime: undefined, - endTime: undefined, - startDate: undefined, - endDate: undefined - })).toBe(undefined); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: 15000, startDate: '2023-01-01', endDate: '2023-01-01' })).toBe(true); + }); + it('should return undefined for undefined times or dates', () => { + expect(StartEndable.timesAreValid({ startTime: undefined, endTime: 10000, startDate: '2023-01-01', endDate: '2022-12-31' })).toBe( + undefined + ); + expect(StartEndable.timesAreValid({ startTime: 10000, endTime: undefined, startDate: '2023-01-01', endDate: '2022-12-31' })).toBe( + undefined + ); + expect(StartEndable.timesAreValid({ startTime: undefined, endTime: undefined, startDate: '2023-01-01', endDate: '2022-12-31' })).toBe( + undefined + ); + expect(StartEndable.timesAreValid({ startTime: undefined, endTime: undefined, startDate: undefined, endDate: '2022-12-31' })).toBe( + undefined + ); + expect(StartEndable.timesAreValid({ startTime: undefined, endTime: undefined, startDate: undefined, endDate: undefined })).toBe( + undefined + ); }); }); describe('getDurationSeconds', () => { it('should return the duration in seconds (same date, same time)', () => { - expect(StartEndable.getDurationSeconds({ - startTime: 10000, - endTime: 10000, - startDate: '2023-01-01', - endDate: '2023-01-01' - })).toBe(0); + expect(StartEndable.getDurationSeconds({ startTime: 10000, endTime: 10000, startDate: '2023-01-01', endDate: '2023-01-01' })).toBe(0); }); it('should return the duration in seconds (same date)', () => { - expect(StartEndable.getDurationSeconds({ - startTime: 10000, - endTime: 13600, - startDate: '2023-01-01', - endDate: '2023-01-01' - })).toBe(3600); + expect(StartEndable.getDurationSeconds({ startTime: 10000, endTime: 13600, startDate: '2023-01-01', endDate: '2023-01-01' })).toBe(3600); }); it('should return the duration in seconds (different date)', () => { - expect(StartEndable.getDurationSeconds({ - startTime: 10000, - endTime: 10000, - startDate: '2023-01-01', - endDate: '2023-01-02' - })).toBe(86400); + expect(StartEndable.getDurationSeconds({ startTime: 10000, endTime: 10000, startDate: '2023-01-01', endDate: '2023-01-02' })).toBe(86400); }); it('should return the duration in seconds (no date)', () => { - expect(StartEndable.getDurationSeconds({ - startTime: 10000, - endTime: 15000 - })).toBe(5000); + expect(StartEndable.getDurationSeconds({ startTime: 10000, endTime: 15000 })).toBe(5000); }); it('should return the duration in seconds (one date)', () => { - expect(StartEndable.getDurationSeconds({ - startTime: 10000, - endTime: 15000, - startDate: '2023-01-01' - })).toBe(5000); - expect(StartEndable.getDurationSeconds({ - startTime: 10000, - endTime: 15000, - endDate: '2023-01-01' - })).toBe(5000); + expect(StartEndable.getDurationSeconds({ startTime: 10000, endTime: 15000, startDate: '2023-01-01' })).toBe(5000); + expect(StartEndable.getDurationSeconds({ startTime: 10000, endTime: 15000, endDate: '2023-01-01' })).toBe(5000); }); it('should return undefined for undefined start time', () => { - expect(StartEndable.getDurationSeconds({ - startTime: undefined, - endTime: 10000, - startDate: '2023-01-01', - endDate: '2022-12-31' - })).toBe(undefined); + expect(StartEndable.getDurationSeconds({ startTime: undefined, endTime: 10000, startDate: '2023-01-01', endDate: '2022-12-31' })).toBe( + undefined + ); }); it('should return undefined for undefined end time', () => { - expect(StartEndable.getDurationSeconds({ - startTime: 1000, - endTime: undefined, - startDate: '2023-01-01', - endDate: '2022-12-31' - })).toBe(undefined); + expect(StartEndable.getDurationSeconds({ startTime: 1000, endTime: undefined, startDate: '2023-01-01', endDate: '2022-12-31' })).toBe( + undefined + ); }); it('should return undefined for invalid time', () => { - expect(StartEndable.getDurationSeconds({ - startTime: -1, - endTime: 10000, - startDate: '2023-01-01', - endDate: '2023-01-02' - })).toBe(undefined); - expect(StartEndable.getDurationSeconds({ - startTime: '7200' as any, - endTime: 10000, - startDate: '2023-01-01', - endDate: '2023-01-02' - })).toBe(undefined); - expect(StartEndable.getDurationSeconds({ - startTime: 10000 as any, - endTime: '7200' as any, - startDate: '2023-01-01', - endDate: '2023-01-02' - })).toBe(undefined); + expect(StartEndable.getDurationSeconds({ startTime: -1, endTime: 10000, startDate: '2023-01-01', endDate: '2023-01-02' })).toBe( + undefined + ); + expect( + StartEndable.getDurationSeconds({ startTime: '7200' as any, endTime: 10000, startDate: '2023-01-01', endDate: '2023-01-02' }) + ).toBe(undefined); + expect( + StartEndable.getDurationSeconds({ startTime: 10000 as any, endTime: '7200' as any, startDate: '2023-01-01', endDate: '2023-01-02' }) + ).toBe(undefined); }); }); }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Survey.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Survey.test.ts index 3b8025852..cb125e71e 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Survey.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Survey.test.ts @@ -9,7 +9,6 @@ import { Survey } from '../Survey'; import { v4 as uuidV4 } from 'uuid'; describe('Survey Class', () => { - const surveyName = 'Sample Survey'; const surveyShortname = 'SS'; const surveyStartDate = '2023-04-15'; // April 15, 2023 @@ -17,17 +16,31 @@ describe('Survey Class', () => { it('should instantiate with the provided UUID', () => { const validUuid = uuidV4(); - const surveyInstance = new Survey({ _uuid: validUuid, name: surveyName, shortname: surveyShortname, startDate: surveyStartDate, endDate: surveyEndDate }); + const surveyInstance = new Survey({ + _uuid: validUuid, + name: surveyName, + shortname: surveyShortname, + startDate: surveyStartDate, + endDate: surveyEndDate + }); expect(surveyInstance['_uuid']).toEqual(validUuid); // accessing the protected property for testing purposes }); it('should throw an error when instantiated with an invalid UUID', () => { const invalidUuid = 'invalid-uuid'; - expect(() => new Survey({ _uuid: invalidUuid, name: surveyName, shortname: surveyShortname, startDate: surveyStartDate, endDate: surveyEndDate })).toThrow('Uuidable: invalid uuid'); + expect( + () => new Survey({ _uuid: invalidUuid, name: surveyName, shortname: surveyShortname, startDate: surveyStartDate, endDate: surveyEndDate }) + ).toThrow('Uuidable: invalid uuid'); }); it('should correctly set name, shortname, startDate, and endDate properties', () => { - const surveyInstance = new Survey({ _uuid: uuidV4(), name: surveyName, shortname: surveyShortname, startDate: surveyStartDate, endDate: surveyEndDate }); + const surveyInstance = new Survey({ + _uuid: uuidV4(), + name: surveyName, + shortname: surveyShortname, + startDate: surveyStartDate, + endDate: surveyEndDate + }); expect(surveyInstance.name).toEqual(surveyName); expect(surveyInstance.shortname).toEqual(surveyShortname); expect(surveyInstance.startDate).toEqual(surveyStartDate); @@ -35,10 +48,26 @@ describe('Survey Class', () => { }); it('should validate params', () => { - // Valid attributes with an additional attribute - expect(Survey.validateParams({ _uuid: uuidV4(), name: surveyName, shortname: surveyShortname, startDate: surveyStartDate, endDate: surveyEndDate })).toEqual([]); - expect(Survey.validateParams({ _uuid: uuidV4(), name: surveyName, shortname: surveyShortname, startDate: surveyStartDate, endDate: surveyEndDate, additionalAttribute: 'additionalValue' })).toEqual([]); + expect( + Survey.validateParams({ + _uuid: uuidV4(), + name: surveyName, + shortname: surveyShortname, + startDate: surveyStartDate, + endDate: surveyEndDate + }) + ).toEqual([]); + expect( + Survey.validateParams({ + _uuid: uuidV4(), + name: surveyName, + shortname: surveyShortname, + startDate: surveyStartDate, + endDate: surveyEndDate, + additionalAttribute: 'additionalValue' + }) + ).toEqual([]); // Invalid name (should be a string) expect(Survey.validateParams({ name: 23, shortname: 'foo' })[0].message).toEqual('Survey validateParams: name should be a string'); @@ -47,10 +76,18 @@ describe('Survey Class', () => { expect(Survey.validateParams({ name: 'bar', shortname: {} })[0].message).toEqual('Survey validateParams: shortname should be a string'); // Invalid description (should be a string) - expect(Survey.validateParams({ name: 'bar', shortname: 'foo', description: new Date() })[0].message).toEqual('Survey validateParams: description should be a string'); + expect(Survey.validateParams({ name: 'bar', shortname: 'foo', description: new Date() })[0].message).toEqual( + 'Survey validateParams: description should be a string' + ); // Invalid UUID - const invalidUuid = Survey.validateParams({ name: 'bar', shortname: 'foo', _uuid: 'foo', startDate: surveyStartDate, endDate: surveyEndDate }); + const invalidUuid = Survey.validateParams({ + name: 'bar', + shortname: 'foo', + _uuid: 'foo', + startDate: surveyStartDate, + endDate: surveyEndDate + }); expect(invalidUuid.length).toEqual(1); expect(invalidUuid[0].message).toEqual('Uuidable validateParams: _uuid should be a valid uuid'); @@ -66,9 +103,8 @@ describe('Survey Class', () => { expect(invalidEndDate2[0].message).toEqual('Survey validateParams: endDate should be a valid date string'); // Missing required attributes: - const missingRequired = Survey.validateParams({ }); + const missingRequired = Survey.validateParams({}); expect(missingRequired.length).toEqual(1); expect(missingRequired[0].message).toEqual('Survey validateParams: shortname is required'); - }); }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/SurveyObjectRegistry.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/SurveyObjectRegistry.test.ts index 12021e63a..669e24770 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/SurveyObjectRegistry.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/SurveyObjectRegistry.test.ts @@ -238,30 +238,12 @@ describe('SurveyObjectsRegistry', () => { it('should support complete survey object hierarchy', () => { // Create mock objects with parent-child relationships const mockInterview = { uuid: validUuid } as Interview; - const mockHousehold = { - uuid: validUuid3, - interviewUuid: validUuid - } as Household; - const mockPerson = { - uuid: validUuid4, - householdUuid: validUuid3 - } as Person; - const mockJourney = { - uuid: validUuid5, - personUuid: validUuid4 - } as Journey; - const mockVisitedPlace = { - uuid: validUuid6, - journeyUuid: validUuid5 - } as VisitedPlace; - const mockTrip = { - uuid: validUuid7, - journeyUuid: validUuid5 - } as Trip; - const mockSegment = { - uuid: validUuid10, - tripUuid: validUuid7 - } as Segment; + const mockHousehold = { uuid: validUuid3, interviewUuid: validUuid } as Household; + const mockPerson = { uuid: validUuid4, householdUuid: validUuid3 } as Person; + const mockJourney = { uuid: validUuid5, personUuid: validUuid4 } as Journey; + const mockVisitedPlace = { uuid: validUuid6, journeyUuid: validUuid5 } as VisitedPlace; + const mockTrip = { uuid: validUuid7, journeyUuid: validUuid5 } as Trip; + const mockSegment = { uuid: validUuid10, tripUuid: validUuid7 } as Segment; // Register all objects registry.registerInterview(mockInterview); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Trip.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Trip.test.ts index f99e97b5c..533f25128 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Trip.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Trip.test.ts @@ -26,7 +26,7 @@ describe('Trip', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validAttributes: TripAttributes = { @@ -38,7 +38,7 @@ describe('Trip', () => { startTimePeriod: 'am', endTimePeriod: 'pm', _weights: [{ weight: 1.5, method: new WeightMethod(weightMethodAttributes) }], - _isValid: true, + _isValid: true }; const extendedAttributes: ExtendedTripAttributes = { @@ -53,16 +53,9 @@ describe('Trip', () => { _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - _isValid: true, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, + _isValid: true + } }, _endPlace: { _uuid: uuidV4(), @@ -73,28 +66,13 @@ describe('Trip', () => { _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 1], - }, - properties: {}, - }, - _isValid: true, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 1] }, properties: {} }, + _isValid: true + } }, _segments: [ - { - _uuid: uuidV4(), - mode: 'walk', - _isValid: true, - }, - { - _uuid: uuidV4(), - mode: 'bicycle', - _isValid: true, - }, + { _uuid: uuidV4(), mode: 'walk', _isValid: true }, + { _uuid: uuidV4(), mode: 'bicycle', _isValid: true } ], _junctions: [ { @@ -106,16 +84,9 @@ describe('Trip', () => { _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - _isValid: true, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, + _isValid: true + } }, { _uuid: uuidV4(), @@ -126,18 +97,11 @@ describe('Trip', () => { _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 1], - }, - properties: {}, - }, - _isValid: true, - }, - }, - ], + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 1] }, properties: {} }, + _isValid: true + } + } + ] }; test('should create a Trip instance with valid attributes', () => { @@ -148,9 +112,14 @@ describe('Trip', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = Trip.validateParams.toString(); - tripAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute)).forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + tripAttributes + .filter( + (attribute) => + attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute) + ) + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -208,14 +177,8 @@ describe('Trip', () => { }); test('should create a Trip instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const tripAttributes = { - ...validAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const tripAttributes = { ...validAttributes, ...customAttributes }; const trip = new Trip(tripAttributes, registry); expect(trip).toBeInstanceOf(Trip); expect(trip.attributes).toEqual(validAttributes); @@ -259,7 +222,7 @@ describe('Trip', () => { ['tripChainUuid', uuidV4()], ['_isValid', false], ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]], - ['preData', { importedTripData: 'value', distance: 12.5 }], + ['preData', { importedTripData: 'value', distance: 12.5 }] ])('should set and get %s', (attribute, value) => { const trip = new Trip(validAttributes, registry); trip[attribute] = value; @@ -270,7 +233,7 @@ describe('Trip', () => { test.each([ ['_uuid', extendedAttributes._uuid], ['customAttributes', { customAttribute: extendedAttributes.customAttribute }], - ['attributes', validAttributes], + ['attributes', validAttributes] ])('should set and get %s', (attribute, value) => { const trip = new Trip(extendedAttributes, registry); expect(trip[attribute]).toEqual(value); @@ -283,7 +246,7 @@ describe('Trip', () => { ['_origin', () => new VisitedPlace(extendedAttributes._startPlace as ExtendedVisitedPlaceAttributes, registry)], ['_destination', () => new VisitedPlace(extendedAttributes._endPlace as ExtendedVisitedPlaceAttributes, registry)], ['_segments', () => extendedAttributes._segments?.map((segment) => new Segment(segment as ExtendedSegmentAttributes, registry))], - ['_junctions', () => extendedAttributes._junctions?.map((junction) => new Junction(junction as ExtendedJunctionAttributes, registry))], + ['_junctions', () => extendedAttributes._junctions?.map((junction) => new Junction(junction as ExtendedJunctionAttributes, registry))] ])('should set and get %s', (attribute, valueFactory) => { const trip = new Trip(validAttributes, registry); const value = valueFactory(); @@ -583,16 +546,9 @@ describe('Trip', () => { _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - _isValid: true, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, + _isValid: true + } }; const endPlaceAttributes: ExtendedVisitedPlaceAttributes = { @@ -604,23 +560,12 @@ describe('Trip', () => { _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 1], - }, - properties: {}, - }, - _isValid: true, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 1] }, properties: {} }, + _isValid: true + } }; - const tripAttributes: ExtendedTripAttributes = { - ...validAttributes, - _startPlace: startPlaceAttributes, - _endPlace: endPlaceAttributes, - }; + const tripAttributes: ExtendedTripAttributes = { ...validAttributes, _startPlace: startPlaceAttributes, _endPlace: endPlaceAttributes }; const result = Trip.create(tripAttributes, registry); expect(isOk(result)).toBe(true); @@ -632,7 +577,7 @@ describe('Trip', () => { activity: startPlaceAttributes.activity, activityCategory: startPlaceAttributes.activityCategory, _weights: startPlaceAttributes._weights, - _isValid: startPlaceAttributes._isValid, + _isValid: startPlaceAttributes._isValid }); expect(trip.origin).toBeDefined(); expect(trip.origin?.attributes).toEqual({ @@ -641,7 +586,7 @@ describe('Trip', () => { activity: startPlaceAttributes.activity, activityCategory: startPlaceAttributes.activityCategory, _weights: startPlaceAttributes._weights, - _isValid: startPlaceAttributes._isValid, + _isValid: startPlaceAttributes._isValid }); expect(trip.endPlace).toBeDefined(); expect(trip.endPlace?.attributes).toEqual({ @@ -650,7 +595,7 @@ describe('Trip', () => { activity: endPlaceAttributes.activity, activityCategory: endPlaceAttributes.activityCategory, _weights: endPlaceAttributes._weights, - _isValid: endPlaceAttributes._isValid, + _isValid: endPlaceAttributes._isValid }); expect(trip.destination).toBeDefined(); expect(trip.destination?.attributes).toEqual({ @@ -659,28 +604,17 @@ describe('Trip', () => { activity: endPlaceAttributes.activity, activityCategory: endPlaceAttributes.activityCategory, _weights: endPlaceAttributes._weights, - _isValid: endPlaceAttributes._isValid, + _isValid: endPlaceAttributes._isValid }); }); test('should create Segment instances for segments when creating a Trip instance', () => { const segmentAttributes: ExtendedSegmentAttributes[] = [ - { - _uuid: uuidV4(), - mode: 'walk', - _isValid: true, - }, - { - _uuid: uuidV4(), - mode: 'bicycle', - _isValid: true, - }, + { _uuid: uuidV4(), mode: 'walk', _isValid: true }, + { _uuid: uuidV4(), mode: 'bicycle', _isValid: true } ]; - const tripAttributes: ExtendedTripAttributes = { - ...validAttributes, - _segments: segmentAttributes, - }; + const tripAttributes: ExtendedTripAttributes = { ...validAttributes, _segments: segmentAttributes }; const result = Trip.create(tripAttributes, registry); expect(isOk(result)).toBe(true); @@ -702,16 +636,9 @@ describe('Trip', () => { _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - _isValid: true, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, + _isValid: true + } }, { _uuid: uuidV4(), @@ -722,23 +649,13 @@ describe('Trip', () => { _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 1], - }, - properties: {}, - }, - _isValid: true, - }, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 1] }, properties: {} }, + _isValid: true + } + } ]; - const tripAttributes: ExtendedTripAttributes = { - ...validAttributes, - _junctions: junctionAttributes, - }; + const tripAttributes: ExtendedTripAttributes = { ...validAttributes, _junctions: junctionAttributes }; const result = Trip.create(tripAttributes, registry); expect(isOk(result)).toBe(true); @@ -751,7 +668,7 @@ describe('Trip', () => { endTime: junctionAttributes[0].endTime, parkingType: junctionAttributes[0].parkingType, _weights: junctionAttributes[0]._weights, - _isValid: junctionAttributes[0]._isValid, + _isValid: junctionAttributes[0]._isValid }); expect(trip.junctions?.[1].attributes).toEqual({ _uuid: junctionAttributes[1]._uuid, @@ -759,12 +676,11 @@ describe('Trip', () => { endTime: junctionAttributes[1].endTime, parkingType: junctionAttributes[1].parkingType, _weights: junctionAttributes[1]._weights, - _isValid: junctionAttributes[1]._isValid, + _isValid: junctionAttributes[1]._isValid }); }); }); - describe('Trip methods', () => { let trip: Trip; @@ -823,7 +739,11 @@ describe('Trip', () => { }); test('should return the modes of the segments', () => { - trip.segments = [new Segment({ mode: 'walk' }, registry), new Segment({ mode: 'transitBus' }, registry), new Segment({ mode: 'bicycle' }, registry)]; + trip.segments = [ + new Segment({ mode: 'walk' }, registry), + new Segment({ mode: 'transitBus' }, registry), + new Segment({ mode: 'bicycle' }, registry) + ]; expect(trip.getModes()).toEqual(['walk', 'transitBus', 'bicycle']); }); }); @@ -840,7 +760,11 @@ describe('Trip', () => { }); test('should return the mode categories of the segments', () => { - trip.segments = [new Segment({ mode: 'walk' }, registry), new Segment({ mode: 'transitBus' }, registry), new Segment({ mode: 'bicycle' }, registry)]; + trip.segments = [ + new Segment({ mode: 'walk' }, registry), + new Segment({ mode: 'transitBus' }, registry), + new Segment({ mode: 'bicycle' }, registry) + ]; expect(trip.getModeCategories()).toEqual(['walk', 'transit', 'bicycle']); }); }); @@ -879,7 +803,11 @@ describe('Trip', () => { }); test('should return the modes without walk', () => { - trip.segments = [new Segment({ mode: 'walk' }, registry), new Segment({ mode: 'transitBus' }, registry), new Segment({ mode: 'bicycle' }, registry)]; + trip.segments = [ + new Segment({ mode: 'walk' }, registry), + new Segment({ mode: 'transitBus' }, registry), + new Segment({ mode: 'bicycle' }, registry) + ]; expect(trip.getModesWithoutWalk()).toEqual(['transitBus', 'bicycle']); }); }); @@ -896,7 +824,11 @@ describe('Trip', () => { }); test('should return the transit modes', () => { - trip.segments = [new Segment({ mode: 'walk' }, registry), new Segment({ mode: 'transitBus' }, registry), new Segment({ mode: 'transitRRT' }, registry)]; + trip.segments = [ + new Segment({ mode: 'walk' }, registry), + new Segment({ mode: 'transitBus' }, registry), + new Segment({ mode: 'transitRRT' }, registry) + ]; expect(trip.getTransitModes()).toEqual(['transitBus', 'transitRRT']); }); }); @@ -913,7 +845,11 @@ describe('Trip', () => { }); test('should return the non-transit modes', () => { - trip.segments = [new Segment({ mode: 'walk' }, registry), new Segment({ mode: 'transitBus' }, registry), new Segment({ mode: 'bicycle' }, registry)]; + trip.segments = [ + new Segment({ mode: 'walk' }, registry), + new Segment({ mode: 'transitBus' }, registry), + new Segment({ mode: 'bicycle' }, registry) + ]; expect(trip.getNonTransitModes()).toEqual(['walk', 'bicycle']); }); }); @@ -940,12 +876,20 @@ describe('Trip', () => { }); test('should return true if segments has transit mode and multiple non-transit modes, not including walking', () => { - trip.segments = [new Segment({ mode: 'carDriver' }, registry), new Segment({ mode: 'transitBus' }, registry), new Segment({ mode: 'bicycle' }, registry)]; + trip.segments = [ + new Segment({ mode: 'carDriver' }, registry), + new Segment({ mode: 'transitBus' }, registry), + new Segment({ mode: 'bicycle' }, registry) + ]; expect(trip.isTransitMultimodal()).toBe(true); }); test('should return true if segments has transit mode and multiple non-transit modes, including walking', () => { - trip.segments = [new Segment({ mode: 'walk' }, registry), new Segment({ mode: 'transitBus' }, registry), new Segment({ mode: 'bicycle' }, registry)]; + trip.segments = [ + new Segment({ mode: 'walk' }, registry), + new Segment({ mode: 'transitBus' }, registry), + new Segment({ mode: 'bicycle' }, registry) + ]; expect(trip.isTransitMultimodal()).toBe(true); }); }); @@ -967,12 +911,20 @@ describe('Trip', () => { }); test('should return false if segments has non-transit modes other than walk', () => { - trip.segments = [new Segment({ mode: 'walk' }, registry), new Segment({ mode: 'transitBus' }, registry), new Segment({ mode: 'bicycle' }, registry)]; + trip.segments = [ + new Segment({ mode: 'walk' }, registry), + new Segment({ mode: 'transitBus' }, registry), + new Segment({ mode: 'bicycle' }, registry) + ]; expect(trip.isTransitOnly()).toBe(false); }); test('should return true if segments has only transit modes and walk (which is ignored)', () => { - trip.segments = [new Segment({ mode: 'walk' }, registry), new Segment({ mode: 'transitBus' }, registry), new Segment({ mode: 'transitRRT' }, registry)]; + trip.segments = [ + new Segment({ mode: 'walk' }, registry), + new Segment({ mode: 'transitBus' }, registry), + new Segment({ mode: 'transitRRT' }, registry) + ]; expect(trip.isTransitOnly()).toBe(true); }); }); @@ -1019,13 +971,28 @@ describe('Trip', () => { trip.endTime = 7200; expect(trip.getDurationSeconds()).toBe(undefined); }); - }); describe('getBirdDistanceMeters', () => { test('should return the bird distance in meters', () => { - trip.origin = new VisitedPlace({ _place: { geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [45.5, -75.5] }, properties: {} }, _isValid: true } }, registry); - trip.destination = new VisitedPlace({ _place: { geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [45.6, -75.4] }, properties: {} }, _isValid: true } }, registry); + trip.origin = new VisitedPlace( + { + _place: { + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [45.5, -75.5] }, properties: {} }, + _isValid: true + } + }, + registry + ); + trip.destination = new VisitedPlace( + { + _place: { + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [45.6, -75.4] }, properties: {} }, + _isValid: true + } + }, + registry + ); expect(trip.getBirdDistanceMeters()).toBe(11466); }); @@ -1035,16 +1002,55 @@ describe('Trip', () => { }); test('should return undefined if origin or destination is not a valid point', () => { - trip.origin = new VisitedPlace({ geography: { type: 'Feature', geometry: { type: 'Polygon', coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]] }, properties: {} } as any }, registry); - trip.destination = new VisitedPlace({ geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [2, 2] }, properties: {} } as any }, registry); + trip.origin = new VisitedPlace( + { + geography: { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0] + ] + ] + }, + properties: {} + } as any + }, + registry + ); + trip.destination = new VisitedPlace( + { geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [2, 2] }, properties: {} } as any }, + registry + ); expect(trip.getBirdDistanceMeters()).toBe(undefined); }); }); describe('getBirdSpeedKph', () => { test('should return the bird speed in km/h', () => { - trip.origin = new VisitedPlace({ _place: { geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [45.5, -75.5] }, properties: {} }, _isValid: true } }, registry); - trip.destination = new VisitedPlace({ _place: { geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [45.6, -75.4] }, properties: {} }, _isValid: true } }, registry); + trip.origin = new VisitedPlace( + { + _place: { + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [45.5, -75.5] }, properties: {} }, + _isValid: true + } + }, + registry + ); + trip.destination = new VisitedPlace( + { + _place: { + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [45.6, -75.4] }, properties: {} }, + _isValid: true + } + }, + registry + ); trip.startTime = 7200; // 2:00 AM in seconds trip.endTime = 10020; // 2:47 AM in seconds (47 minutes later = 2820 seconds) const speed = trip.getBirdSpeedKph() as number; @@ -1057,11 +1063,33 @@ describe('Trip', () => { }); test('should return undefined if origin or destination is not a valid point', () => { - trip.origin = new VisitedPlace({ geography: { type: 'Feature', geometry: { type: 'Polygon', coordinates: [[[0, 0], [0, 1], [1, 1], [1, 0], [0, 0]]] }, properties: {} } as any }, registry); - trip.destination = new VisitedPlace({ geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [2, 2] }, properties: {} } as any }, registry); + trip.origin = new VisitedPlace( + { + geography: { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [0, 0], + [0, 1], + [1, 1], + [1, 0], + [0, 0] + ] + ] + }, + properties: {} + } as any + }, + registry + ); + trip.destination = new VisitedPlace( + { geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [2, 2] }, properties: {} } as any }, + registry + ); expect(trip.getBirdSpeedKph()).toBe(undefined); }); }); - }); }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/TripChain.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/TripChain.test.ts index 453e20702..3113c90b0 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/TripChain.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/TripChain.test.ts @@ -24,7 +24,7 @@ describe('TripChain', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validAttributes: TripChainAttributes = { @@ -41,7 +41,7 @@ describe('TripChain', () => { mainActivity: 'work', mainActivityCategory: 'work', _weights: [{ weight: 1.5, method: new WeightMethod(weightMethodAttributes) }], - _isValid: true, + _isValid: true }; const extendedAttributes: ExtendedTripChainAttributes = { @@ -56,7 +56,7 @@ describe('TripChain', () => { endTime: 5400, startTimePeriod: 'am', endTimePeriod: 'pm', - _isValid: true, + _isValid: true }, { _uuid: uuidV4(), @@ -66,35 +66,13 @@ describe('TripChain', () => { endTime: 7200, startTimePeriod: 'am', endTimePeriod: 'pm', - _isValid: true, - }, + _isValid: true + } ], _visitedPlaces: [ - { - _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - _isValid: true, - }, - { - _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 1], - }, - properties: {}, - }, - _isValid: true, - }, - ], + { _uuid: uuidV4(), geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, _isValid: true }, + { _uuid: uuidV4(), geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 1] }, properties: {} }, _isValid: true } + ] }; test('should create a TripChain instance with valid attributes', () => { @@ -105,9 +83,14 @@ describe('TripChain', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = TripChain.validateParams.toString(); - tripChainAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute)).forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + tripChainAttributes + .filter( + (attribute) => + attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute) + ) + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -165,14 +148,8 @@ describe('TripChain', () => { }); test('should create a TripChain instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const tripChainAttributes = { - ...validAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const tripChainAttributes = { ...validAttributes, ...customAttributes }; const tripChain = new TripChain(tripChainAttributes, registry); expect(tripChain).toBeInstanceOf(TripChain); expect(tripChain.attributes).toEqual(validAttributes); @@ -222,7 +199,7 @@ describe('TripChain', () => { ['isConstrained', false], ['mainActivity', 'school'], ['mainActivityCategory', 'education'], - ['preData', { importedTripChainData: 'value', purpose: 'work' }], + ['preData', { importedTripChainData: 'value', purpose: 'work' }] ])('should set and get %s', (attribute, value) => { const tripChain = new TripChain(validAttributes, registry); tripChain[attribute] = value; @@ -233,7 +210,7 @@ describe('TripChain', () => { test.each([ ['_uuid', extendedAttributes._uuid], ['customAttributes', { customAttribute: extendedAttributes.customAttribute }], - ['attributes', validAttributes], + ['attributes', validAttributes] ])('should set and get %s', (attribute, value) => { const tripChain = new TripChain(extendedAttributes, registry); expect(tripChain[attribute]).toEqual(value); @@ -245,7 +222,7 @@ describe('TripChain', () => { ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]], ['_trips', extendedAttributes._trips], ['_visitedPlaces', extendedAttributes._visitedPlaces], - ['journeyUuid', uuidV4()], + ['journeyUuid', uuidV4()] ])('should set and get %s', (attribute, value) => { const tripChain = new TripChain(validAttributes, registry); tripChain[attribute] = value; @@ -274,7 +251,7 @@ describe('TripChain', () => { endTime: 5400, startTimePeriod: 'am', endTimePeriod: 'pm', - _isValid: true, + _isValid: true }, { _uuid: uuidV4(), @@ -284,14 +261,11 @@ describe('TripChain', () => { endTime: 7200, startTimePeriod: 'am', endTimePeriod: 'pm', - _isValid: true, - }, + _isValid: true + } ]; - const tripChainAttributes: ExtendedTripChainAttributes = { - ...validAttributes, - _trips: tripAttributes, - }; + const tripChainAttributes: ExtendedTripChainAttributes = { ...validAttributes, _trips: tripAttributes }; const result = TripChain.create(tripChainAttributes, registry); expect(isOk(result)).toBe(true); @@ -309,39 +283,22 @@ describe('TripChain', () => { _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, - _isValid: true, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, + _isValid: true + } }, { _uuid: uuidV4(), _isValid: true, _place: { _uuid: uuidV4(), - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [1, 1], - }, - properties: {}, - }, - _isValid: true, - }, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [1, 1] }, properties: {} }, + _isValid: true + } + } ]; - const tripChainAttributes: ExtendedTripChainAttributes = { - ...validAttributes, - _visitedPlaces: visitedPlaceAttributes, - }; + const tripChainAttributes: ExtendedTripChainAttributes = { ...validAttributes, _visitedPlaces: visitedPlaceAttributes }; const result = TripChain.create(tripChainAttributes, registry); expect(isOk(result)).toBe(true); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Uuidable.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Uuidable.test.ts index cd45bf5fe..e28a4eb50 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Uuidable.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Uuidable.test.ts @@ -9,7 +9,6 @@ import { Uuidable } from '../Uuidable'; import { validate as uuidValidate, v4 as uuidV4 } from 'uuid'; describe('Uuidable Class', () => { - it('should have a _uuid property when instantiated without parameters', () => { const uuidBaseInstance = new Uuidable(); expect(uuidBaseInstance).toHaveProperty('_uuid'); @@ -35,7 +34,9 @@ describe('Uuidable Class', () => { it('should return errors for invalid params and accept empty or valid uuid', () => { expect(Uuidable.validateParams({ _uuid: 'invalid-uuid' })).toEqual([new Error('Uuidable validateParams: _uuid should be a valid uuid')]); - expect(Uuidable.validateParams({ _uuid: 'invalid-uuid' }, 'testName')).toEqual([new Error('testName Uuidable validateParams: _uuid should be a valid uuid')]); + expect(Uuidable.validateParams({ _uuid: 'invalid-uuid' }, 'testName')).toEqual([ + new Error('testName Uuidable validateParams: _uuid should be a valid uuid') + ]); expect(Uuidable.validateParams({})).toEqual([]); expect(Uuidable.validateParams({ _uuid: uuidV4() })).toEqual([]); }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Vehicle.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Vehicle.test.ts index ca4b6d2fe..95cc7602a 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Vehicle.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Vehicle.test.ts @@ -22,7 +22,7 @@ describe('Vehicle', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validAttributes: VehicleAttributes = { @@ -41,13 +41,10 @@ describe('Vehicle', () => { licensePlateNumber: 'ABC123', internalId: 'V001', _weights: [{ weight: 1.5, method: new WeightMethod(weightMethodAttributes) }], - _isValid: true, + _isValid: true }; - const extendedAttributes: ExtendedVehicleAttributes = { - ...validAttributes, - customAttribute: 'Custom Value', - }; + const extendedAttributes: ExtendedVehicleAttributes = { ...validAttributes, customAttribute: 'Custom Value' }; test('should create a Vehicle instance with valid attributes', () => { const vehicle = new Vehicle(validAttributes, registry); @@ -57,9 +54,11 @@ describe('Vehicle', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = Vehicle.validateParams.toString(); - vehicleAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights').forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + vehicleAttributes + .filter((attribute) => attribute !== '_uuid' && attribute !== '_weights') + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -135,14 +134,8 @@ describe('Vehicle', () => { }); test('should create a Vehicle instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const vehicleAttributes = { - ...validAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const vehicleAttributes = { ...validAttributes, ...customAttributes }; const vehicle = new Vehicle(vehicleAttributes, registry); expect(vehicle).toBeInstanceOf(Vehicle); expect(vehicle.attributes).toEqual(validAttributes); @@ -164,7 +157,7 @@ describe('Vehicle', () => { ['acquiredYear', 2022], ['licensePlateNumber', 'XYZ789'], ['internalId', 'V002'], - ['preData', { importedVehicleData: 'value', vin: 'ABC123XYZ' }], + ['preData', { importedVehicleData: 'value', vin: 'ABC123XYZ' }] ])('should set and get %s', (attribute, value) => { const vehicle = new Vehicle(validAttributes, registry); vehicle[attribute] = value; @@ -175,7 +168,7 @@ describe('Vehicle', () => { test.each([ ['_uuid', extendedAttributes._uuid], ['customAttributes', { customAttribute: extendedAttributes.customAttribute }], - ['attributes', validAttributes], + ['attributes', validAttributes] ])('should set and get %s', (attribute, value) => { const vehicle = new Vehicle(extendedAttributes, registry); expect(vehicle[attribute]).toEqual(value); @@ -184,7 +177,7 @@ describe('Vehicle', () => { test.each([ ['_isValid', false], - ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]], + ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]] ])('should set and get %s', (attribute, value) => { const vehicle = new Vehicle(validAttributes, registry); vehicle[attribute] = value; diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/VisitedPlace.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/VisitedPlace.test.ts index 6471ef6f6..669af4cb9 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/VisitedPlace.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/VisitedPlace.test.ts @@ -24,7 +24,7 @@ describe('VisitedPlace', () => { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validPlaceAttributes: ExtendedPlaceAttributes = { @@ -42,14 +42,7 @@ describe('VisitedPlace', () => { lastAction: 'findPlace', deviceUsed: 'tablet', zoom: 15, - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, _weights: [{ weight: 1.2, method: new WeightMethod(weightMethodAttributes) }], _isValid: true }; @@ -70,15 +63,12 @@ describe('VisitedPlace', () => { _isValid: true }; - const validVisitedPlaceAttributesWithPlace: ExtendedVisitedPlaceAttributes = { - ...validVisitedPlaceAttributes, - _place: validPlaceAttributes - }; + const validVisitedPlaceAttributesWithPlace: ExtendedVisitedPlaceAttributes = { ...validVisitedPlaceAttributes, _place: validPlaceAttributes }; const extendedVisitedPlaceAttributes: ExtendedVisitedPlaceAttributes = { ...validVisitedPlaceAttributesWithPlace, customAttribute1: 'value1', - customAttribute2: 'value2', + customAttribute2: 'value2' }; test('should create a VisitedPlace instance with valid attributes', () => { @@ -89,9 +79,14 @@ describe('VisitedPlace', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = VisitedPlace.validateParams.toString(); - visitedPlaceAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute)).forEach((attributeName) => { - expect(validateParamsCode).toContain('\'' + attributeName + '\''); - }); + visitedPlaceAttributes + .filter( + (attribute) => + attribute !== '_uuid' && attribute !== '_weights' && !(startEndDateAndTimesAttributes as unknown as string[]).includes(attribute) + ) + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -109,7 +104,7 @@ describe('VisitedPlace', () => { const invalidAttributes = 'foo' as any; const result = VisitedPlace.create(invalidAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(1); + expect(unwrap(result) as Error[]).toHaveLength(1); }); test('should create a VisitedPlace instance with extended attributes', () => { @@ -163,14 +158,8 @@ describe('VisitedPlace', () => { }); test('should create a VisitedPlace instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const visitedPlaceAttributesWithCustom = { - ...validVisitedPlaceAttributesWithPlace, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const visitedPlaceAttributesWithCustom = { ...validVisitedPlaceAttributesWithPlace, ...customAttributes }; const visitedPlace = new VisitedPlace(visitedPlaceAttributesWithCustom, registry); expect(visitedPlace).toBeInstanceOf(VisitedPlace); expect(visitedPlace.attributes).toEqual(validVisitedPlaceAttributes); @@ -218,7 +207,7 @@ describe('VisitedPlace', () => { ['activityCategory', 'leisure'], ['shortcut', uuidV4()], ['_sequence', 2], - ['preData', { importedVisitedPlaceData: 'value', duration: 30 }], + ['preData', { importedVisitedPlaceData: 'value', duration: 30 }] ])('should set and get %s', (attribute, value) => { const visitedPlace = new VisitedPlace(validVisitedPlaceAttributesWithPlace, registry); visitedPlace[attribute] = value; @@ -228,11 +217,14 @@ describe('VisitedPlace', () => { describe('Getters for attributes with no setters', () => { test.each([ ['_uuid', validVisitedPlaceAttributes._uuid], - ['customAttributes', { - customAttribute1: extendedVisitedPlaceAttributes.customAttribute1, - customAttribute2: extendedVisitedPlaceAttributes.customAttribute2 - }], - ['attributes', validVisitedPlaceAttributes], + [ + 'customAttributes', + { + customAttribute1: extendedVisitedPlaceAttributes.customAttribute1, + customAttribute2: extendedVisitedPlaceAttributes.customAttribute2 + } + ], + ['attributes', validVisitedPlaceAttributes] ])('should set and get %s', (attribute, value) => { const visitedPlace = new VisitedPlace(extendedVisitedPlaceAttributes, registry); expect(visitedPlace[attribute]).toEqual(value); @@ -243,7 +235,7 @@ describe('VisitedPlace', () => { ['_isValid', false], ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]], ['journeyUuid', uuidV4()], - ['_place', validPlaceAttributes], + ['_place', validPlaceAttributes] ])('should set and get %s', (attribute, value) => { const visitedPlace = new VisitedPlace(validVisitedPlaceAttributesWithPlace, registry); visitedPlace[attribute] = value; @@ -261,4 +253,3 @@ describe('VisitedPlace', () => { }); }); }); - diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/Weight.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/Weight.test.ts index 6330abdbd..b19c09f22 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/Weight.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/Weight.test.ts @@ -13,7 +13,7 @@ const weightMethodAttributes: WeightMethodAttributes = { _uuid: uuidV4(), shortname: 'ShortName', name: 'Test Weight Method', - description: 'Description of the weight method', + description: 'Description of the weight method' }; describe('Weight', () => { diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/WeightMethod.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/WeightMethod.test.ts index 80581df04..6c75757da 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/WeightMethod.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/WeightMethod.test.ts @@ -14,7 +14,7 @@ describe('WeightMethod', () => { _uuid: validUUID, shortname: 'ShortName', name: 'Test Weight Method', - description: 'Description of the weight method', + description: 'Description of the weight method' }; it('should create a new WeightMethod instance', () => { @@ -27,11 +27,7 @@ describe('WeightMethod', () => { }); it('should create a new WeightMethod instance with minimal attributes', () => { - const minimalAttributes: WeightMethodAttributes = { - _uuid: validUUID, - shortname: 'ShortName', - name: 'Minimal Test Weight Method', - }; + const minimalAttributes: WeightMethodAttributes = { _uuid: validUUID, shortname: 'ShortName', name: 'Minimal Test Weight Method' }; const weightMethod = new WeightMethod(minimalAttributes); expect(weightMethod).toBeInstanceOf(WeightMethod); @@ -40,18 +36,11 @@ describe('WeightMethod', () => { expect(weightMethod.name).toEqual('Minimal Test Weight Method'); expect(weightMethod.description).toBeUndefined(); }); - - }); describe('WeightMethod validateParams', () => { it('should return an empty array for valid params', () => { - const validParams = { - _uuid: uuidV4(), - name: 'Valid Weight Method', - shortname: 'VWM', - description: 'A valid weight method.', - }; + const validParams = { _uuid: uuidV4(), name: 'Valid Weight Method', shortname: 'VWM', description: 'A valid weight method.' }; const errors = WeightMethod.validateParams(validParams); @@ -63,7 +52,7 @@ describe('WeightMethod validateParams', () => { _uuid: 12345, // Invalid UUID name: 123, // Should be a string shortname: 123, // Should be a string - description: 123, // Should be a string + description: 123 // Should be a string }; const errors = WeightMethod.validateParams(invalidParams); @@ -72,7 +61,7 @@ describe('WeightMethod validateParams', () => { new Error('Uuidable validateParams: _uuid should be a valid uuid'), new Error('WeightMethod validateParams: name should be a string'), new Error('WeightMethod validateParams: shortname should be a string'), - new Error('WeightMethod validateParams: description should be a string'), + new Error('WeightMethod validateParams: description should be a string') ]); }); @@ -85,7 +74,7 @@ describe('WeightMethod validateParams', () => { expect(errors).toEqual([ new Error('WeightMethod validateParams: name is required'), - new Error('WeightMethod validateParams: shortname is required'), + new Error('WeightMethod validateParams: shortname is required') ]); }); }); diff --git a/packages/evolution-common/src/services/baseObjects/__tests__/WorkPlace.test.ts b/packages/evolution-common/src/services/baseObjects/__tests__/WorkPlace.test.ts index c2ec2fc0d..66a0602b5 100644 --- a/packages/evolution-common/src/services/baseObjects/__tests__/WorkPlace.test.ts +++ b/packages/evolution-common/src/services/baseObjects/__tests__/WorkPlace.test.ts @@ -20,11 +20,11 @@ describe('WorkPlace', () => { registry.clear(); }); - const weightMethodAttributes : WeightMethodAttributes = { + const weightMethodAttributes: WeightMethodAttributes = { _uuid: uuidV4(), shortname: 'sample-shortname', name: 'Sample Weight Method', - description: 'Sample weight method description', + description: 'Sample weight method description' }; const validPlaceAttributes: { [key: string]: unknown } = { @@ -42,14 +42,7 @@ describe('WorkPlace', () => { lastAction: 'findPlace', deviceUsed: 'tablet', zoom: 15, - geography: { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0], - }, - properties: {}, - }, + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, _weights: [{ weight: 1.2, method: new WeightMethod(weightMethodAttributes) }], _isValid: true }; @@ -57,13 +50,13 @@ describe('WorkPlace', () => { const validWorkPlaceAttributes: { [key: string]: unknown } = { ...validPlaceAttributes, parkingType: 'interiorAssignedOrGuaranteed', - parkingFeeType: 'paidByEmployee', + parkingFeeType: 'paidByEmployee' }; const extendedWorkPlaceAttributes: { [key: string]: unknown } = { ...validWorkPlaceAttributes, customAttribute1: 'value1', - customAttribute2: 'value2', + customAttribute2: 'value2' }; test('should create a WorkPlace instance with valid attributes', () => { @@ -74,9 +67,11 @@ describe('WorkPlace', () => { test('should have a validateParams section for each attribute', () => { const validateParamsCode = WorkPlace.validateParams.toString(); - placeAttributes.filter((attribute) => attribute !== '_uuid' && attribute !== '_weights').forEach((attributeName) => { - expect(validateParamsCode).toContain('\''+attributeName+'\''); - }); + placeAttributes + .filter((attribute) => attribute !== '_uuid' && attribute !== '_weights') + .forEach((attributeName) => { + expect(validateParamsCode).toContain('\'' + attributeName + '\''); + }); }); test('should get uuid', () => { @@ -100,7 +95,7 @@ describe('WorkPlace', () => { const invalidAttributes = 'foo' as any; const result = WorkPlace.create(invalidAttributes, registry); expect(hasErrors(result)).toBe(true); - expect((unwrap(result) as Error[])).toHaveLength(1); + expect(unwrap(result) as Error[]).toHaveLength(1); }); test('should return errors for invalid attributes', () => { @@ -134,14 +129,8 @@ describe('WorkPlace', () => { }); test('should create a WorkPlace instance with custom attributes', () => { - const customAttributes = { - customAttribute1: 'value1', - customAttribute2: 'value2', - }; - const placeAttributes = { - ...validPlaceAttributes, - ...customAttributes, - }; + const customAttributes = { customAttribute1: 'value1', customAttribute2: 'value2' }; + const placeAttributes = { ...validPlaceAttributes, ...customAttributes }; const place = new WorkPlace(placeAttributes, registry); expect(place).toBeInstanceOf(WorkPlace); expect(place.attributes).toEqual(validPlaceAttributes); @@ -151,7 +140,7 @@ describe('WorkPlace', () => { describe('validateParams', () => { test.each([ ['parkingType', 123], - ['parkingFeeType', 123], + ['parkingFeeType', 123] ])('should return an error for invalid %s', (param, value) => { const invalidAttributes = { ...validWorkPlaceAttributes, [param]: value }; const errors = WorkPlace.validateParams(invalidAttributes); @@ -170,14 +159,7 @@ describe('WorkPlace', () => { ['parkingType', 'exteriorAssignedOrGuaranteed'], ['parkingFeeType', 'free'], ['preData', { importedWorkPlaceData: 'value', employeeCount: 50 }], - ['preGeography', { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [-73.6, 45.6], - }, - properties: {}, - }], + ['preGeography', { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.6, 45.6] }, properties: {} }] ])('should set and get %s', (attribute, value) => { const workPlace = new WorkPlace(validWorkPlaceAttributes, registry); workPlace[attribute] = value; @@ -187,11 +169,11 @@ describe('WorkPlace', () => { describe('Getters for attributes with no setters', () => { test.each([ ['_uuid', validWorkPlaceAttributes._uuid], - ['customAttributes', { - customAttribute1: extendedWorkPlaceAttributes.customAttribute1, - customAttribute2: extendedWorkPlaceAttributes.customAttribute2 - }], - ['attributes', validWorkPlaceAttributes], + [ + 'customAttributes', + { customAttribute1: extendedWorkPlaceAttributes.customAttribute1, customAttribute2: extendedWorkPlaceAttributes.customAttribute2 } + ], + ['attributes', validWorkPlaceAttributes] ])('should set and get %s', (attribute, value) => { const workPlace = new WorkPlace(extendedWorkPlaceAttributes, registry); expect(workPlace[attribute]).toEqual(value); @@ -200,7 +182,7 @@ describe('WorkPlace', () => { test.each([ ['_isValid', false], - ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]], + ['_weights', [{ weight: 2.0, method: new WeightMethod(weightMethodAttributes) }]] ])('should set and get %s', (attribute, value) => { const workPlace = new WorkPlace(validWorkPlaceAttributes, registry); workPlace[attribute] = value; @@ -218,14 +200,7 @@ describe('WorkPlace', () => { }); test('should preserve preGeography through (un)serialize', () => { - const preGeography = { - type: 'Feature' as const, - geometry: { - type: 'Point' as const, - coordinates: [-73.6, 45.6], - }, - properties: {}, - }; + const preGeography = { type: 'Feature' as const, geometry: { type: 'Point' as const, coordinates: [-73.6, 45.6] }, properties: {} }; const attrs = { ...validWorkPlaceAttributes, preGeography }; const wp1 = new WorkPlace(attrs, registry); const wp2 = WorkPlace.unserialize(attrs, registry); @@ -233,5 +208,4 @@ describe('WorkPlace', () => { expect(wp2.preGeography).toEqual(preGeography); }); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.getSet.test.ts b/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.getSet.test.ts index 038eb44cf..2bedec9c9 100644 --- a/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.getSet.test.ts +++ b/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.getSet.test.ts @@ -22,12 +22,7 @@ describe('Interview - Getters and Setters', () => { let interview: Interview; beforeEach(() => { - const validParams = { - _uuid: uuidV4(), - accessCode: 'ABC123', - assignedDate: '2023-09-30', - _startedAt: 1632929461 - }; + const validParams = { _uuid: uuidV4(), accessCode: 'ABC123', assignedDate: '2023-09-30', _startedAt: 1632929461 }; const interviewResult = create(validParams, { id: 123, participant_id: 456 } as RawInterviewAttributes, registry) as Result; if (isOk(interviewResult)) { interview = unwrap(interviewResult) as Interview; @@ -38,9 +33,7 @@ describe('Interview - Getters and Setters', () => { it('should allow undefined id and participant id', () => { let _interview: Interview; - const validParamsWithoutIdAndParticipantId = { - _uuid: uuidV4(), - }; + const validParamsWithoutIdAndParticipantId = { _uuid: uuidV4() }; const result = create(validParamsWithoutIdAndParticipantId, {} as RawInterviewAttributes, registry) as Result; if (isOk(result)) { _interview = unwrap(result) as Interview; @@ -179,5 +172,4 @@ describe('Interview - Getters and Setters', () => { interview.acceptToBeContactedForHelp = undefined; expect(interview.acceptToBeContactedForHelp).toBeUndefined(); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.test.ts b/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.test.ts index ef02fd78a..e23a866cb 100644 --- a/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.test.ts +++ b/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.test.ts @@ -47,9 +47,7 @@ describe('Interview', () => { updatedAt: 1625184000, completedAt: 1625270400, source: 'web', - languages: [ - { language: 'en', startTimestamp: 1625097600, endTimestamp: 1625270400 } - ], + languages: [{ language: 'en', startTimestamp: 1625097600, endTimestamp: 1625270400 }], browsers: [ { _ua: 'Mozilla/5.0...', @@ -60,12 +58,7 @@ describe('Interview', () => { endTimestamp: 1625270400 } ], - sections: { - 'home': { - _startedAt: 1625097600, - _isCompleted: true - } - } + sections: { home: { _startedAt: 1625097600, _isCompleted: true } } }; describe('constructor', () => { @@ -77,10 +70,7 @@ describe('Interview', () => { }); it('should create an Interview instance with acceptToBeContactedForHelp', () => { - const paramsWithAcceptContact = { - ...validParams, - acceptToBeContactedForHelp: true - }; + const paramsWithAcceptContact = { ...validParams, acceptToBeContactedForHelp: true }; const interview = new Interview(paramsWithAcceptContact, createRawInterviewAttributes(), registry); expect(interview.acceptToBeContactedForHelp).toBe(true); }); @@ -100,7 +90,9 @@ describe('Interview', () => { it('should throw an error for invalid UUID', () => { const invalidParams = { ...validParams, _uuid: 'invalid-uuid' }; - expect(() => new Interview(invalidParams, createRawInterviewAttributes({ uuid: 'invalid-uuid' }), registry)).toThrow('Uuidable: invalid uuid'); + expect(() => new Interview(invalidParams, createRawInterviewAttributes({ uuid: 'invalid-uuid' }), registry)).toThrow( + 'Uuidable: invalid uuid' + ); }); }); @@ -115,10 +107,7 @@ describe('Interview', () => { }); it('should create an Interview instance with acceptToBeContactedForHelp using create method', () => { - const paramsWithAcceptContact = { - ...validParams, - acceptToBeContactedForHelp: false - }; + const paramsWithAcceptContact = { ...validParams, acceptToBeContactedForHelp: false }; const result = create(paramsWithAcceptContact, createRawInterviewAttributes(), registry); expect(isOk(result)).toBe(true); if (isOk(result)) { @@ -155,26 +144,22 @@ describe('Interview', () => { it('should throw an error for an invalid UUID', () => { const invalidUuidParams = { ...validParams, _uuid: 'invalid-uuid' }; - expect(() => new Interview(invalidUuidParams, createRawInterviewAttributes({ uuid: 'invalid-uuid' }), registry)).toThrow('Uuidable: invalid uuid'); + expect(() => new Interview(invalidUuidParams, createRawInterviewAttributes({ uuid: 'invalid-uuid' }), registry)).toThrow( + 'Uuidable: invalid uuid' + ); }); }); describe('paradata handling', () => { it('should create an Interview instance with valid paradata', () => { - const paramsWithParadata = { - ...validParams, - _paradata: validParadataParams - }; + const paramsWithParadata = { ...validParams, _paradata: validParadataParams }; const interview = new Interview(paramsWithParadata, createRawInterviewAttributes(), registry); expect(interview.paradata).toBeInstanceOf(InterviewParadata); expect(interview.paradata?.startedAt).toBe(validParadataParams.startedAt); }); it('should create an Interview instance with valid paradata using create method', () => { - const paramsWithParadata = { - ...validParams, - _paradata: validParadataParams - }; + const paramsWithParadata = { ...validParams, _paradata: validParadataParams }; const result = create(paramsWithParadata, createRawInterviewAttributes(), registry); expect(isOk(result)).toBe(true); if (isOk(result)) { @@ -190,10 +175,7 @@ describe('Interview', () => { startedAt: 'invalid' // should be a number // other attributes are tested in a separate file (InterviewParadata.validateParams) }; - const paramsWithInvalidParadata = { - ...validParams, - _paradata: invalidParadataParams - }; + const paramsWithInvalidParadata = { ...validParams, _paradata: invalidParadataParams }; const result = create(paramsWithInvalidParadata, createRawInterviewAttributes(), registry); expect(hasErrors(result)).toBe(true); if (hasErrors(result)) { @@ -201,16 +183,11 @@ describe('Interview', () => { expect(result.errors[0].message).toContain('startedAt'); } }); - }); describe('Interview - Custom attributes and composed objects', () => { it('should handle custom attributes', () => { - const customParams = { - ...validParams, - customField1: 'value1', - customField2: 42 - }; + const customParams = { ...validParams, customField1: 'value1', customField2: 42 }; const interview = new Interview(customParams, createRawInterviewAttributes(), registry); expect(interview.customAttributes.customField1).toBe('value1'); expect(interview.customAttributes.customField2).toBe(42); @@ -233,10 +210,7 @@ describe('Interview', () => { }); it('should handle preData in constructor', () => { - const paramsWithPreData = { - ...validParams, - preData: { importedField: 'importedValue', surveyId: 'S456' } - }; + const paramsWithPreData = { ...validParams, preData: { importedField: 'importedValue', surveyId: 'S456' } }; const interview = new Interview(paramsWithPreData, createRawInterviewAttributes(), registry); expect(interview.preData).toEqual({ importedField: 'importedValue', surveyId: 'S456' }); }); @@ -269,10 +243,7 @@ describe('Interview', () => { describe('preData serialization', () => { test('should preserve preData through create and unserialize', () => { - const paramsWithPreData = { - ...validParams, - preData: { importedInterviewData: 'value', respondentId: 'R123' } - }; + const paramsWithPreData = { ...validParams, preData: { importedInterviewData: 'value', respondentId: 'R123' } }; const result1 = create(paramsWithPreData, createRawInterviewAttributes(), registry); expect(isOk(result1)).toBe(true); if (isOk(result1)) { @@ -321,37 +292,22 @@ describe('Interview', () => { describe('hasMinimumRequiredData', () => { it('should return true when paradata startedAt is defined', () => { - const paramsWithRequiredData = { - ...validParams, - _paradata: { - ...validParadataParams, - startedAt: 1625097600 - } - }; + const paramsWithRequiredData = { ...validParams, _paradata: { ...validParadataParams, startedAt: 1625097600 } }; const interview = new Interview(paramsWithRequiredData, createRawInterviewAttributes(), registry); expect(interview.hasMinimumRequiredData()).toBe(true); }); it('should return false when startedAt is undefined', () => { - const paramsWithoutStartedAt = { - ...validParams, - _paradata: { - ...validParadataParams, - startedAt: undefined - } - }; + const paramsWithoutStartedAt = { ...validParams, _paradata: { ...validParadataParams, startedAt: undefined } }; const interview = new Interview(paramsWithoutStartedAt, createRawInterviewAttributes(), registry); expect(interview.hasMinimumRequiredData()).toBe(false); }); it('should return false when paradata is undefined', () => { - const paramsWithoutParadata = { - ...validParams, - }; + const paramsWithoutParadata = { ...validParams }; const interview = new Interview(paramsWithoutParadata, createRawInterviewAttributes(), registry); expect(interview.hasMinimumRequiredData()).toBe(false); }); - }); describe('Interview - extractDirtyParadataParams', () => { @@ -363,9 +319,7 @@ describe('Interview', () => { _completedAt: 1632930461, _source: 'web', _personRandomSequence: [uuidV4(), uuidV4()], - _sections: { - home: [{ startTimestamp: 1632929461, endTimestamp: 1632930461, widgets: {} }] - } + _sections: { home: [{ startTimestamp: 1632929461, endTimestamp: 1632930461, widgets: {} }] } }; const result = Interview.extractDirtyParadataParams(dirtyParams); @@ -381,12 +335,7 @@ describe('Interview', () => { it('should extract paradata params from dirty params with single browser', () => { const dirtyParams = { - _browser: { - _ua: 'Mozilla/5.0', - browser: { name: 'Chrome', version: '93.0' }, - startTimestamp: 1632929461, - endTimestamp: 1632930461 - }, + _browser: { _ua: 'Mozilla/5.0', browser: { name: 'Chrome', version: '93.0' }, startTimestamp: 1632929461, endTimestamp: 1632930461 }, _startedAt: 1632929461 }; @@ -397,9 +346,7 @@ describe('Interview', () => { }); it('should handle missing optional fields', () => { - const dirtyParams = { - _startedAt: 1632929461 - }; + const dirtyParams = { _startedAt: 1632929461 }; const result = Interview.extractDirtyParadataParams(dirtyParams); @@ -457,7 +404,6 @@ describe('Interview', () => { } }); - it('should extract paradata from dirty params when _paradata and paradata are not provided', () => { const validParams = { _uuid: uuidV4(), @@ -468,16 +414,9 @@ describe('Interview', () => { _completedAt: 1632930461, _source: 'web', _language: 'fr', - _browser: { - _ua: 'Mozilla/5.0', - browser: { name: 'Firefox', version: '92.0' }, - startTimestamp: 1632929461, - endTimestamp: 1632930461 - }, + _browser: { _ua: 'Mozilla/5.0', browser: { name: 'Firefox', version: '92.0' }, startTimestamp: 1632929461, endTimestamp: 1632930461 }, _personRandomSequence: ['uuid1', 'uuid2'], - _sections: { - home: [{ startTimestamp: 1632929461, endTimestamp: 1632930461, widgets: {} }] - } + _sections: { home: [{ startTimestamp: 1632929461, endTimestamp: 1632930461, widgets: {} }] } }; const result = create(validParams, { id: 123, participant_id: 456 } as RawInterviewAttributes, registry); @@ -498,11 +437,7 @@ describe('Interview', () => { }); it('should handle minimal dirty params without paradata fields', () => { - const validParams = { - _uuid: uuidV4(), - accessCode: 'ABC123', - assignedDate: '2023-09-30' - }; + const validParams = { _uuid: uuidV4(), accessCode: 'ABC123', assignedDate: '2023-09-30' }; const result = create(validParams, { id: 123, participant_id: 456 } as RawInterviewAttributes, registry); expect(isOk(result)).toBe(true); diff --git a/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.validateParams.test.ts b/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.validateParams.test.ts index f4f895157..96af39872 100644 --- a/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.validateParams.test.ts +++ b/packages/evolution-common/src/services/baseObjects/interview/__tests__/Interview.validateParams.test.ts @@ -28,7 +28,7 @@ describe('Interview - validateParams', () => { interestRange: 43, difficultyRange: 300, burdenRange: 200, - consideredAbandoning: 'yes', + consideredAbandoning: 'yes' }; it('should validate valid params', () => { @@ -169,13 +169,9 @@ describe('Interview - validateParams', () => { expect(errors).toHaveLength(0); }); - it ('should validate with valid paradata', () => { - const params = { - ...validParams, - paradata: { startedAt: 123456789 } - }; + it('should validate with valid paradata', () => { + const params = { ...validParams, paradata: { startedAt: 123456789 } }; const errors = validateParams(params, { id: 1, participant_id: 1 } as any); expect(errors).toHaveLength(0); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewAudited.test.ts b/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewAudited.test.ts index 4cd2b09a8..af7729eef 100644 --- a/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewAudited.test.ts +++ b/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewAudited.test.ts @@ -22,17 +22,14 @@ describe('InterviewAudited', () => { beforeEach(() => { // Create a mock Interview instance - interview = new Interview({ - _uuid: '123e4567-e89b-12d3-a456-426614174000', - accessCode: 'TEST123', - assignedDate: '2023-05-01' - }, { id: 1, participant_id: 1 } as any, registry); + interview = new Interview( + { _uuid: '123e4567-e89b-12d3-a456-426614174000', accessCode: 'TEST123', assignedDate: '2023-05-01' }, + { id: 1, participant_id: 1 } as any, + registry + ); // Create an InterviewAudited instance - interviewAudited = new InterviewAudited({ - _interview: interview, - _originalInterviewId: 1 - }); + interviewAudited = new InterviewAudited({ _interview: interview, _originalInterviewId: 1 }); }); it('should create an InterviewAudited instance', () => { @@ -80,12 +77,7 @@ describe('InterviewAudited', () => { describe('constructor with initial values', () => { it('should set initial values when provided', () => { - const initialAudits: Audit[] = [{ - version: 1, - level: 'error', - errorCode: 'TEST_ERROR', - message: 'Test error message' - }]; + const initialAudits: Audit[] = [{ version: 1, level: 'error', errorCode: 'TEST_ERROR', message: 'Test error message' }]; const auditedWithInitialValues = new InterviewAudited({ _interview: interview, @@ -117,6 +109,4 @@ describe('InterviewAudited', () => { expect(interviewAudited.getAuditedObject).toBeDefined(); expect(interviewAudited.getOriginalInterviewId).toBeDefined(); }); - }); - diff --git a/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.getSet.test.ts b/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.getSet.test.ts index a71b8b594..aae9f5a31 100644 --- a/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.getSet.test.ts +++ b/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.getSet.test.ts @@ -18,15 +18,8 @@ describe('InterviewParadata - Getters and Setters', () => { source: 'web', personsRandomSequence: ['uuid1', 'uuid2'], languages: [{ language: 'en', startTimestamp: 1632929461, endTimestamp: 1632930461 }], - browsers: [{ - _ua: 'Mozilla/5.0', - browser: { name: 'Chrome', version: '93.0' }, - startTimestamp: 1632929461, - endTimestamp: 1632930461 - }], - sections: { - 'section1': [{ startTimestamp: 1632929461, endTimestamp: 1632930461, widgets: {} }] - } + browsers: [{ _ua: 'Mozilla/5.0', browser: { name: 'Chrome', version: '93.0' }, startTimestamp: 1632929461, endTimestamp: 1632930461 }], + sections: { section1: [{ startTimestamp: 1632929461, endTimestamp: 1632930461, widgets: {} }] } }; const result = InterviewParadata.create(validParams) as Result; if (isOk(result)) { @@ -92,12 +85,9 @@ describe('InterviewParadata - Getters and Setters', () => { expect(paradata.browsers).toHaveLength(1); expect(paradata.browsers?.[0].browser?.name).toBe('Chrome'); - const newBrowsers = [{ - _ua: 'Mozilla/5.0', - browser: { name: 'Firefox', version: '92.0' }, - startTimestamp: 1632930462, - endTimestamp: 1632931462 - }]; + const newBrowsers = [ + { _ua: 'Mozilla/5.0', browser: { name: 'Firefox', version: '92.0' }, startTimestamp: 1632930462, endTimestamp: 1632931462 } + ]; paradata.browsers = newBrowsers; expect(paradata.browsers).toEqual(newBrowsers); @@ -114,14 +104,7 @@ describe('InterviewParadata - Getters and Setters', () => { { section: 'section2', action: 'start', ts: 1632930462 }, { section: 'section2', action: 'end', ts: 1632931462 } ], - 'section2': { - _startedAt: 1632930462, - _isCompleted: true, - 'person/uuid1': { - _startedAt: 1632930462, - _isCompleted: true - } - } + section2: { _startedAt: 1632930462, _isCompleted: true, 'person/uuid1': { _startedAt: 1632930462, _isCompleted: true } } }; paradata.sections = newSections; expect(paradata.sections).toEqual(newSections); @@ -129,5 +112,4 @@ describe('InterviewParadata - Getters and Setters', () => { paradata.sections = undefined; expect(paradata.sections).toEqual({}); }); - }); diff --git a/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.test.ts b/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.test.ts index fa62a7019..a782b8ff4 100644 --- a/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.test.ts +++ b/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.test.ts @@ -15,9 +15,7 @@ describe('InterviewParadata', () => { completedAt: 1625270400, source: 'web', personsRandomSequence: ['uuid1', 'uuid2'], - languages: [ - { language: 'en', startTimestamp: 1625097600, endTimestamp: 1625270400 } - ], + languages: [{ language: 'en', startTimestamp: 1625097600, endTimestamp: 1625270400 }], browsers: [ { _ua: 'Mozilla/5.0...', @@ -33,25 +31,13 @@ describe('InterviewParadata', () => { { section: 'home', action: 'start', ts: 1757531067 }, { section: 'household', action: 'start', ts: 1757531095, iterationContext: ['1'] } ], - 'home': { - _startedAt: 1625097600, - _isCompleted: true - }, - 'household': { - _startedAt: 1625097600, - _isCompleted: true - }, - 'householdMembers': { + home: { _startedAt: 1625097600, _isCompleted: true }, + household: { _startedAt: 1625097600, _isCompleted: true }, + householdMembers: { _startedAt: 1625097600, _isCompleted: true, - 'person/uuid1': { - _startedAt: 1625097600, - _isCompleted: true - }, - 'person/uuid2': { - _startedAt: 1625097600, - _isCompleted: true - } + 'person/uuid1': { _startedAt: 1625097600, _isCompleted: true }, + 'person/uuid2': { _startedAt: 1625097600, _isCompleted: true } } } }; @@ -115,19 +101,19 @@ describe('InterviewParadata', () => { expect(paradata.sections?._actions).toBeDefined(); expect(Array.isArray(paradata.sections?._actions)).toBe(true); expect(paradata.sections?.home).toBeDefined(); - expect((paradata.sections?.home as { _startedAt?: number, _isCompleted?: boolean })?._startedAt).toBe(1625097600); - expect((paradata.sections?.home as { _startedAt?: number, _isCompleted?: boolean })?._isCompleted).toBe(true); + expect((paradata.sections?.home as { _startedAt?: number; _isCompleted?: boolean })?._startedAt).toBe(1625097600); + expect((paradata.sections?.home as { _startedAt?: number; _isCompleted?: boolean })?._isCompleted).toBe(true); expect(paradata.sections?.household).toBeDefined(); - expect((paradata.sections?.household as { _startedAt?: number, _isCompleted?: boolean })?._startedAt).toBe(1625097600); - expect((paradata.sections?.household as { _startedAt?: number, _isCompleted?: boolean })?._isCompleted).toBe(true); + expect((paradata.sections?.household as { _startedAt?: number; _isCompleted?: boolean })?._startedAt).toBe(1625097600); + expect((paradata.sections?.household as { _startedAt?: number; _isCompleted?: boolean })?._isCompleted).toBe(true); expect(paradata.sections?.householdMembers).toBeDefined(); - expect((paradata.sections?.householdMembers as { _startedAt?: number, _isCompleted?: boolean })?._startedAt).toBe(1625097600); - expect((paradata.sections?.householdMembers as { _startedAt?: number, _isCompleted?: boolean })?._isCompleted).toBe(true); - const householdMembers = paradata.sections?.householdMembers as { [key: string]: { _startedAt?: number, _isCompleted?: boolean } }; - expect((householdMembers?.['person/uuid1'] as { _startedAt?: number, _isCompleted?: boolean })?._startedAt).toBe(1625097600); - expect((householdMembers?.['person/uuid1'] as { _startedAt?: number, _isCompleted?: boolean })?._isCompleted).toBe(true); - expect((householdMembers?.['person/uuid2'] as { _startedAt?: number, _isCompleted?: boolean })?._startedAt).toBe(1625097600); - expect((householdMembers?.['person/uuid2'] as { _startedAt?: number, _isCompleted?: boolean })?._isCompleted).toBe(true); + expect((paradata.sections?.householdMembers as { _startedAt?: number; _isCompleted?: boolean })?._startedAt).toBe(1625097600); + expect((paradata.sections?.householdMembers as { _startedAt?: number; _isCompleted?: boolean })?._isCompleted).toBe(true); + const householdMembers = paradata.sections?.householdMembers as { [key: string]: { _startedAt?: number; _isCompleted?: boolean } }; + expect((householdMembers?.['person/uuid1'] as { _startedAt?: number; _isCompleted?: boolean })?._startedAt).toBe(1625097600); + expect((householdMembers?.['person/uuid1'] as { _startedAt?: number; _isCompleted?: boolean })?._isCompleted).toBe(true); + expect((householdMembers?.['person/uuid2'] as { _startedAt?: number; _isCompleted?: boolean })?._startedAt).toBe(1625097600); + expect((householdMembers?.['person/uuid2'] as { _startedAt?: number; _isCompleted?: boolean })?._isCompleted).toBe(true); }); it('should handle empty params', () => { @@ -146,10 +132,7 @@ describe('InterviewParadata', () => { describe('Edge cases', () => { it('should handle empty section data', () => { - const emptyParams = { - ...validParams, - sections: {} - }; + const emptyParams = { ...validParams, sections: {} }; const paradata = new InterviewParadata(emptyParams); expect(paradata.sections).toEqual({}); }); diff --git a/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.validateParams.test.ts b/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.validateParams.test.ts index 514230a9d..9c1eca673 100644 --- a/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.validateParams.test.ts +++ b/packages/evolution-common/src/services/baseObjects/interview/__tests__/InterviewParadata.validateParams.test.ts @@ -15,23 +15,25 @@ describe('InterviewParadata - InterviewParadata.validateParams', () => { source: 'web', personsRandomSequence: ['uuid1', 'uuid2'], languages: [{ language: 'en', startTimestamp: 1632929461, endTimestamp: 1632930461 }], - browsers: [{ - _ua: 'Mozilla/5.0', - browser: { name: 'Chrome', version: '93.0' }, - engine: { name: 'Blink', version: '93.0' }, - os: { name: 'Windows', version: '10', versionName: 'Windows 10' }, - platform: { type: 'desktop', vendor: 'Microsoft', model: 'PC' }, - startTimestamp: 1632929461, - endTimestamp: 1632930461 - }], - sections: { - home: [{ + browsers: [ + { + _ua: 'Mozilla/5.0', + browser: { name: 'Chrome', version: '93.0' }, + engine: { name: 'Blink', version: '93.0' }, + os: { name: 'Windows', version: '10', versionName: 'Windows 10' }, + platform: { type: 'desktop', vendor: 'Microsoft', model: 'PC' }, startTimestamp: 1632929461, - endTimestamp: 1632930461, - widgets: { - widgetShortname: [{ startTimestamp: 1632929461, endTimestamp: 1632930461 }] + endTimestamp: 1632930461 + } + ], + sections: { + home: [ + { + startTimestamp: 1632929461, + endTimestamp: 1632930461, + widgets: { widgetShortname: [{ startTimestamp: 1632929461, endTimestamp: 1632930461 }] } } - }] + ] } }; @@ -40,7 +42,7 @@ describe('InterviewParadata - InterviewParadata.validateParams', () => { expect(errors).toHaveLength(0); }); - it ('should accept empty params', () => { + it('should accept empty params', () => { const params = {}; const errors = InterviewParadata.validateParams(params); expect(errors).toHaveLength(0); @@ -77,25 +79,18 @@ describe('InterviewParadata - InterviewParadata.validateParams', () => { }); it('should validate with empty sections array', () => { - const params = { - ...validParams, - sections: {} - }; + const params = { ...validParams, sections: {} }; const errors = InterviewParadata.validateParams(params); expect(errors).toHaveLength(0); }); it('should validate with empty languages array', () => { - const params = { - ...validParams, - languages: [] - }; + const params = { ...validParams, languages: [] }; const errors = InterviewParadata.validateParams(params); expect(errors).toHaveLength(0); }); describe('Languages validation', () => { - it('should make sure languages is an array', () => { const params = { ...validParams, languages: 'not-an-array' }; const errors = InterviewParadata.validateParams(params); @@ -122,7 +117,6 @@ describe('InterviewParadata - InterviewParadata.validateParams', () => { }); describe('Browsers validation', () => { - it('should make sure browsers is an array', () => { const params = { ...validParams, browsers: 'not-an-array' }; const errors = InterviewParadata.validateParams(params); @@ -142,7 +136,10 @@ describe('InterviewParadata - InterviewParadata.validateParams', () => { }); it('should validate browser version field', () => { - const params = { ...validParams, browsers: [{ ...validParams.browsers[0], browser: { ...validParams.browsers[0].browser, version: 123 } }] }; + const params = { + ...validParams, + browsers: [{ ...validParams.browsers[0], browser: { ...validParams.browsers[0].browser, version: 123 } }] + }; const errors = InterviewParadata.validateParams(params); expect(errors.some((e) => e.message.includes('browsers.[0].browser.version'))).toBe(true); }); @@ -154,7 +151,10 @@ describe('InterviewParadata - InterviewParadata.validateParams', () => { }); it('should validate engine version field', () => { - const params = { ...validParams, browsers: [{ ...validParams.browsers[0], engine: { ...validParams.browsers[0].engine, version: 123 } }] }; + const params = { + ...validParams, + browsers: [{ ...validParams.browsers[0], engine: { ...validParams.browsers[0].engine, version: 123 } }] + }; const errors = InterviewParadata.validateParams(params); expect(errors.some((e) => e.message.includes('browsers.[0].engine.version'))).toBe(true); }); @@ -178,19 +178,28 @@ describe('InterviewParadata - InterviewParadata.validateParams', () => { }); it('should validate platform type field', () => { - const params = { ...validParams, browsers: [{ ...validParams.browsers[0], platform: { ...validParams.browsers[0].platform, type: 123 } }] }; + const params = { + ...validParams, + browsers: [{ ...validParams.browsers[0], platform: { ...validParams.browsers[0].platform, type: 123 } }] + }; const errors = InterviewParadata.validateParams(params); expect(errors.some((e) => e.message.includes('browsers.[0].platform.type'))).toBe(true); }); it('should validate platform vendor field', () => { - const params = { ...validParams, browsers: [{ ...validParams.browsers[0], platform: { ...validParams.browsers[0].platform, vendor: 123 } }] }; + const params = { + ...validParams, + browsers: [{ ...validParams.browsers[0], platform: { ...validParams.browsers[0].platform, vendor: 123 } }] + }; const errors = InterviewParadata.validateParams(params); expect(errors.some((e) => e.message.includes('browsers.[0].platform.vendor'))).toBe(true); }); it('should validate platform model field', () => { - const params = { ...validParams, browsers: [{ ...validParams.browsers[0], platform: { ...validParams.browsers[0].platform, model: 123 } }] }; + const params = { + ...validParams, + browsers: [{ ...validParams.browsers[0], platform: { ...validParams.browsers[0].platform, model: 123 } }] + }; const errors = InterviewParadata.validateParams(params); expect(errors.some((e) => e.message.includes('browsers.[0].platform.model'))).toBe(true); }); @@ -209,19 +218,10 @@ describe('InterviewParadata - InterviewParadata.validateParams', () => { }); describe('Sections validation (legacy format)', () => { - it('should accept legacy sections format with _actions and section objects', () => { const params = { ...validParams, - sections: { - _actions: [ - { section: 'home', action: 'start', ts: 1632929461 } - ], - home: { - _startedAt: 1632929461, - _isCompleted: true - } - } + sections: { _actions: [{ section: 'home', action: 'start', ts: 1632929461 }], home: { _startedAt: 1632929461, _isCompleted: true } } }; const errors = InterviewParadata.validateParams(params); expect(errors).toHaveLength(0); @@ -231,19 +231,11 @@ describe('InterviewParadata - InterviewParadata.validateParams', () => { const params = { ...validParams, sections: { - tripsIntro: { - _startedAt: 1632929461, - _isCompleted: true, - 'person/uuid1': { - _startedAt: 1632929461, - _isCompleted: true - } - } + tripsIntro: { _startedAt: 1632929461, _isCompleted: true, 'person/uuid1': { _startedAt: 1632929461, _isCompleted: true } } } }; const errors = InterviewParadata.validateParams(params); expect(errors).toHaveLength(0); }); }); - }); diff --git a/packages/evolution-common/src/services/geodata/__tests__/SurveyGeographyUtils.test.ts b/packages/evolution-common/src/services/geodata/__tests__/SurveyGeographyUtils.test.ts index a6d6c703c..2d334a33a 100644 --- a/packages/evolution-common/src/services/geodata/__tests__/SurveyGeographyUtils.test.ts +++ b/packages/evolution-common/src/services/geodata/__tests__/SurveyGeographyUtils.test.ts @@ -8,12 +8,15 @@ import * as turf from '@turf/turf'; import { pointsToBezierCurve } from '../SurveyGeographyUtils'; import { isFeature, isLineString } from 'geojson-validation'; - describe('pointsToBezierCurve', () => { describe('pointsToBezierCurve', () => { it('should throw an error if less than 2 points are provided', () => { - expect(() => pointsToBezierCurve([], { superposedSequence: 0, additionalProperties: {} })).toThrow('There must be at least 2 points to convert to a bezier curve'); - expect(() => pointsToBezierCurve([turf.point([0, 0]).geometry], { superposedSequence: 0, additionalProperties: {} })).toThrow('There must be at least 2 points to convert to a bezier curve'); + expect(() => pointsToBezierCurve([], { superposedSequence: 0, additionalProperties: {} })).toThrow( + 'There must be at least 2 points to convert to a bezier curve' + ); + expect(() => pointsToBezierCurve([turf.point([0, 0]).geometry], { superposedSequence: 0, additionalProperties: {} })).toThrow( + 'There must be at least 2 points to convert to a bezier curve' + ); }); it('should return a bezier curve feature from a 2 points array', () => { @@ -61,4 +64,4 @@ describe('pointsToBezierCurve', () => { expect(isLineString(result2.geometry)).toEqual(true); }); }); -}); \ No newline at end of file +}); diff --git a/packages/evolution-common/src/services/odSurvey/__tests__/helpers.test.ts b/packages/evolution-common/src/services/odSurvey/__tests__/helpers.test.ts index 6da0272dc..8718b0f25 100644 --- a/packages/evolution-common/src/services/odSurvey/__tests__/helpers.test.ts +++ b/packages/evolution-common/src/services/odSurvey/__tests__/helpers.test.ts @@ -13,10 +13,7 @@ import projectConfig from '../../../config/project.config'; import { Journey, Person, Trip, UserInterviewAttributes } from '../../questionnaire/types'; import { setProjectConfiguration } from 'chaire-lib-common/lib/config/shared/project.config'; -const baseInterviewAttributes: Pick< - UserInterviewAttributes, - 'id' | 'uuid' | 'participant_id' | 'is_completed' | 'is_questionable' | 'is_valid' -> = { +const baseInterviewAttributes: Pick = { id: 1, uuid: 'arbitrary uuid', participant_id: 1, @@ -27,60 +24,19 @@ const baseInterviewAttributes: Pick< const interviewAttributes: UserInterviewAttributes = { ...baseInterviewAttributes, - response: { - section1: { - q1: 'abc', - q2: 3 - }, - section2: { - q1: 'test' - } - }, - validations: { - section1: { - q1: true, - q2: false - }, - section2: { - q1: true - } - } as any, + response: { section1: { q1: 'abc', q2: 3 }, section2: { q1: 'test' } }, + validations: { section1: { q1: true, q2: false }, section2: { q1: true } } as any, is_valid: true }; const interviewAttributesWithHh: UserInterviewAttributes = { ...baseInterviewAttributes, response: { - section1: { - q1: 'abc', - q2: 3 - }, - section2: { - q1: 'test' - }, - household: { - size: 2, - persons: { - personId1: { - _uuid: 'personId1', - _sequence: 1 - }, - personId2: { - _uuid: 'personId2', - _sequence: 2 - } - } - } + section1: { q1: 'abc', q2: 3 }, + section2: { q1: 'test' }, + household: { size: 2, persons: { personId1: { _uuid: 'personId1', _sequence: 1 }, personId2: { _uuid: 'personId2', _sequence: 2 } } } } as any, - validations: { - section1: { - q1: true, - q2: false - }, - section2: { - q1: true - } - } as any + validations: { section1: { q1: true, q2: false }, section2: { q1: true } } as any }; const originalConfig = _cloneDeep(projectConfig); @@ -91,7 +47,7 @@ beforeEach(() => { afterEach(() => { // Reset projectConfig to original after each test setProjectConfiguration(_cloneDeep(originalConfig)); -}) +}); each([ ['Has Household', interviewAttributesWithHh.response, interviewAttributesWithHh.response.household], @@ -103,30 +59,15 @@ each([ }); each([ - [ - 'Person 1', - interviewAttributesWithHh.response, - 'personId1', - (interviewAttributesWithHh.response as any).household.persons.personId1 - ], - [ - 'Person 2', - interviewAttributesWithHh.response, - 'personId2', - (interviewAttributesWithHh.response as any).household.persons.personId2 - ], + ['Person 1', interviewAttributesWithHh.response, 'personId1', (interviewAttributesWithHh.response as any).household.persons.personId1], + ['Person 2', interviewAttributesWithHh.response, 'personId2', (interviewAttributesWithHh.response as any).household.persons.personId2], [ 'Undefined active person', interviewAttributesWithHh.response, undefined, (interviewAttributesWithHh.response as any).household.persons.personId1 ], - [ - 'Empty persons', - { household: { ...interviewAttributesWithHh.response.household, persons: {} } }, - 'personId1', - null - ], + ['Empty persons', { household: { ...interviewAttributesWithHh.response.household, persons: {} } }, 'personId1', null], ['Empty household', { household: {} }, undefined, null], ['Empty response', {}, 'personId', null] ]).test('getActivePerson: %s', (_title, response, currentPersonId, expected) => { @@ -154,9 +95,7 @@ describe('getPersons', () => { }); test('with persons', () => { - expect(Helpers.getPersons({ interview: interviewAttributesWithHh })).toEqual( - (interviewAttributesWithHh.response as any).household.persons - ); + expect(Helpers.getPersons({ interview: interviewAttributesWithHh })).toEqual((interviewAttributesWithHh.response as any).household.persons); }); test('array: test without household', () => { @@ -199,12 +138,7 @@ each([ { personId: 'personId1' }, (interviewAttributesWithHh.response as any).household.persons.personId1 ], - [ - 'personId param provided, non-existent person', - interviewAttributesWithHh.response, - { personId: 'unexistentPersonId' }, - null - ], + ['personId param provided, non-existent person', interviewAttributesWithHh.response, { personId: 'unexistentPersonId' }, null], [ 'path param provided, matches household.persons.{personId}.', interviewAttributesWithHh.response, @@ -217,18 +151,10 @@ each([ { path: 'household.persons.personId2' }, // FIXME Should this case match? null ], - [ - 'path param provided, does not match pattern', - interviewAttributesWithHh.response, - { path: 'household.something.personId2' }, - null - ], + ['path param provided, does not match pattern', interviewAttributesWithHh.response, { path: 'household.something.personId2' }, null], [ 'no personId or path, uses _activePersonId', - { - ...interviewAttributesWithHh.response, - _activePersonId: 'personId2' - }, + { ...interviewAttributesWithHh.response, _activePersonId: 'personId2' }, {}, (interviewAttributesWithHh.response as any).household.persons.personId2 ], @@ -286,12 +212,7 @@ each([ 'One person', { ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: { - personId1: { _uuid: 'personId1', _sequence: 1 } - } - } + household: { ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1 } } } }, 1 ], @@ -310,17 +231,7 @@ each([ }, 3 ], - [ - 'Empty persons', - { - ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: {} - } - }, - 0 - ], + ['Empty persons', { ...interviewAttributesWithHh.response, household: { ...interviewAttributesWithHh.response.household, persons: {} } }, 0], ['Empty household', { household: {} }, 0], ['Empty response', {}, 0] ]).test('countPersons: %s', (_title, response, expected) => { @@ -336,12 +247,7 @@ each([ 'One person, no age', { ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: { - personId1: { _uuid: 'personId1', _sequence: 1 } - } - } + household: { ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1 } } } }, undefined, 0 @@ -350,12 +256,7 @@ each([ 'One person, age 17', { ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: { - personId1: { _uuid: 'personId1', _sequence: 1, age: 17 } - } - } + household: { ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1, age: 17 } } } }, undefined, 0 @@ -364,12 +265,7 @@ each([ 'One person, age 18', { ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: { - personId1: { _uuid: 'personId1', _sequence: 1, age: 18 } - } - } + household: { ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1, age: 18 } } } }, undefined, 1 @@ -408,13 +304,7 @@ each([ ], [ 'Empty persons', - { - ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: {} - } - }, + { ...interviewAttributesWithHh.response, household: { ...interviewAttributesWithHh.response.household, persons: {} } }, undefined, 0 ], @@ -425,12 +315,7 @@ each([ 'One person, age 21, adultAge 21', { ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: { - personId1: { _uuid: 'personId1', _sequence: 1, age: 21 } - } - } + household: { ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1, age: 21 } } } }, 21, 1 @@ -439,12 +324,7 @@ each([ 'One person, age 20, adultAge 21', { ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: { - personId1: { _uuid: 'personId1', _sequence: 1, age: 20 } - } - } + household: { ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1, age: 20 } } } }, 21, 0 @@ -500,13 +380,7 @@ describe('getInterviewablePersonsArray', () => { }); each([ - [ - 'Have ages, one interviewable person', - { - personId1: { _uuid: 'personId1', _sequence: 1, age: 23 } - }, - ['personId1'] - ], + ['Have ages, one interviewable person', { personId1: { _uuid: 'personId1', _sequence: 1, age: 23 } }, ['personId1']], [ 'Have ages, some non-interviewable persons', { @@ -518,10 +392,7 @@ describe('getInterviewablePersonsArray', () => { ], [ 'No age, all interviewable', - { - personId1: { _uuid: 'personId1', _sequence: 1 }, - personId2: { _uuid: 'personId2', _sequence: 2 } - }, + { personId1: { _uuid: 'personId1', _sequence: 1 }, personId2: { _uuid: 'personId2', _sequence: 2 } }, ['personId1', 'personId2'] ], ['No persons', {}, []] @@ -538,12 +409,7 @@ each([ 'One person-only', { ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: { - personId1: { _uuid: 'personId1', _sequence: 1, age: 18 } - } - } + household: { ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1, age: 18 } } } }, 'personId1', true @@ -552,12 +418,7 @@ each([ 'One person-only, too young', { ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: { - personId1: { _uuid: 'personId1', _sequence: 1, age: 12 } - } - } + household: { ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1, age: 12 } } } }, 'personId1', false @@ -644,13 +505,7 @@ each([ ], [ 'Empty persons', - { - ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: {} - } - }, + { ...interviewAttributesWithHh.response, household: { ...interviewAttributesWithHh.response.household, persons: {} } }, { _uuid: 'somePersonId', _sequence: 1 }, false ], @@ -659,8 +514,7 @@ each([ ]).test('isSelfDeclared: %s', (_title, response, personIdOrPerson: string | Person, expected) => { const interview = _cloneDeep(interviewAttributesWithHh); interview.response = response; - const person = - typeof personIdOrPerson === 'string' ? response.household!.persons![personIdOrPerson] : personIdOrPerson; + const person = typeof personIdOrPerson === 'string' ? response.household!.persons![personIdOrPerson] : personIdOrPerson; expect(Helpers.isSelfDeclared({ interview, person })).toEqual(expected); }); @@ -669,12 +523,7 @@ each([ 'One person-only', { ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: { - personId1: { _uuid: 'personId1', _sequence: 1, age: 18 } - } - } + household: { ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1, age: 18 } } } }, 'personId1', 1 @@ -745,13 +594,7 @@ each([ ], [ 'Empty persons', - { - ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: {} - } - }, + { ...interviewAttributesWithHh.response, household: { ...interviewAttributesWithHh.response.household, persons: {} } }, { _uuid: 'somePersonId', _sequence: 1 }, 0 ], @@ -760,8 +603,7 @@ each([ ]).test('getCountOrSelfDeclared: %s', (_title, response, personIdOrPerson: string | Person, expected) => { const interview = _cloneDeep(interviewAttributesWithHh); interview.response = response; - const person = - typeof personIdOrPerson === 'string' ? response.household!.persons![personIdOrPerson] : personIdOrPerson; + const person = typeof personIdOrPerson === 'string' ? response.household!.persons![personIdOrPerson] : personIdOrPerson; expect(Helpers.getCountOrSelfDeclared({ interview, person })).toEqual(expected); }); @@ -779,23 +621,10 @@ each([ }); each([ - [ - 'Undefined household', - { - ...interviewAttributesWithHh.response, - household: undefined - }, - false - ], + ['Undefined household', { ...interviewAttributesWithHh.response, household: undefined }, false], [ 'Empty persons object', - { - ...interviewAttributesWithHh.response, - household: { - ...interviewAttributesWithHh.response.household, - persons: {} - } - }, + { ...interviewAttributesWithHh.response, household: { ...interviewAttributesWithHh.response.household, persons: {} } }, false ], [ @@ -821,13 +650,7 @@ each([ ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1, age: 5, hasDisability: 'yes' }, - personId2: { - _uuid: 'personId2', - _sequence: 2, - age: 30, - whoWillAnswerForThisPerson: 'personId3', - hasDisability: 'yes' - }, + personId2: { _uuid: 'personId2', _sequence: 2, age: 30, whoWillAnswerForThisPerson: 'personId3', hasDisability: 'yes' }, personId3: { _uuid: 'personId3', _sequence: 3, age: 20, hasDisability: 'yes' } } } @@ -842,13 +665,7 @@ each([ ...interviewAttributesWithHh.response.household, persons: { personId1: { _uuid: 'personId1', _sequence: 1, age: 5, hasDisability: 'no' }, - personId2: { - _uuid: 'personId2', - _sequence: 2, - age: 30, - whoWillAnswerForThisPerson: 'personId3', - hasDisability: 'no' - }, + personId2: { _uuid: 'personId2', _sequence: 2, age: 30, whoWillAnswerForThisPerson: 'personId3', hasDisability: 'no' }, personId3: { _uuid: 'personId3', _sequence: 3, age: 20, hasDisability: 'no' } } } @@ -877,23 +694,11 @@ each([ }); describe('getJourneys', () => { - const person: Person = { - _uuid: 'arbitraryPerson', - _sequence: 1 - }; + const person: Person = { _uuid: 'arbitraryPerson', _sequence: 1 }; const journeys = { - journeyId1: { - _uuid: 'journeyId1', - _sequence: 2, - visitedPlaces: {}, - trips: {} - }, - journeyId2: { - _uuid: 'journeyId2', - _sequence: 1, - activity: 'work' - } + journeyId1: { _uuid: 'journeyId1', _sequence: 2, visitedPlaces: {}, trips: {} }, + journeyId2: { _uuid: 'journeyId2', _sequence: 1, activity: 'work' } }; test('object: test without journeys', () => { @@ -930,15 +735,7 @@ describe('getJourneys', () => { each([ ['null personId, no active person, no journey', interviewAttributesWithHh.response, null, null], - [ - 'null personId, get active person, no journey', - { - ...interviewAttributesWithHh.response, - _activePersonId: 'personId2' - }, - null, - null - ], + ['null personId, get active person, no journey', { ...interviewAttributesWithHh.response, _activePersonId: 'personId2' }, null, null], [ 'null personId, get active person, with active journey', { @@ -947,10 +744,7 @@ describe('getJourneys', () => { ...interviewAttributesWithHh.response.household, persons: { ...interviewAttributesWithHh.response.household!.persons, - personId2: { - ...interviewAttributesWithHh.response.household!.persons!.personId2, - journeys: journeys - } + personId2: { ...interviewAttributesWithHh.response.household!.persons!.personId2, journeys: journeys } } }, _activePersonId: 'personId2', @@ -967,10 +761,7 @@ describe('getJourneys', () => { ...interviewAttributesWithHh.response.household, persons: { ...interviewAttributesWithHh.response.household!.persons, - personId2: { - ...interviewAttributesWithHh.response.household!.persons!.personId2, - journeys: journeys - } + personId2: { ...interviewAttributesWithHh.response.household!.persons!.personId2, journeys: journeys } } }, _activePersonId: 'personId1', @@ -987,10 +778,7 @@ describe('getJourneys', () => { ...interviewAttributesWithHh.response.household, persons: { ...interviewAttributesWithHh.response.household!.persons, - personId2: { - ...interviewAttributesWithHh.response.household!.persons!.personId2, - journeys: journeys - } + personId2: { ...interviewAttributesWithHh.response.household!.persons!.personId2, journeys: journeys } } }, _activePersonId: 'personId1', @@ -1007,10 +795,7 @@ describe('getJourneys', () => { ...interviewAttributesWithHh.response.household, persons: { ...interviewAttributesWithHh.response.household!.persons, - personId2: { - ...interviewAttributesWithHh.response.household!.persons!.personId2, - journeys: journeys - } + personId2: { ...interviewAttributesWithHh.response.household!.persons!.personId2, journeys: journeys } } }, _activePersonId: 'personId2', @@ -1028,17 +813,9 @@ describe('getJourneys', () => { persons: { personId1: { ...interviewAttributesWithHh.response.household!.persons!.personId2, - journeys: { - someJourneyId: { - _uuid: 'someJourneyId', - _sequence: 1 - } - } + journeys: { someJourneyId: { _uuid: 'someJourneyId', _sequence: 1 } } }, - personId2: { - ...interviewAttributesWithHh.response.household!.persons!.personId2, - journeys: journeys - } + personId2: { ...interviewAttributesWithHh.response.household!.persons!.personId2, journeys: journeys } } }, _activePersonId: 'personId1', @@ -1052,33 +829,17 @@ describe('getJourneys', () => { ]).test('getActiveJourney: %s', (_title, response, personId, expected) => { const interview = _cloneDeep(interviewAttributesWithHh); interview.response = response; - const person = - personId === null - ? null - : interview.response?.household?.persons - ? interview.response?.household?.persons[personId] - : null; + const person = personId === null ? null : interview.response?.household?.persons ? interview.response?.household?.persons[personId] : null; expect(Helpers.getActiveJourney({ interview, person })).toEqual(expected); }); }); describe('getVisitedPlaces', () => { - const journey: Journey = { - _uuid: 'arbitraryJourney', - _sequence: 1 - }; + const journey: Journey = { _uuid: 'arbitraryJourney', _sequence: 1 }; const visitedPlaces = { - visitedPlace1: { - _uuid: 'visitedPlace1', - _sequence: 2, - activity: 'home' - }, - visitedPlace2: { - _uuid: 'visitedPlace2', - _sequence: 1, - activity: 'work' - } + visitedPlace1: { _uuid: 'visitedPlace1', _sequence: 2, activity: 'home' }, + visitedPlace2: { _uuid: 'visitedPlace2', _sequence: 1, activity: 'work' } }; test('object: test without visited places', () => { @@ -1110,10 +871,7 @@ describe('getVisitedPlaces', () => { test('array: with visited places, ordered', () => { const attributes = _cloneDeep(journey); attributes.visitedPlaces = visitedPlaces; - expect(Helpers.getVisitedPlacesArray({ journey: attributes })).toEqual([ - visitedPlaces.visitedPlace2, - visitedPlaces.visitedPlace1 - ]); + expect(Helpers.getVisitedPlacesArray({ journey: attributes })).toEqual([visitedPlaces.visitedPlace2, visitedPlaces.visitedPlace1]); }); each([ @@ -1141,12 +899,7 @@ describe('getVisitedPlaces', () => { ], [ 'With journey, no active visitedPlace', - { - activePersonId: 'personId1', - activeJourneyId: 'journeyId1', - testPersonId: 'personId1', - testJourneyId: 'journeyId1' - }, + { activePersonId: 'personId1', activeJourneyId: 'journeyId1', testPersonId: 'personId1', testJourneyId: 'journeyId1' }, false ], [ @@ -1179,13 +932,7 @@ describe('getVisitedPlaces', () => { personId1: { _uuid: 'personId1', _sequence: 1, - journeys: { - journeyId1: { - _uuid: 'journeyId1', - _sequence: 1, - visitedPlaces: visitedPlaces - } - } + journeys: { journeyId1: { _uuid: 'journeyId1', _sequence: 1, visitedPlaces: visitedPlaces } } }, personId2: { _uuid: 'personId2', @@ -1194,12 +941,7 @@ describe('getVisitedPlaces', () => { journeyId2: { _uuid: 'journeyId2', _sequence: 1, - visitedPlaces: { - visitedPlaceP2V1: { - _uuid: 'visitedPlaceP2V1', - _sequence: 1 - } - } + visitedPlaces: { visitedPlaceP2V1: { _uuid: 'visitedPlaceP2V1', _sequence: 1 } } } } } @@ -1218,10 +960,9 @@ describe('getVisitedPlaces', () => { ? interview.response.household!.persons![testData.testPersonId].journeys![testData.testJourneyId] : null; const result = expectResult - ? interview.response.household!.persons![(testData.testPersonId || testData.activePersonId) as string] - .journeys![(testData.testJourneyId || testData.activeJourneyId) as string].visitedPlaces![ - testData.activeVisitedPlaceId! - ] + ? interview.response.household!.persons![(testData.testPersonId || testData.activePersonId) as string].journeys![ + (testData.testJourneyId || testData.activeJourneyId) as string + ].visitedPlaces![testData.activeVisitedPlaceId!] : null; const activeVisitedPlace = Helpers.getActiveVisitedPlace({ interview, journey }); if (expectResult) { @@ -1235,22 +976,11 @@ describe('getVisitedPlaces', () => { }); describe('getNext/PreviousVisitedPlace', () => { - const journey: Journey = { - _uuid: 'arbitraryJourney', - _sequence: 1 - }; + const journey: Journey = { _uuid: 'arbitraryJourney', _sequence: 1 }; const visitedPlaces = { - visitedPlace1: { - _uuid: 'visitedPlace1', - _sequence: 1, - activity: 'home' - }, - visitedPlace2: { - _uuid: 'visitedPlace2', - _sequence: 2, - activity: 'work' - } + visitedPlace1: { _uuid: 'visitedPlace1', _sequence: 1, activity: 'home' }, + visitedPlace2: { _uuid: 'visitedPlace2', _sequence: 2, activity: 'work' } }; test('Next: without visited places', () => { @@ -1429,14 +1159,10 @@ describe('replaceVisitedPlaceShortcuts', () => { ).toEqual({ updatedValuesByPath: { ['response.household.persons.person1.journeys.journey1.visitedPlaces.shortcutToShortcut.shortcut']: ( - (shortcutInterview.response.household!.persons!.person3.journeys!.journey1.visitedPlaces || {})[ - 'shortcutToBasic2' - ] as any + (shortcutInterview.response.household!.persons!.person3.journeys!.journey1.visitedPlaces || {})['shortcutToBasic2'] as any ).shortcut, - ['response.household.persons.person1.journeys.journey1.visitedPlaces.shortcutToShortcut.geography']: - (shortcutInterview.response.household!.persons!.person3.journeys!.journey1.visitedPlaces || {})[ - 'shortcutToBasic2' - ].geography + ['response.household.persons.person1.journeys.journey1.visitedPlaces.shortcutToShortcut.geography']: (shortcutInterview.response + .household!.persons!.person3.journeys!.journey1.visitedPlaces || {})['shortcutToBasic2'].geography }, unsetPaths: [] }); @@ -1451,14 +1177,10 @@ describe('replaceVisitedPlaceShortcuts', () => { ).toEqual({ updatedValuesByPath: { ['response.household.persons.person3.journeys.journey1.visitedPlaces.shortcutToBasic2.name']: ( - (shortcutInterview.response.household!.persons!.person2.journeys!.journey1.visitedPlaces || {})[ - 'basicPlace2' - ] as any + (shortcutInterview.response.household!.persons!.person2.journeys!.journey1.visitedPlaces || {})['basicPlace2'] as any ).name, - ['response.household.persons.person3.journeys.journey1.visitedPlaces.shortcutToBasic2.geography']: - (shortcutInterview.response.household!.persons!.person2.journeys!.journey1.visitedPlaces || {})[ - 'basicPlace2' - ].geography + ['response.household.persons.person3.journeys.journey1.visitedPlaces.shortcutToBasic2.geography']: (shortcutInterview.response + .household!.persons!.person2.journeys!.journey1.visitedPlaces || {})['basicPlace2'].geography }, unsetPaths: ['response.household.persons.person3.journeys.journey1.visitedPlaces.shortcutToBasic2.shortcut'] }); @@ -1473,14 +1195,10 @@ describe('replaceVisitedPlaceShortcuts', () => { ).toEqual({ updatedValuesByPath: { ['response.household.persons.person2.journeys.journey1.visitedPlaces.isAShortcut.name']: ( - (shortcutInterview.response.household!.persons!.person1.journeys!.journey1.visitedPlaces || {})[ - 'usedAsShortcut' - ] as any + (shortcutInterview.response.household!.persons!.person1.journeys!.journey1.visitedPlaces || {})['usedAsShortcut'] as any ).name, - ['response.household.persons.person2.journeys.journey1.visitedPlaces.isAShortcut.geography']: - (shortcutInterview.response.household!.persons!.person1.journeys!.journey1.visitedPlaces || {})[ - 'usedAsShortcut' - ].geography, + ['response.household.persons.person2.journeys.journey1.visitedPlaces.isAShortcut.geography']: (shortcutInterview.response.household! + .persons!.person1.journeys!.journey1.visitedPlaces || {})['usedAsShortcut'].geography, ['response.household.persons.person3.journeys.journey1.visitedPlaces.isAShortcutToo.shortcut']: 'household.persons.person2.journeys.journey1.visitedPlaces.isAShortcut', ['response.household.persons.person3.journeys.journey1.visitedPlaces.againAShortcut.shortcut']: @@ -1492,21 +1210,9 @@ describe('replaceVisitedPlaceShortcuts', () => { }); describe('getTrips', () => { - const journey: Journey = { - _uuid: 'arbitraryJourney', - _sequence: 1 - }; + const journey: Journey = { _uuid: 'arbitraryJourney', _sequence: 1 }; - const trips = { - trip1: { - _uuid: 'trip1', - _sequence: 2 - }, - trip2: { - _uuid: 'trip2', - _sequence: 1 - } - }; + const trips = { trip1: { _uuid: 'trip1', _sequence: 2 }, trip2: { _uuid: 'trip2', _sequence: 1 } }; test('object: test without trips', () => { expect(Helpers.getTrips({ journey })).toEqual({}); @@ -1565,12 +1271,7 @@ describe('getTrips', () => { ], [ 'With journey, no active trip', - { - activePersonId: 'personId1', - activeJourneyId: 'journeyId1', - testPersonId: 'personId1', - testJourneyId: 'journeyId1' - }, + { activePersonId: 'personId1', activeJourneyId: 'journeyId1', testPersonId: 'personId1', testJourneyId: 'journeyId1' }, false ], [ @@ -1588,44 +1289,17 @@ describe('getTrips', () => { 'getActiveTrip: %s', ( _title, - testData: { - activePersonId?: string; - activeJourneyId?: string; - activeTripId?: string; - testPersonId?: string; - testJourneyId?: string; - }, + testData: { activePersonId?: string; activeJourneyId?: string; activeTripId?: string; testPersonId?: string; testJourneyId?: string }, expectResult ) => { const interview = _cloneDeep(interviewAttributesWithHh); // Set the persons and journeys for the test interview.response.household!.persons = { - personId1: { - _uuid: 'personId1', - _sequence: 1, - journeys: { - journeyId1: { - _uuid: 'journeyId1', - _sequence: 1, - trips: trips - } - } - }, + personId1: { _uuid: 'personId1', _sequence: 1, journeys: { journeyId1: { _uuid: 'journeyId1', _sequence: 1, trips: trips } } }, personId2: { _uuid: 'personId2', _sequence: 2, - journeys: { - journeyId2: { - _uuid: 'journeyId2', - _sequence: 1, - trips: { - tripP2T1: { - _uuid: 'tripP2T1', - _sequence: 1 - } - } - } - } + journeys: { journeyId2: { _uuid: 'journeyId2', _sequence: 1, trips: { tripP2T1: { _uuid: 'tripP2T1', _sequence: 1 } } } } } }; if (testData.activePersonId) { @@ -1642,10 +1316,9 @@ describe('getTrips', () => { ? interview.response.household!.persons![testData.testPersonId].journeys![testData.testJourneyId] : null; const result = expectResult - ? interview.response.household!.persons![(testData.testPersonId || testData.activePersonId) as string] - .journeys![(testData.testJourneyId || testData.activeJourneyId) as string].trips![ - testData.activeTripId! - ] + ? interview.response.household!.persons![(testData.testPersonId || testData.activePersonId) as string].journeys![ + (testData.testJourneyId || testData.activeJourneyId) as string + ].trips![testData.activeTripId!] : null; const activeTrip = Helpers.getActiveTrip({ interview, journey }); if (expectResult) { @@ -1674,10 +1347,7 @@ describe('selectNextIncompleteTrip', () => { segment2: { _uuid: 'segment1', _sequence: 1, mode: 'carDriver' as const, hasNextMode: false, _isNew: false } }; - const trips = { - trip1: { _uuid: 'trip1', _sequence: 1, segments }, - trip2: { _uuid: 'trip2', _sequence: 2 } - }; + const trips = { trip1: { _uuid: 'trip1', _sequence: 1, segments }, trip2: { _uuid: 'trip2', _sequence: 2 } }; const journey: Journey = { _uuid: 'arbitraryJourney', _sequence: 1, trips }; @@ -1729,16 +1399,14 @@ describe('getOrigin/getDestination', () => { const journey = interview.response.household!.persons!.personId2.journeys!.journeyId2; // Unset origin delete journey.trips!.tripId3P2._originVisitedPlaceUuid; - expect(Helpers.getOrigin({ trip: journey.trips!.tripId3P2, visitedPlaces: journey.visitedPlaces! })).toEqual( - null - ); + expect(Helpers.getOrigin({ trip: journey.trips!.tripId3P2, visitedPlaces: journey.visitedPlaces! })).toEqual(null); }); test('getDestination, existing', () => { const journey = interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1; - expect( - Helpers.getDestination({ trip: journey.trips!.tripId1P1, visitedPlaces: journey.visitedPlaces! }) - ).toEqual(journey.visitedPlaces!.workPlace1P1); + expect(Helpers.getDestination({ trip: journey.trips!.tripId1P1, visitedPlaces: journey.visitedPlaces! })).toEqual( + journey.visitedPlaces!.workPlace1P1 + ); }); test('getDestination: unexisting', () => { @@ -1752,9 +1420,7 @@ describe('getOrigin/getDestination', () => { const journey = interview.response.household!.persons!.personId2.journeys!.journeyId2; // Unset destination delete journey.trips!.tripId3P2._destinationVisitedPlaceUuid; - expect( - Helpers.getDestination({ trip: journey.trips!.tripId3P2, visitedPlaces: journey.visitedPlaces! }) - ).toEqual(null); + expect(Helpers.getDestination({ trip: journey.trips!.tripId3P2, visitedPlaces: journey.visitedPlaces! })).toEqual(null); }); }); @@ -1764,40 +1430,30 @@ describe('getVisitedPlaceNames', () => { each([ [ 'Home place', - interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .homePlace1P1, + interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.homePlace1P1, 'survey:visitedPlace:activityCategories:home', 'mocked' ], [ 'Place with a name', - interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .workPlace1P1, + interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.workPlace1P1, undefined, - interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .workPlace1P1.name + interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.workPlace1P1.name ], [ 'Place with a shortcut', - interviewAttributesForTestCases.response.household!.persons!.personId2.journeys!.journeyId2.visitedPlaces! - .shoppingPlace1P2, + interviewAttributesForTestCases.response.household!.persons!.personId2.journeys!.journeyId2.visitedPlaces!.shoppingPlace1P2, undefined, - interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .otherPlace2P1.name + interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.otherPlace2P1.name ], [ 'Place with neither name or shortcut', - interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .otherPlaceP1, + interviewAttributesForTestCases.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.otherPlaceP1, 'survey:placeGeneric', 'mocked 4' ] ]).test('%s', (_title, visitedPlace, mockedTVal, expected) => { - const name = Helpers.getVisitedPlaceName({ - t: mockedT, - visitedPlace, - interview: interviewAttributesForTestCases - }); + const name = Helpers.getVisitedPlaceName({ t: mockedT, visitedPlace, interview: interviewAttributesForTestCases }); if (mockedTVal) { expect(mockedT).toHaveBeenCalledWith(mockedTVal); } else { @@ -1811,19 +1467,11 @@ describe('getVisitedPlaceGeography', () => { // Add a usual workplace and school place and a place const usualWorkPlace = { name: 'name of my work place', - geography: { - type: 'Feature', - geometry: { type: 'Point', coordinates: [-73.5, 45.5] }, - properties: { lastAction: 'mapClicked' } - } + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.5, 45.5] }, properties: { lastAction: 'mapClicked' } } }; const usualSchoolPlace = { name: 'name of my school place', - geography: { - type: 'Feature', - geometry: { type: 'Point', coordinates: [-73.5673, 45.5017] }, - properties: { lastAction: 'mapClicked' } - } + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [-73.5673, 45.5017] }, properties: { lastAction: 'mapClicked' } } }; const testInterviewAttributes = _cloneDeep(interviewAttributesForTestCases); (testInterviewAttributes.response.household!.persons!.personId1 as any).usualWorkPlace = usualWorkPlace; @@ -1841,54 +1489,42 @@ describe('getVisitedPlaceGeography', () => { each([ [ 'Home place', - testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .homePlace1P1, + testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.homePlace1P1, testInterviewAttributes.response.household!.persons!.personId1, testInterviewAttributes.response.home!.geography ], [ 'Place with a geography', - testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .workPlace1P1, + testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.workPlace1P1, testInterviewAttributes.response.household!.persons!.personId1, - testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .workPlace1P1.geography + testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.workPlace1P1.geography ], [ 'Place with a shortcut', - testInterviewAttributes.response.household!.persons!.personId2.journeys!.journeyId2.visitedPlaces! - .shoppingPlace1P2, + testInterviewAttributes.response.household!.persons!.personId2.journeys!.journeyId2.visitedPlaces!.shoppingPlace1P2, testInterviewAttributes.response.household!.persons!.personId2, - testInterviewAttributes.response.household!.persons!.personId2.journeys!.journeyId2.visitedPlaces! - .shoppingPlace1P2.geography + testInterviewAttributes.response.household!.persons!.personId2.journeys!.journeyId2.visitedPlaces!.shoppingPlace1P2.geography ], [ 'Place without a geography', - testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .otherPlaceP1, + testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.otherPlaceP1, testInterviewAttributes.response.household!.persons!.personId1, null ], [ 'Usual work place', - testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .usualWorkPlace1P1, + testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.usualWorkPlace1P1, testInterviewAttributes.response.household!.persons!.personId1, usualWorkPlace.geography ], [ 'Usual school place', - testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces! - .usualSchoolPlace1P1, + testInterviewAttributes.response.household!.persons!.personId1.journeys!.journeyId1.visitedPlaces!.usualSchoolPlace1P1, testInterviewAttributes.response.household!.persons!.personId1, usualSchoolPlace.geography ] ]).test('%s', (_title, visitedPlace, person, expected) => { - const geography = Helpers.getVisitedPlaceGeography({ - visitedPlace, - interview: testInterviewAttributes, - person - }); + const geography = Helpers.getVisitedPlaceGeography({ visitedPlace, interview: testInterviewAttributes, person }); if (expected) { expect(geography).toEqual(expected); } else { @@ -1898,23 +1534,9 @@ describe('getVisitedPlaceGeography', () => { }); describe('getSegments', () => { - const trip: Trip = { - _uuid: 'arbitraryTrip', - _sequence: 1 - }; + const trip: Trip = { _uuid: 'arbitraryTrip', _sequence: 1 }; - const segments = { - segment1: { - _uuid: 'segment1', - _sequence: 2, - _isNew: false - }, - segment2: { - _uuid: 'segment2', - _sequence: 1, - _isNew: false - } - }; + const segments = { segment1: { _uuid: 'segment1', _sequence: 2, _isNew: false }, segment2: { _uuid: 'segment2', _sequence: 1, _isNew: false } }; test('object: test without segments', () => { expect(Helpers.getSegments({ trip })).toEqual({}); diff --git a/packages/evolution-common/src/services/questionnaire/sections/__tests__/NavigationService.test.ts b/packages/evolution-common/src/services/questionnaire/sections/__tests__/NavigationService.test.ts index f42639d3c..da6636e54 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/__tests__/NavigationService.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/__tests__/NavigationService.test.ts @@ -6,13 +6,10 @@ */ import _cloneDeep from 'lodash/cloneDeep'; import _shuffle from 'lodash/shuffle'; -import { SectionConfigWithDefaultsBlock, SurveySections, UserRuntimeInterviewAttributes, getAndValidateSurveySections } from '../../types'; +import { SectionConfigWithDefaultsBlock, SurveySections, UserRuntimeInterviewAttributes, getAndValidateSurveySections } from '../../types'; import { createNavigationService } from '../NavigationService'; -jest.mock('lodash/shuffle', () => ({ - __esModule: true, - default: jest.fn((array) => array) -})); +jest.mock('lodash/shuffle', () => ({ __esModule: true, default: jest.fn((array) => array) })); const mockShuffle = _shuffle as jest.MockedFunction; // TODO Add a basic interview object @@ -21,19 +18,19 @@ const interview: UserRuntimeInterviewAttributes = { uuid: 'uuid', participant_id: 1, is_completed: false, - response: { }, - validations: { }, + response: {}, + validations: {}, is_valid: false, // These are widgets statuses for the current section, if they are not grouped - widgets: { }, + widgets: {}, // These are the widget status for the groups in the current section - groups: { }, + groups: {}, // Contains the paths in the responses of the visible widgets... FIXME Rename? visibleWidgets: [], allWidgetsValid: true, // Name of the currently loaded section sectionLoaded: undefined -} +}; beforeEach(() => { jest.clearAllMocks(); @@ -61,12 +58,7 @@ const simpleSectionsConfig: SurveySections = getAndValidateSurveySections({ isSectionCompleted: hhSectionCompleted, enableConditional: hhSectionEnabled }, - end: { - title: 'End', - previousSection: 'householdMembers', - nextSection: null, - widgets: ['endWidget'] - } + end: { title: 'End', previousSection: 'householdMembers', nextSection: null, widgets: ['endWidget'] } }); // Define a navigation with repeated blocks @@ -74,18 +66,8 @@ const tbIsSectionVisible = jest.fn().mockReturnValue(true); const tbIsSectionEnabled = jest.fn().mockReturnValue(true); const tripsIsEnabled = jest.fn().mockReturnValue(true); const complexSectionsConfig: SurveySections = getAndValidateSurveySections({ - home: { - title: 'Home', - previousSection: null, - nextSection: 'householdMembers', - widgets: ['homeWidget'] - }, - householdMembers: { - title: 'Household members', - previousSection: 'home', - nextSection: 'personsTrips', - widgets: ['hhWidget'] - }, + home: { title: 'Home', previousSection: null, nextSection: 'householdMembers', widgets: ['homeWidget'] }, + householdMembers: { title: 'Household members', previousSection: 'home', nextSection: 'personsTrips', widgets: ['hhWidget'] }, personsTrips: { title: 'Trips', previousSection: 'householdMembers', @@ -100,18 +82,8 @@ const complexSectionsConfig: SurveySections = getAndValidateSurveySections({ skipSelectionInNaturalFlow: true } }, - selectPerson: { - title: 'Select person', - previousSection: 'personsTrips', - nextSection: 'visitedPlaces', - widgets: ['hhWidget'] - }, - visitedPlaces: { - title: 'Visited places', - previousSection: 'selectPerson', - nextSection: 'travelBehavior', - widgets: ['hhWidget'] - }, + selectPerson: { title: 'Select person', previousSection: 'personsTrips', nextSection: 'visitedPlaces', widgets: ['hhWidget'] }, + visitedPlaces: { title: 'Visited places', previousSection: 'selectPerson', nextSection: 'travelBehavior', widgets: ['hhWidget'] }, travelBehavior: { title: 'Travel behavior', previousSection: 'visitedPlaces', @@ -120,51 +92,28 @@ const complexSectionsConfig: SurveySections = getAndValidateSurveySections({ isSectionVisible: tbIsSectionVisible, enableConditional: tbIsSectionEnabled }, - end: { - title: 'End', - previousSection: 'personsTrips', - nextSection: null, - widgets: ['endWidget'] - } + end: { title: 'End', previousSection: 'personsTrips', nextSection: null, widgets: ['endWidget'] } }); const hhWithPersonsResponse = { household: { persons: { - personId1: { - _uuid: 'personId1', - _sequence: 1, - age: 30 - }, - personId2: { - _uuid: 'personId2', - _sequence: 2, - age: 35 - }, - personId3: { - _uuid: 'personId3', - _sequence: 3, - age: 3 - } + personId1: { _uuid: 'personId1', _sequence: 1, age: 30 }, + personId2: { _uuid: 'personId2', _sequence: 2, age: 35 }, + personId3: { _uuid: 'personId3', _sequence: 3, age: 3 } } } }; describe('Simple direct navigation, each section in nav bar', () => { - const navigationService = createNavigationService(simpleSectionsConfig); - describe('initNavigationState',() => { + describe('initNavigationState', () => { test('should navigate to first section when no previous navigation', () => { const expectedSection = 'home'; // Navigate to the first section const nextSectionResult = navigationService.initNavigationState({ interview }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); test('should navigate to last visited section if there are previous navigations', () => { @@ -178,18 +127,13 @@ describe('Simple direct navigation, each section in nav bar', () => { householdMembers: { _startedAt: 2 }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, - { section: 'householdMembers', action: 'start' as const, ts: 2 }, + { section: 'householdMembers', action: 'start' as const, ts: 2 } ] } as any; // Navigate to last visited section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); test('should navigate to requested section, even with previous navigations that reaches later', () => { @@ -206,18 +150,13 @@ describe('Simple direct navigation, each section in nav bar', () => { _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, - { section: 'end', action: 'start' as const, ts: 3 }, + { section: 'end', action: 'start' as const, ts: 3 } ] } as any; // Navigate to the requested section - const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection}); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); test('should navigate to last possible section if requested section is not enabled yet, using default enable', () => { @@ -235,18 +174,13 @@ describe('Simple direct navigation, each section in nav bar', () => { householdMembers: { _startedAt: 2 }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, - { section: 'householdMembers', action: 'start' as const, ts: 2 }, + { section: 'householdMembers', action: 'start' as const, ts: 2 } ] } as any; // Navigate to a specific section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); // The completion conditional of the previous section should have been called expect(hhSectionCompleted).toHaveBeenCalledWith(testInterview, undefined); }); @@ -266,18 +200,13 @@ describe('Simple direct navigation, each section in nav bar', () => { householdMembers: { _startedAt: 2 }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, - { section: 'householdMembers', action: 'start' as const, ts: 2 }, + { section: 'householdMembers', action: 'start' as const, ts: 2 } ] } as any; // Navigate to a specific section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); // The completion conditional of the previous section should have been called expect(hhSectionEnabled).toHaveBeenCalledWith(testInterview, 'householdMembers', undefined); }); @@ -298,26 +227,19 @@ describe('Simple direct navigation, each section in nav bar', () => { householdMembers: { _startedAt: 2 }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, - { section: 'householdMembers', action: 'start' as const, ts: 2 }, + { section: 'householdMembers', action: 'start' as const, ts: 2 } ] } as any; // Navigate to requested section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); // The completion conditional of the previous section should have been called expect(hhSectionCompleted).toHaveBeenCalledWith(testInterview, undefined); }); - }); describe('Navigate function', () => { - test('should navigate to the next section forward', () => { const currentSection = 'home'; const expectedSection = 'householdMembers'; @@ -327,16 +249,11 @@ describe('Simple direct navigation, each section in nav bar', () => { // FIXME The type of the _sections is not quite right, this should be valid but it is not testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, - _actions: [ - { section: 'home', action: 'start' as const, ts: 1 }, - ] + _actions: [{ section: 'home', action: 'start' as const, ts: 1 }] } as any; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -344,12 +261,7 @@ describe('Simple direct navigation, each section in nav bar', () => { currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); test('should stay on page if navigating forward but section incomplete', () => { @@ -362,16 +274,11 @@ describe('Simple direct navigation, each section in nav bar', () => { // FIXME The type of the _sections is not quite right, this should be valid but it is not testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, - _actions: [ - { section: 'home', action: 'start' as const, ts: 1 }, - ] + _actions: [{ section: 'home', action: 'start' as const, ts: 1 }] } as any; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -379,12 +286,7 @@ describe('Simple direct navigation, each section in nav bar', () => { currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); test('should navigate to the next section backward', () => { @@ -399,15 +301,12 @@ describe('Simple direct navigation, each section in nav bar', () => { householdMembers: { _startedAt: 2 }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, - { section: 'householdMembers', action: 'start' as const, ts: 2 }, + { section: 'householdMembers', action: 'start' as const, ts: 2 } ] } as any; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -415,12 +314,7 @@ describe('Simple direct navigation, each section in nav bar', () => { currentSection: _cloneDeep(currentSectionData), direction: 'backward' }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); test('should navigate to the next section backward, even if current section is incomplete', () => { @@ -435,15 +329,12 @@ describe('Simple direct navigation, each section in nav bar', () => { householdMembers: { _startedAt: 2 }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, - { section: 'householdMembers', action: 'start' as const, ts: 2 }, + { section: 'householdMembers', action: 'start' as const, ts: 2 } ] } as any; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -451,12 +342,7 @@ describe('Simple direct navigation, each section in nav bar', () => { currentSection: _cloneDeep(currentSectionData), direction: 'backward' }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); // Should not verify completion is going to previous section expect(hhSectionCompleted).not.toHaveBeenCalled(); }); @@ -475,15 +361,12 @@ describe('Simple direct navigation, each section in nav bar', () => { _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, - { section: 'end', action: 'start' as const, ts: 4 }, + { section: 'end', action: 'start' as const, ts: 4 } ] } as any; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -491,12 +374,7 @@ describe('Simple direct navigation, each section in nav bar', () => { currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); test('should skip section if section is marked as such', () => { @@ -505,7 +383,7 @@ describe('Simple direct navigation, each section in nav bar', () => { // HouseholdMembers section should be skipped hhShouldSectionBeVisible.mockReturnValueOnce(false); - // Prepare the interview with section navigation history + // Prepare the interview with section navigation history const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not testInterview.response._sections = { @@ -516,15 +394,12 @@ describe('Simple direct navigation, each section in nav bar', () => { { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, { section: 'end', action: 'start' as const, ts: 4 }, - { section: 'home', action: 'start' as const, ts: 8 }, + { section: 'home', action: 'start' as const, ts: 8 } ] } as any; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -534,38 +409,26 @@ describe('Simple direct navigation, each section in nav bar', () => { }); expect(hhShouldSectionBeVisible).toHaveBeenCalledWith(testInterview, undefined); expect(hhSectionCompleted).toHaveBeenCalledWith(testInterview, undefined); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); - }) - + }); }); describe('With repeated sections per household members, skipping select in natural flow', () => { - const navigationService = createNavigationService(complexSectionsConfig); - describe('initNavigationState',() => { + describe('initNavigationState', () => { test('should navigate to first section when no previous navigation', () => { const expectedSection = 'home'; // Navigate to the first section const nextSectionResult = navigationService.initNavigationState({ interview }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); test('should navigate to last visited section if there are previous navigations, section not repeated', () => { const expectedSection = 'end'; - + // Prepare the interview with section navigation history const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -608,24 +471,19 @@ describe('With repeated sections per household members, skipping select in natur { section: 'selectPerson', iterationContext: ['personId2'], action: 'start' as const, ts: 24 }, { section: 'visitedPlaces', iterationContext: ['personId2'], action: 'start' as const, ts: 28 }, { section: 'travelBehavior', iterationContext: ['personId2'], action: 'start' as const, ts: 32 }, - { section: 'end', action: 'start' as const, ts: 50 }, + { section: 'end', action: 'start' as const, ts: 50 } ] } as any; Object.assign(testInterview.response, hhWithPersonsResponse); // Navigate to last visited section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); test('should navigate to last visited section with correctly activated object if there are previous navigations', () => { const expectedSection = 'visitedPlaces'; - + // Prepare the interview with section navigation history const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -650,11 +508,7 @@ describe('With repeated sections per household members, skipping select in natur personId1: { _startedAt: 12, _isCompleted: true }, personId2: { _startedAt: 28 } }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true, - personId1: { _startedAt: 20, _isCompleted: true } - }, + travelBehavior: { _startedAt: 20, _isCompleted: true, personId1: { _startedAt: 20, _isCompleted: true } }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, @@ -672,10 +526,7 @@ describe('With repeated sections per household members, skipping select in natur // Navigate to last visited section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: ['personId2'] - }, + targetSection: { sectionShortname: expectedSection, iterationContext: ['personId2'] }, valuesByPath: { 'response._activePersonId': 'personId2' } }); }); @@ -683,7 +534,7 @@ describe('With repeated sections per household members, skipping select in natur test('should navigate to last incomplete section for activated object if requested section in a repeated block but not enabled', () => { const requestedSection = 'travelBehavior'; const expectedSection = 'visitedPlaces'; - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -708,11 +559,7 @@ describe('With repeated sections per household members, skipping select in natur personId1: { _startedAt: 12, _isCompleted: true }, personId2: { _startedAt: 28 } }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true, - personId1: { _startedAt: 20, _isCompleted: true } - }, + travelBehavior: { _startedAt: 20, _isCompleted: true, personId1: { _startedAt: 20, _isCompleted: true } }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, @@ -731,18 +578,13 @@ describe('With repeated sections per household members, skipping select in natur // Navigate to requested section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: ['personId2'] - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: ['personId2'] } }); }); test('should navigate to last section for activated object if requested section in a repeated block but not visible', () => { const requestedSection = 'travelBehavior'; const expectedSection = 'visitedPlaces'; - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -767,11 +609,7 @@ describe('With repeated sections per household members, skipping select in natur personId1: { _startedAt: 12, _isCompleted: true }, personId2: { _startedAt: 28 } }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true, - personId1: { _startedAt: 20, _isCompleted: true } - }, + travelBehavior: { _startedAt: 20, _isCompleted: true, personId1: { _startedAt: 20, _isCompleted: true } }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, @@ -790,18 +628,13 @@ describe('With repeated sections per household members, skipping select in natur // Navigate to requested section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: ['personId2'] - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: ['personId2'] } }); }); test('should navigate to object selection section if requested section in a repeated block but no active object', () => { const requestedSection = 'travelBehavior'; const expectedSection = 'selectPerson'; - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -824,7 +657,7 @@ describe('With repeated sections per household members, skipping select in natur _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true }, - personId2: { _startedAt: 28, _isCompleted: true } + personId2: { _startedAt: 28, _isCompleted: true } }, travelBehavior: { _startedAt: 20, @@ -849,19 +682,14 @@ describe('With repeated sections per household members, skipping select in natur // Navigate to requested section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - } - }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: undefined } }); }); test('should navigate to first section of repeated block with first iteration if the main repeated block section is requested', () => { const requestedSection = 'personsTrips'; const expectedSection = 'selectPerson'; const expectedContext = ['personId1']; - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -886,11 +714,7 @@ describe('With repeated sections per household members, skipping select in natur personId1: { _startedAt: 12, _isCompleted: true }, personId2: { _startedAt: 28 } }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true, - personId1: { _startedAt: 20, _isCompleted: true } - }, + travelBehavior: { _startedAt: 20, _isCompleted: true, personId1: { _startedAt: 20, _isCompleted: true } }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, @@ -909,10 +733,7 @@ describe('With repeated sections per household members, skipping select in natur // Navigate to requested section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedContext }, valuesByPath: { 'response._activePersonId': 'personId1' } }); }); @@ -921,7 +742,7 @@ describe('With repeated sections per household members, skipping select in natur const requestedSection = 'personsTrips'; const expectedSection = 'visitedPlaces'; const expectedContext = ['personId1']; - + // Prepare the interview with section navigation history and a household with only one person const testInterview = _cloneDeep(interview); const testHhWithPersons = _cloneDeep(hhWithPersonsResponse) as any; @@ -931,21 +752,9 @@ describe('With repeated sections per household members, skipping select in natur testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, householdMembers: { _startedAt: 2, _isCompleted: true }, - personsTrips: { - _startedAt: 6, - _isCompleted: true, - personId1: { _startedAt: 6, _isCompleted: true }, - }, - visitedPlaces: { - _startedAt: 12, - _isCompleted: true, - personId1: { _startedAt: 12, _isCompleted: true }, - }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true, - personId1: { _startedAt: 20, _isCompleted: true } - }, + personsTrips: { _startedAt: 6, _isCompleted: true, personId1: { _startedAt: 6, _isCompleted: true } }, + visitedPlaces: { _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true } }, + travelBehavior: { _startedAt: 20, _isCompleted: true, personId1: { _startedAt: 20, _isCompleted: true } }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, @@ -959,17 +768,13 @@ describe('With repeated sections per household members, skipping select in natur // Navigate to requested section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedContext }, valuesByPath: { 'response._activePersonId': 'personId1' } }); }); }); describe('Navigate function', () => { - test('should skip selection section when coming from previous section the first time', () => { const currentSection = 'householdMembers'; const expectedSection = 'visitedPlaces'; @@ -988,10 +793,7 @@ describe('With repeated sections per household members, skipping select in natur Object.assign(testInterview.response, hhWithPersonsResponse); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -1000,10 +802,7 @@ describe('With repeated sections per household members, skipping select in natur direction: 'forward' }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: ['personId1'] - }, + targetSection: { sectionShortname: expectedSection, iterationContext: ['personId1'] }, valuesByPath: { 'response._activePersonId': 'personId1' } }); }); @@ -1018,26 +817,10 @@ describe('With repeated sections per household members, skipping select in natur testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, householdMembers: { _startedAt: 2, _isCompleted: true }, - personsTrips: { - _startedAt: 6, - _isCompleted: true, - personId1: { _startedAt: 6, _isCompleted: true } - }, - selectPerson: { - _startedAt: 9, - _isCompleted: true, - personId1: { _startedAt: 9, _isCompleted: true } - }, - visitedPlaces: { - _startedAt: 12, - _isCompleted: true, - personId1: { _startedAt: 12, _isCompleted: true } - }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true, - personId1: { _startedAt: 20, _isCompleted: true } - }, + personsTrips: { _startedAt: 6, _isCompleted: true, personId1: { _startedAt: 6, _isCompleted: true } }, + selectPerson: { _startedAt: 9, _isCompleted: true, personId1: { _startedAt: 9, _isCompleted: true } }, + visitedPlaces: { _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true } }, + travelBehavior: { _startedAt: 20, _isCompleted: true, personId1: { _startedAt: 20, _isCompleted: true } }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, @@ -1050,10 +833,7 @@ describe('With repeated sections per household members, skipping select in natur Object.assign(testInterview.response, hhWithPersonsResponse); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: ['personId1'] - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: ['personId1'] }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -1062,15 +842,12 @@ describe('With repeated sections per household members, skipping select in natur direction: 'forward' }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: ['personId2'] - }, + targetSection: { sectionShortname: expectedSection, iterationContext: ['personId2'] }, valuesByPath: { 'response._activePersonId': 'personId2' } }); }); - test('should show selection section when coming from previous, but sections already visited', () => { + test('should show selection section when coming from previous, but sections already visited', () => { const currentSection = 'householdMembers'; const expectedSection = 'selectPerson'; @@ -1080,25 +857,10 @@ describe('With repeated sections per household members, skipping select in natur testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, householdMembers: { _startedAt: 2, _isCompleted: true }, - personsTrips: { - _startedAt: 6, - _isCompleted: true, - personId1: { _startedAt: 6, _isCompleted: true } - }, - selectPerson: { - _startedAt: 9, - _isCompleted: true, - personId1: { _startedAt: 9, _isCompleted: true } - }, - visitedPlaces: { - _startedAt: 12, - _isCompleted: true, - personId1: { _startedAt: 12, _isCompleted: true } - }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true - }, + personsTrips: { _startedAt: 6, _isCompleted: true, personId1: { _startedAt: 6, _isCompleted: true } }, + selectPerson: { _startedAt: 9, _isCompleted: true, personId1: { _startedAt: 9, _isCompleted: true } }, + visitedPlaces: { _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true } }, + travelBehavior: { _startedAt: 20, _isCompleted: true }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, @@ -1111,10 +873,7 @@ describe('With repeated sections per household members, skipping select in natur Object.assign(testInterview.response, hhWithPersonsResponse); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -1123,10 +882,7 @@ describe('With repeated sections per household members, skipping select in natur direction: 'forward' }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: ['personId1'] - }, + targetSection: { sectionShortname: expectedSection, iterationContext: ['personId1'] }, valuesByPath: { 'response._activePersonId': 'personId1' } }); }); @@ -1134,7 +890,7 @@ describe('With repeated sections per household members, skipping select in natur test('should navigate to the "end" section when all repeated blocks completed', () => { const currentSection = 'travelBehavior'; const expectedSection = 'end'; - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -1157,7 +913,7 @@ describe('With repeated sections per household members, skipping select in natur _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true }, - personId2: { _startedAt: 28, _isCompleted: true } + personId2: { _startedAt: 28, _isCompleted: true } }, travelBehavior: { _startedAt: 20, @@ -1181,10 +937,7 @@ describe('With repeated sections per household members, skipping select in natur Object.assign(testInterview.response, hhWithPersonsResponse); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: ['personId2'] - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: ['personId2'] }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -1193,10 +946,7 @@ describe('With repeated sections per household members, skipping select in natur direction: 'forward' }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - }, + targetSection: { sectionShortname: expectedSection, iterationContext: undefined }, valuesByPath: { 'response._activePersonId': undefined } }); }); @@ -1204,7 +954,7 @@ describe('With repeated sections per household members, skipping select in natur test('should show selection section when entering repeated block, even if interview is completed', () => { const currentSection = 'householdMembers'; const expectedSection = 'selectPerson'; - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -1227,7 +977,7 @@ describe('With repeated sections per household members, skipping select in natur _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true }, - personId2: { _startedAt: 28, _isCompleted: true } + personId2: { _startedAt: 28, _isCompleted: true } }, travelBehavior: { _startedAt: 20, @@ -1253,10 +1003,7 @@ describe('With repeated sections per household members, skipping select in natur Object.assign(testInterview.response, hhWithPersonsResponse); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -1265,10 +1012,7 @@ describe('With repeated sections per household members, skipping select in natur direction: 'forward' }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: ['personId1'] - }, + targetSection: { sectionShortname: expectedSection, iterationContext: ['personId1'] }, valuesByPath: { 'response._activePersonId': 'personId1' } }); }); @@ -1278,7 +1022,7 @@ describe('With repeated sections per household members, skipping select in natur const currentIterationContext = ['personId2']; const expectedSection = 'selectPerson'; const expectedIterationContext = ['personId1']; - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -1301,7 +1045,7 @@ describe('With repeated sections per household members, skipping select in natur _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true }, - personId2: { _startedAt: 28, _isCompleted: true } + personId2: { _startedAt: 28, _isCompleted: true } }, travelBehavior: { _startedAt: 20, @@ -1327,10 +1071,7 @@ describe('With repeated sections per household members, skipping select in natur Object.assign(testInterview.response, hhWithPersonsResponse); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -1339,10 +1080,7 @@ describe('With repeated sections per household members, skipping select in natur direction: 'backward' }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': 'personId1' } }); }); @@ -1351,7 +1089,7 @@ describe('With repeated sections per household members, skipping select in natur const currentSection = 'selectPerson'; const currentIterationContext = ['personId1']; const expectedSection = 'householdMembers'; - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -1374,7 +1112,7 @@ describe('With repeated sections per household members, skipping select in natur _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true }, - personId2: { _startedAt: 28, _isCompleted: true } + personId2: { _startedAt: 28, _isCompleted: true } }, travelBehavior: { _startedAt: 20, @@ -1400,10 +1138,7 @@ describe('With repeated sections per household members, skipping select in natur Object.assign(testInterview.response, hhWithPersonsResponse); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -1412,45 +1147,27 @@ describe('With repeated sections per household members, skipping select in natur direction: 'backward' }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - }, + targetSection: { sectionShortname: expectedSection, iterationContext: undefined }, valuesByPath: { 'response._activePersonId': undefined } }); }); - test('should go to next section in block with same iteration if not skipped', () => { + test('should go to next section in block with same iteration if not skipped', () => { const currentSection = 'visitedPlaces'; const currentIterationContext = ['personId1']; const expectedSection = 'travelBehavior'; const expectedIterationContext = ['personId1']; - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, householdMembers: { _startedAt: 2, _isCompleted: true }, - personsTrips: { - _startedAt: 6, - _isCompleted: true, - personId1: { _startedAt: 6, _isCompleted: true } - }, - selectPerson: { - _startedAt: 9, - _isCompleted: true, - personId1: { _startedAt: 9, _isCompleted: true } - }, - visitedPlaces: { - _startedAt: 12, - _isCompleted: true, - personId1: { _startedAt: 12, _isCompleted: true } - }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true - }, + personsTrips: { _startedAt: 6, _isCompleted: true, personId1: { _startedAt: 6, _isCompleted: true } }, + selectPerson: { _startedAt: 9, _isCompleted: true, personId1: { _startedAt: 9, _isCompleted: true } }, + visitedPlaces: { _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true } }, + travelBehavior: { _startedAt: 20, _isCompleted: true }, end: { _startedAt: 40, _isCompleted: true }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, @@ -1464,10 +1181,7 @@ describe('With repeated sections per household members, skipping select in natur testInterview.response._activePersonId = 'personId1'; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -1476,47 +1190,29 @@ describe('With repeated sections per household members, skipping select in natur direction: 'forward' }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: undefined }); expect(tbIsSectionVisible).toHaveBeenCalledWith(testInterview, ['personId1']); }); - test('should go to next iteration if last section of block is skipped in natural flow', () => { + test('should go to next iteration if last section of block is skipped in natural flow', () => { const currentSection = 'visitedPlaces'; const currentIterationContext = ['personId1']; const expectedSection = 'visitedPlaces'; const expectedIterationContext = ['personId2']; tbIsSectionVisible.mockReturnValueOnce(false); - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, householdMembers: { _startedAt: 2, _isCompleted: true }, - personsTrips: { - _startedAt: 6, - _isCompleted: true, - personId1: { _startedAt: 6, _isCompleted: true } - }, - selectPerson: { - _startedAt: 9, - _isCompleted: true, - personId1: { _startedAt: 9, _isCompleted: true } - }, - visitedPlaces: { - _startedAt: 12, - _isCompleted: true, - personId1: { _startedAt: 12, _isCompleted: true } - }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true - }, + personsTrips: { _startedAt: 6, _isCompleted: true, personId1: { _startedAt: 6, _isCompleted: true } }, + selectPerson: { _startedAt: 9, _isCompleted: true, personId1: { _startedAt: 9, _isCompleted: true } }, + visitedPlaces: { _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true } }, + travelBehavior: { _startedAt: 20, _isCompleted: true }, end: { _startedAt: 40, _isCompleted: true }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, @@ -1529,10 +1225,7 @@ describe('With repeated sections per household members, skipping select in natur Object.assign(testInterview.response, hhWithPersonsResponse); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -1541,10 +1234,7 @@ describe('With repeated sections per household members, skipping select in natur direction: 'forward' }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': expectedIterationContext[0] } }); expect(tbIsSectionVisible).toHaveBeenCalledWith(testInterview, ['personId1']); @@ -1556,8 +1246,8 @@ describe('With repeated sections per household members, skipping select in natur { sectionShortname: 'home' }, { sectionShortname: 'householdMembers' }, { sectionShortname: 'visitedPlaces', iterationContext: ['personId1'] }, - { sectionShortname: 'travelBehavior', iterationContext: ['personId1']}, - { sectionShortname: 'visitedPlaces', iterationContext: ['personId2']}, + { sectionShortname: 'travelBehavior', iterationContext: ['personId1'] }, + { sectionShortname: 'visitedPlaces', iterationContext: ['personId2'] }, { sectionShortname: 'end' } ]; // Skip travelBehavior at second call @@ -1567,29 +1257,22 @@ describe('With repeated sections per household members, skipping select in natur // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // Initialize sections, they will be filled as needed, prefill responses - testInterview.response._sections = { - _actions: [] - } as any; + testInterview.response._sections = { _actions: [] } as any; Object.assign(testInterview.response, hhWithPersonsResponse); const initialNavigation = navigationService.initNavigationState({ interview: testInterview }); - expect(initialNavigation).toEqual({ - targetSection: expectedSectionSequence[0] - }); + expect(initialNavigation).toEqual({ targetSection: expectedSectionSequence[0] }); // Prepare the previous navigation state let currentSection = initialNavigation.targetSection; for (let i = 1; i < expectedSectionSequence.length; i++) { // Add section completion data to the interview - (testInterview.response._sections as any)[currentSection.sectionShortname] = { - _startedAt: i * 10, - _isCompleted: true, - }; - if (currentSection.iterationContext) { + (testInterview.response._sections as any)[currentSection.sectionShortname] = { _startedAt: i * 10, _isCompleted: true }; + if (currentSection.iterationContext) { (testInterview.response._sections as any)[currentSection.sectionShortname][currentSection.iterationContext[0]] = { _startedAt: i * 10, - _isCompleted: true, + _isCompleted: true }; } (testInterview.response._sections as any)._actions.push({ @@ -1609,7 +1292,10 @@ describe('With repeated sections per household members, skipping select in natur }); expect(nextSectionResult).toEqual({ targetSection: expectedSection, - valuesByPath: expectedSection.iterationContext === undefined && currentSection.iterationContext === undefined ? undefined : { 'response._activePersonId': expectedSection.iterationContext ? expectedSection.iterationContext[0] : undefined } + valuesByPath: + expectedSection.iterationContext === undefined && currentSection.iterationContext === undefined + ? undefined + : { 'response._activePersonId': expectedSection.iterationContext ? expectedSection.iterationContext[0] : undefined } }); currentSection = nextSectionResult.targetSection; } @@ -1619,7 +1305,7 @@ describe('With repeated sections per household members, skipping select in natur const currentSection = 'householdMembers'; const expectedSection = 'end'; tripsIsEnabled.mockReturnValueOnce(false); - + // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); // FIXME The type of the _sections is not quite right, this should be valid but it is not @@ -1634,9 +1320,7 @@ describe('With repeated sections per household members, skipping select in natur Object.assign(testInterview.response, hhWithPersonsResponse); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection - } + const currentSectionData = { sectionShortname: currentSection }; // Navigate to specific section const nextSectionResult = navigationService.navigate({ @@ -1645,10 +1329,7 @@ describe('With repeated sections per household members, skipping select in natur direction: 'forward' }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - }, + targetSection: { sectionShortname: expectedSection, iterationContext: undefined }, valuesByPath: undefined }); }); @@ -1682,7 +1363,7 @@ describe('With repeated sections per household members, skipping select in natur _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true }, - personId2: { _startedAt: 28, _isCompleted: true } + personId2: { _startedAt: 28, _isCompleted: true } }, travelBehavior: { _startedAt: 20, @@ -1708,27 +1389,20 @@ describe('With repeated sections per household members, skipping select in natur Object.assign(testInterview.response, hhWithPersonsResponse); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); - expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - } + const nextSectionResult = navigationService.navigate({ + interview: testInterview, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' }); + expect(nextSectionResult).toEqual({ targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext } }); }); - }); - }); describe('navigate function, further use cases', () => { - describe('test iteration context order', () => { // Prepare the interview with section navigation history and a household const testInterview = _cloneDeep(interview); @@ -1755,19 +1429,17 @@ describe('navigate function, further use cases', () => { const navigationService = createNavigationService(sectionConfig); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward'}); + const nextSectionResult = navigationService.navigate({ + interview: testInterview, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(mockShuffle).toHaveBeenCalledWith(['personId1', 'personId2']); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': expectedIterationContext[0], 'response._RandomSequence': ['personId2', 'personId1'] } }); }); @@ -1783,23 +1455,24 @@ describe('navigate function, further use cases', () => { (sectionConfig['personsTrips'] as SectionConfigWithDefaultsBlock).repeatedBlock.order = 'random'; (sectionConfig['personsTrips'] as SectionConfigWithDefaultsBlock).repeatedBlock.pathPrefix = pathPrefix; mockShuffle.mockReturnValueOnce(['personId2', 'personId1']); - + const navigationService = createNavigationService(sectionConfig); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); + const nextSectionResult = navigationService.navigate({ + interview: testInterview, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, - valuesByPath: { 'response._activePersonId': expectedIterationContext[1], 'response._personRandomSequence': ['personId2', 'personId1'] } + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, + valuesByPath: { + 'response._activePersonId': expectedIterationContext[1], + 'response._personRandomSequence': ['personId2', 'personId1'] + } }); expect(mockShuffle).toHaveBeenCalledWith(['personId1', 'personId2']); }); @@ -1818,19 +1491,17 @@ describe('navigate function, further use cases', () => { testInterviewWithRandomSequence.response._RandomSequence = ['personId2', 'personId1']; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterviewWithRandomSequence, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); + const nextSectionResult = navigationService.navigate({ + interview: testInterviewWithRandomSequence, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(mockShuffle).not.toHaveBeenCalled(); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': expectedIterationContext[0] } }); }); @@ -1851,19 +1522,17 @@ describe('navigate function, further use cases', () => { testInterviewWithRandomSequence.response._RandomSequence = ['personId2', 'personId3']; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterviewWithRandomSequence, currentSection: _cloneDeep(currentSectionData), direction: 'forward'}); + const nextSectionResult = navigationService.navigate({ + interview: testInterviewWithRandomSequence, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(mockShuffle).toHaveBeenCalledWith(['personId1', 'personId2']); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': expectedIterationContext[0], 'response._RandomSequence': ['personId2', 'personId1'] } }); }); @@ -1883,19 +1552,17 @@ describe('navigate function, further use cases', () => { testInterviewWithRandomSequence.response._RandomSequence = ['personId2', 'personId3', 'personId1']; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterviewWithRandomSequence, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); + const nextSectionResult = navigationService.navigate({ + interview: testInterviewWithRandomSequence, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(mockShuffle).toHaveBeenCalledWith(['personId1', 'personId2']); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': expectedIterationContext[0], 'response._RandomSequence': ['personId2', 'personId1'] } }); }); @@ -1913,23 +1580,21 @@ describe('navigate function, further use cases', () => { // Set random order const testInterviewWithRandomSequence = _cloneDeep(testInterview); testInterviewWithRandomSequence.response._personRandomSequence = ['personId2', 'personId1']; - + const navigationService = createNavigationService(sectionConfig); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterviewWithRandomSequence, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); + const nextSectionResult = navigationService.navigate({ + interview: testInterviewWithRandomSequence, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(mockShuffle).not.toHaveBeenCalled(); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': expectedIterationContext[1] } }); }); @@ -1950,19 +1615,17 @@ describe('navigate function, further use cases', () => { testInterviewWithRandomSequence.response._RandomSequence = ['personId2', 'personId1']; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - }; + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterviewWithRandomSequence, currentSection: _cloneDeep(currentSectionData), direction: 'forward'}); + const nextSectionResult = navigationService.navigate({ + interview: testInterviewWithRandomSequence, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(mockShuffle).not.toHaveBeenCalled(); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': undefined } }); }); @@ -1975,22 +1638,20 @@ describe('navigate function, further use cases', () => { // Set the order of the iteration contexts to sequential and make sure mockShuffle is not called const sectionConfig = _cloneDeep(complexSectionsConfig); (sectionConfig['personsTrips'] as SectionConfigWithDefaultsBlock).repeatedBlock.order = 'sequential'; - + const navigationService = createNavigationService(sectionConfig); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); + const nextSectionResult = navigationService.navigate({ + interview: testInterview, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': expectedIterationContext[0] } }); expect(mockShuffle).not.toHaveBeenCalled(); @@ -2010,25 +1671,10 @@ describe('navigate function, further use cases', () => { testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, householdMembers: { _startedAt: 2, _isCompleted: true }, - personsTrips: { - _startedAt: 6, - _isCompleted: true, - personId1: { _startedAt: 6, _isCompleted: true } - }, - selectPerson: { - _startedAt: 9, - _isCompleted: true, - personId1: { _startedAt: 9, _isCompleted: true } - }, - visitedPlaces: { - _startedAt: 12, - _isCompleted: true, - personId1: { _startedAt: 12, _isCompleted: true } - }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true - }, + personsTrips: { _startedAt: 6, _isCompleted: true, personId1: { _startedAt: 6, _isCompleted: true } }, + selectPerson: { _startedAt: 9, _isCompleted: true, personId1: { _startedAt: 9, _isCompleted: true } }, + visitedPlaces: { _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true } }, + travelBehavior: { _startedAt: 20, _isCompleted: true }, end: { _startedAt: 40, _isCompleted: true }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, @@ -2042,10 +1688,7 @@ describe('navigate function, further use cases', () => { testInterview.response._activePersonId = 'personId1'; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Prepare section configuration with a path prefix and create navigation service const sectionConfig = _cloneDeep(complexSectionsConfig); @@ -2054,12 +1697,13 @@ describe('navigate function, further use cases', () => { const navigationService = createNavigationService(sectionConfig); // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); + const nextSectionResult = navigationService.navigate({ + interview: testInterview, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: undefined }); expect(tbIsSectionVisible).toHaveBeenCalledWith(testInterview, ['person', 'personId1']); @@ -2077,35 +1721,21 @@ describe('navigate function, further use cases', () => { testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, householdMembers: { _startedAt: 2, _isCompleted: true }, - personsTrips: { - _startedAt: 6, - _isCompleted: true, - personId1: { _startedAt: 6, _isCompleted: true } - }, - selectPerson: { - _startedAt: 9, - _isCompleted: true, - personId1: { _startedAt: 9, _isCompleted: true } - }, - visitedPlaces: { - _startedAt: 12, - _isCompleted: true, - }, + personsTrips: { _startedAt: 6, _isCompleted: true, personId1: { _startedAt: 6, _isCompleted: true } }, + selectPerson: { _startedAt: 9, _isCompleted: true, personId1: { _startedAt: 9, _isCompleted: true } }, + visitedPlaces: { _startedAt: 12, _isCompleted: true }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, { section: 'householdMembers', action: 'start' as const, ts: 2 }, { section: 'personsTrips', iterationContext: ['personId1'], action: 'start' as const, ts: 6 }, - { section: 'selectPerson', iterationContext: ['personId1'], action: 'start' as const, ts: 9 }, + { section: 'selectPerson', iterationContext: ['personId1'], action: 'start' as const, ts: 9 } ] } as any; Object.assign(testInterview.response, hhWithPersonsResponse); testInterview.response._activePersonId = 'personId1'; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Prepare section configuration with a path prefix and create navigation service const sectionConfig = _cloneDeep(complexSectionsConfig); @@ -2114,12 +1744,13 @@ describe('navigate function, further use cases', () => { const navigationService = createNavigationService(sectionConfig); // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); + const nextSectionResult = navigationService.navigate({ + interview: testInterview, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: undefined }); }); @@ -2136,7 +1767,7 @@ describe('navigate function, further use cases', () => { beforeEach(() => { mockIsIterationValid.mockReset(); mockIsIterationValid.mockReturnValue(true); - }) + }); test('Last iteration, all iterations valid', () => { const currentSection = 'travelBehavior'; @@ -2191,18 +1822,16 @@ describe('navigate function, further use cases', () => { testInterview.response._activePersonId = 'personId2'; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); + const nextSectionResult = navigationService.navigate({ + interview: testInterview, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': undefined } }); expect(mockIsIterationValid).toHaveBeenCalledWith(testInterview, ['personId1']); @@ -2264,18 +1893,16 @@ describe('navigate function, further use cases', () => { testInterview.response._activePersonId = 'personId2'; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); + const nextSectionResult = navigationService.navigate({ + interview: testInterview, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': expectedIterationContext[0] } }); expect(mockIsIterationValid).toHaveBeenCalledWith(testInterview, ['personId1']); @@ -2293,26 +1920,10 @@ describe('navigate function, further use cases', () => { testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, householdMembers: { _startedAt: 2, _isCompleted: true }, - personsTrips: { - _startedAt: 6, - _isCompleted: true, - personId1: { _startedAt: 6, _isCompleted: true } - }, - selectPerson: { - _startedAt: 9, - _isCompleted: true, - personId1: { _startedAt: 9, _isCompleted: true } - }, - visitedPlaces: { - _startedAt: 12, - _isCompleted: true, - personId1: { _startedAt: 12, _isCompleted: true } - }, - travelBehavior: { - _startedAt: 20, - _isCompleted: true, - personId1: { _startedAt: 15, _isCompleted: true } - }, + personsTrips: { _startedAt: 6, _isCompleted: true, personId1: { _startedAt: 6, _isCompleted: true } }, + selectPerson: { _startedAt: 9, _isCompleted: true, personId1: { _startedAt: 9, _isCompleted: true } }, + visitedPlaces: { _startedAt: 12, _isCompleted: true, personId1: { _startedAt: 12, _isCompleted: true } }, + travelBehavior: { _startedAt: 20, _isCompleted: true, personId1: { _startedAt: 15, _isCompleted: true } }, end: { _startedAt: 40, _isCompleted: true }, _actions: [ { section: 'home', action: 'start' as const, ts: 1 }, @@ -2327,18 +1938,16 @@ describe('navigate function, further use cases', () => { testInterview.response._activePersonId = 'personId1'; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Navigate to specific section - const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }); + const nextSectionResult = navigationService.navigate({ + interview: testInterview, + currentSection: _cloneDeep(currentSectionData), + direction: 'forward' + }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: expectedIterationContext - }, + targetSection: { sectionShortname: expectedSection, iterationContext: expectedIterationContext }, valuesByPath: { 'response._activePersonId': expectedIterationContext[0] } }); expect(mockIsIterationValid).not.toHaveBeenCalled(); @@ -2357,7 +1966,7 @@ describe('navigate function, further use cases', () => { beforeEach(() => { jest.clearAllMocks(); - }) + }); test('Entering household section through navigation from nowhere', () => { // Prepare expected sections and on enter values by path @@ -2380,10 +1989,7 @@ describe('navigate function, further use cases', () => { // Navigate to last visited section const nextSectionResult = navigationService.initNavigationState({ interview: testInterview }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - }, + targetSection: { sectionShortname: expectedSection, iterationContext: undefined }, valuesByPath: onEnterValues }); expect(mockOnEnterHh).toHaveBeenCalledWith(testInterview, undefined); @@ -2405,24 +2011,16 @@ describe('navigate function, further use cases', () => { testInterview.response._sections = { home: { _startedAt: 1, _isCompleted: true }, householdMembers: { _startedAt: 2 }, - _actions: [ - { section: 'home', action: 'start' as const, ts: 1 } - ] + _actions: [{ section: 'home', action: 'start' as const, ts: 1 }] } as any; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to next section const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: currentSectionData }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - }, + targetSection: { sectionShortname: expectedSection, iterationContext: undefined }, valuesByPath: Object.assign({}, onEnterValues, onExitValues) }); expect(mockOnEnterHh).toHaveBeenCalledWith(testInterview, undefined); @@ -2449,18 +2047,16 @@ describe('navigate function, further use cases', () => { } as any; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to next section - const nextSectionResult = navigationService.initNavigationState({ interview: testInterview, requestedSection: 'end', currentSection: currentSectionData }); + const nextSectionResult = navigationService.initNavigationState({ + interview: testInterview, + requestedSection: 'end', + currentSection: currentSectionData + }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - }, + targetSection: { sectionShortname: expectedSection, iterationContext: undefined }, valuesByPath: Object.assign({}, onExitValues) }); expect(mockOnEnterHh).not.toHaveBeenCalled(); @@ -2487,25 +2083,18 @@ describe('navigate function, further use cases', () => { } as any; // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: undefined - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: undefined }; // Navigate to next section const nextSectionResult = navigationService.navigate({ interview: testInterview, currentSection: currentSectionData }); expect(nextSectionResult).toEqual({ - targetSection: { - sectionShortname: expectedSection, - iterationContext: undefined - }, + targetSection: { sectionShortname: expectedSection, iterationContext: undefined }, valuesByPath: undefined }); expect(mockOnEnterHh).not.toHaveBeenCalled(); expect(mockOnExitHome).not.toHaveBeenCalled(); }); }); - }); describe('Exceptions in sectionConfig\'s functions', () => { @@ -2524,11 +2113,15 @@ describe('Exceptions in sectionConfig\'s functions', () => { // Set the `onSectionEntry` to throw an error const sectionConfig = _cloneDeep(simpleSectionsConfig); - sectionConfig.householdMembers.onSectionEntry = () => { throw new Error('Test error'); } + sectionConfig.householdMembers.onSectionEntry = () => { + throw new Error('Test error'); + }; const navigationService = createNavigationService(sectionConfig); // Navigate to last visited section - expect(() => navigationService.initNavigationState({ interview: testInterview })).toThrow('NavigationService: Error evaluating onSectionEntry for section householdMembers: Error: Test error'); + expect(() => navigationService.initNavigationState({ interview: testInterview })).toThrow( + 'NavigationService: Error evaluating onSectionEntry for section householdMembers: Error: Test error' + ); }); test('onSectionExit throws an error', () => { @@ -2546,17 +2139,18 @@ describe('Exceptions in sectionConfig\'s functions', () => { // Set the `onSectionExit` to throw an error const sectionConfig = _cloneDeep(simpleSectionsConfig); - sectionConfig.home.onSectionExit = () => { throw new Error('Test error'); } + sectionConfig.home.onSectionExit = () => { + throw new Error('Test error'); + }; const navigationService = createNavigationService(sectionConfig); // Prepare the previous navigation state - const currentSectionData = { - sectionShortname: 'home', - iterationContext: undefined - } + const currentSectionData = { sectionShortname: 'home', iterationContext: undefined }; // Navigate to next section - expect(() => navigationService.navigate({ interview: testInterview, currentSection: currentSectionData })).toThrow('NavigationService: Error evaluating onSectionExit for section home: Error: Test error'); + expect(() => navigationService.navigate({ interview: testInterview, currentSection: currentSectionData })).toThrow( + 'NavigationService: Error evaluating onSectionExit for section home: Error: Test error' + ); }); test('isSectionVisible throws an error', () => { @@ -2574,11 +2168,15 @@ describe('Exceptions in sectionConfig\'s functions', () => { // Set the `isSectionVisible` to throw an error const sectionConfig = _cloneDeep(simpleSectionsConfig); - sectionConfig.householdMembers.isSectionVisible = () => { throw new Error('Test error'); } + sectionConfig.householdMembers.isSectionVisible = () => { + throw new Error('Test error'); + }; const navigationService = createNavigationService(sectionConfig); // Navigate to last visited section - expect(() => navigationService.initNavigationState({ interview: testInterview })).toThrow('NavigationService: Error evaluating isSectionVisible for section householdMembers: Error: Test error'); + expect(() => navigationService.initNavigationState({ interview: testInterview })).toThrow( + 'NavigationService: Error evaluating isSectionVisible for section householdMembers: Error: Test error' + ); }); test('isSectionCompleted throws an error', () => { @@ -2596,11 +2194,15 @@ describe('Exceptions in sectionConfig\'s functions', () => { // Set the `isSectionCompleted` to throw an error const sectionConfig = _cloneDeep(simpleSectionsConfig); - sectionConfig.home.isSectionCompleted = () => { throw new Error('Test error'); } + sectionConfig.home.isSectionCompleted = () => { + throw new Error('Test error'); + }; const navigationService = createNavigationService(sectionConfig); // Navigate to last visited section - expect(() => navigationService.navigate({ interview: testInterview, currentSection: { sectionShortname: 'home' } })).toThrow('NavigationService: Error evaluating isSectionCompleted for section home: Error: Test error'); + expect(() => navigationService.navigate({ interview: testInterview, currentSection: { sectionShortname: 'home' } })).toThrow( + 'NavigationService: Error evaluating isSectionCompleted for section home: Error: Test error' + ); }); test('enableConditional throws an error', () => { @@ -2618,11 +2220,15 @@ describe('Exceptions in sectionConfig\'s functions', () => { // Set the `enableConditional` to throw an error const sectionConfig = _cloneDeep(simpleSectionsConfig); - sectionConfig.householdMembers.enableConditional = () => { throw new Error('Test error'); } + sectionConfig.householdMembers.enableConditional = () => { + throw new Error('Test error'); + }; const navigationService = createNavigationService(sectionConfig); // Navigate to last visited section - expect(() => navigationService.initNavigationState({ interview: testInterview })).toThrow('NavigationService: Error evaluating enableConditional for section householdMembers: Error: Test error'); + expect(() => navigationService.initNavigationState({ interview: testInterview })).toThrow( + 'NavigationService: Error evaluating enableConditional for section householdMembers: Error: Test error' + ); }); test('isIterationValid throws an error', () => { @@ -2674,19 +2280,20 @@ describe('Exceptions in sectionConfig\'s functions', () => { // Set the `isIterationValid` function to throw an error const sectionConfig = _cloneDeep(complexSectionsConfig); - (sectionConfig['personsTrips'] as SectionConfigWithDefaultsBlock).repeatedBlock.isIterationValid = () => { throw new Error('Test error'); } - + (sectionConfig['personsTrips'] as SectionConfigWithDefaultsBlock).repeatedBlock.isIterationValid = () => { + throw new Error('Test error'); + }; + const navigationService = createNavigationService(sectionConfig); // Prepare the previous navigation state const currentSection = 'travelBehavior'; const currentIterationContext = ['personId2']; - const currentSectionData = { - sectionShortname: currentSection, - iterationContext: currentIterationContext - } + const currentSectionData = { sectionShortname: currentSection, iterationContext: currentIterationContext }; // Navigate to specific section - expect(() => navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward' })).toThrow('NavigationService: Error evaluating isIterationValid for section personsTrips with iteration personId1: Error: Test error'); + expect(() => + navigationService.navigate({ interview: testInterview, currentSection: _cloneDeep(currentSectionData), direction: 'forward' }) + ).toThrow('NavigationService: Error evaluating isIterationValid for section personsTrips with iteration personId1: Error: Test error'); }); }); diff --git a/packages/evolution-common/src/services/questionnaire/sections/__tests__/navigationHelpers.test.ts b/packages/evolution-common/src/services/questionnaire/sections/__tests__/navigationHelpers.test.ts index 515ca05b6..04aa55c07 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/__tests__/navigationHelpers.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/__tests__/navigationHelpers.test.ts @@ -20,7 +20,7 @@ describe('getLastVisitedSection', () => { } } } as unknown as UserRuntimeInterviewAttributes; - + expect(getLastVisitedSection({ interview })).toEqual({ sectionShortname: 'section2', iterationContext: undefined }); }); @@ -30,212 +30,117 @@ describe('getLastVisitedSection', () => { _sections: { _actions: [ { section: 'section1', action: 'viewed', ts: 1 }, - { section: 'section2', iterationContext: [ 'person', 'uuid' ], action: 'viewed', ts: 3 } + { section: 'section2', iterationContext: ['person', 'uuid'], action: 'viewed', ts: 3 } ] } } } as unknown as UserRuntimeInterviewAttributes; - - expect(getLastVisitedSection({ interview })).toEqual({ sectionShortname: 'section2', iterationContext: [ 'person', 'uuid' ] }); + + expect(getLastVisitedSection({ interview })).toEqual({ sectionShortname: 'section2', iterationContext: ['person', 'uuid'] }); }); it('should return undefined when there are no section actions', () => { - const interview = { - response: { - _sections: { - _actions: [] - } - } - } as unknown as UserRuntimeInterviewAttributes; - + const interview = { response: { _sections: { _actions: [] } } } as unknown as UserRuntimeInterviewAttributes; + expect(getLastVisitedSection({ interview })).toBeUndefined(); }); it('should return undefined when _actions does not exist', () => { - const interview = { - response: { - _sections: {} - } - } as unknown as UserRuntimeInterviewAttributes; - + const interview = { response: { _sections: {} } } as unknown as UserRuntimeInterviewAttributes; + expect(getLastVisitedSection({ interview })).toBeUndefined(); }); it('should return undefined when _sections does not exist', () => { - const interview = { - response: {} - } as unknown as UserRuntimeInterviewAttributes; - + const interview = { response: {} } as unknown as UserRuntimeInterviewAttributes; + expect(getLastVisitedSection({ interview })).toBeUndefined(); }); }); describe('isSectionCompleted', () => { it('should return true when a section is marked as completed', () => { - const interview = { - response: { - _sections: { - 'section1': { - _isCompleted: true - } - } - } - } as unknown as UserRuntimeInterviewAttributes; - + const interview = { response: { _sections: { section1: { _isCompleted: true } } } } as unknown as UserRuntimeInterviewAttributes; + expect(isSectionCompleted({ interview, sectionName: 'section1' })).toBe(true); }); it('should return false when a section is not marked as completed', () => { - const interview = { - response: { - _sections: { - 'section1': { - _isCompleted: false - } - } - } - } as unknown as UserRuntimeInterviewAttributes; - + const interview = { response: { _sections: { section1: { _isCompleted: false } } } } as unknown as UserRuntimeInterviewAttributes; + expect(isSectionCompleted({ interview, sectionName: 'section1' })).toBe(false); }); it('should return true when a section with iteration context is marked as completed', () => { const interview = { response: { - _sections: { - 'section1': { - _isCompleted: true, - 'person/uuid': { - _isCompleted: true - }, - 'person/uuid2': { - _isCompleted: false - } - } - } + _sections: { section1: { _isCompleted: true, 'person/uuid': { _isCompleted: true }, 'person/uuid2': { _isCompleted: false } } } } } as unknown as UserRuntimeInterviewAttributes; - + expect(isSectionCompleted({ interview, sectionName: 'section1', iterationContext: ['person', 'uuid'] })).toBe(true); }); it('should return false when a section with iteration context is not marked as completed', () => { const interview = { response: { - _sections: { - 'section1': { - _isCompleted: false, - 'person/uuid': { - _isCompleted: true - }, - 'person/uuid2': { - _isCompleted: false - } - } - } + _sections: { section1: { _isCompleted: false, 'person/uuid': { _isCompleted: true }, 'person/uuid2': { _isCompleted: false } } } } } as unknown as UserRuntimeInterviewAttributes; - + expect(isSectionCompleted({ interview, sectionName: 'section1', iterationContext: ['person', 'uuid2'] })).toBe(false); }); it('should return false when _isCompleted is undefined', () => { - const interview = { - response: { - _sections: { - 'section1': {} - } - } - } as unknown as UserRuntimeInterviewAttributes; - + const interview = { response: { _sections: { section1: {} } } } as unknown as UserRuntimeInterviewAttributes; + expect(isSectionCompleted({ interview, sectionName: 'section1' })).toBe(false); }); it('should return false when section does not exist', () => { - const interview = { - response: { - _sections: {} - } - } as unknown as UserRuntimeInterviewAttributes; - + const interview = { response: { _sections: {} } } as unknown as UserRuntimeInterviewAttributes; + expect(isSectionCompleted({ interview, sectionName: 'nonExistentSection' })).toBe(false); }); it('should return false when _sections does not exist', () => { - const interview = { - response: {} - } as unknown as UserRuntimeInterviewAttributes; - + const interview = { response: {} } as unknown as UserRuntimeInterviewAttributes; + expect(isSectionCompleted({ interview, sectionName: 'section1' })).toBe(false); }); it('should return false when specific iteration context is not defined', () => { const interview = { - response: { - _sections: { - 'section1': { - _isCompleted: true, - 'person/uuid': { - _isCompleted: true - } - } - } - } + response: { _sections: { section1: { _isCompleted: true, 'person/uuid': { _isCompleted: true } } } } } as unknown as UserRuntimeInterviewAttributes; - + expect(isSectionCompleted({ interview, sectionName: 'section1', iterationContext: ['person', 'uuid3'] })).toBe(false); }); }); describe('isIterationContextStarted', () => { it('should return true when iteration context is empty', () => { - const interview = { - response: { - _sections: {} - } - } as unknown as UserRuntimeInterviewAttributes; + const interview = { response: { _sections: {} } } as unknown as UserRuntimeInterviewAttributes; expect(isIterationContextStarted({ interview, iterationContext: [] })).toBe(true); }); it('should return true when iteration context has started', () => { const interview = { - response: { - _sections: { - section1: { - 'person/uuid': { - _startedAt: 1234567890 - } - } - } - } + response: { _sections: { section1: { 'person/uuid': { _startedAt: 1234567890 } } } } } as unknown as UserRuntimeInterviewAttributes; expect(isIterationContextStarted({ interview, iterationContext: ['person', 'uuid'] })).toBe(true); }); it('should return false when iteration context has not started', () => { - const interview = { - response: { - _sections: { - section1: { - 'person/uuid': {} - } - } - } - } as unknown as UserRuntimeInterviewAttributes; + const interview = { response: { _sections: { section1: { 'person/uuid': {} } } } } as unknown as UserRuntimeInterviewAttributes; expect(isIterationContextStarted({ interview, iterationContext: ['person', 'uuid'] })).toBe(false); }); it('should return false when iteration context does not exist', () => { - const interview = { - response: { - _sections: { - section1: {} - } - } - } as unknown as UserRuntimeInterviewAttributes; + const interview = { response: { _sections: { section1: {} } } } as unknown as UserRuntimeInterviewAttributes; expect(isIterationContextStarted({ interview, iterationContext: ['person', 'uuid'] })).toBe(false); }); @@ -243,18 +148,7 @@ describe('isIterationContextStarted', () => { it('should return true when any section for the iteration context has started', () => { const interview = { response: { - _sections: { - section1: { - 'person/uuid': { - _startedAt: 1234567890 - } - }, - section2: { - 'person/uuid2': { - _startedAt: 9876543210 - } - } - } + _sections: { section1: { 'person/uuid': { _startedAt: 1234567890 } }, section2: { 'person/uuid2': { _startedAt: 9876543210 } } } } } as unknown as UserRuntimeInterviewAttributes; @@ -264,14 +158,7 @@ describe('isIterationContextStarted', () => { it('should return false when no sections for the iteration context have started', () => { const interview = { response: { - _sections: { - section1: { - 'person/uuid': { _startedAt: 1234567890 } - }, - section2: { - 'person/uuid2': { _startedAt: 1234567890 } - } - } + _sections: { section1: { 'person/uuid': { _startedAt: 1234567890 } }, section2: { 'person/uuid2': { _startedAt: 1234567890 } } } } } as unknown as UserRuntimeInterviewAttributes; diff --git a/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/buttonValidateAndGotoNextSection.test.ts b/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/buttonValidateAndGotoNextSection.test.ts index 3cafac1a2..ab6ab5f58 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/buttonValidateAndGotoNextSection.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/buttonValidateAndGotoNextSection.test.ts @@ -15,10 +15,9 @@ const translatableKey = 'myButtonKey'; beforeEach(() => { jest.clearAllMocks(); -}) +}); describe('getButtonValidateAndGotoNextSection', () => { - test('should return the correct widget config', () => { const widgetConfig = getButtonValidateAndGotoNextSection(translatableKey, widgetFactoryOptions); expect(widgetConfig).toEqual({ @@ -32,7 +31,6 @@ describe('getButtonValidateAndGotoNextSection', () => { action: widgetFactoryOptions.buttonActions.validateButtonActionWithCompleteSection }); }); - }); describe('getButtonValidateAndGotoNextSection labels', () => { @@ -45,7 +43,6 @@ describe('getButtonValidateAndGotoNextSection labels', () => { utilHelpers.translateString(title, { t: mockedT } as any, interviewAttributesForTestCases, 'path'); expect(mockedT).toHaveBeenCalledWith([`customSurvey:${translatableKey}`, translatableKey]); }); - }); describe('getButtonValidateAndGotoNextSection button action', () => { @@ -54,7 +51,13 @@ describe('getButtonValidateAndGotoNextSection button action', () => { test('test button action', () => { expect(widgetFactoryOptions.buttonActions.validateButtonActionWithCompleteSection).not.toHaveBeenCalled(); const action = widgetConfig.action; - action({ startUpdateInterview: jest.fn(), startAddGroupedObjects: jest.fn(), startRemoveGroupedObjects: jest.fn(), startNavigate: jest.fn() }, interviewAttributesForTestCases, 'path', 'segments', {}); + action( + { startUpdateInterview: jest.fn(), startAddGroupedObjects: jest.fn(), startRemoveGroupedObjects: jest.fn(), startNavigate: jest.fn() }, + interviewAttributesForTestCases, + 'path', + 'segments', + {} + ); expect(widgetFactoryOptions.buttonActions.validateButtonActionWithCompleteSection).toHaveBeenCalled(); - }) -}); \ No newline at end of file + }); +}); diff --git a/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/widgetPersonVisitedPlacesMap.test.ts b/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/widgetPersonVisitedPlacesMap.test.ts index 7fa76438b..9623b941c 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/widgetPersonVisitedPlacesMap.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/widgetPersonVisitedPlacesMap.test.ts @@ -27,7 +27,12 @@ const mockedGetVisitedPlaceGeography = odHelpers.getVisitedPlaceGeography as jes // Mock points to Bezier by returning a simple line string with the coordinates jest.mock('../../../../geodata/SurveyGeographyUtils', () => ({ - pointsToBezierCurve: jest.fn().mockImplementation((points: GeoJSON.Point[]) => ({ type: 'Feature', geometry: { type: 'LineString', properties: {}, coordinates: points.map(point => point.coordinates) } })) + pointsToBezierCurve: jest + .fn() + .mockImplementation((points: GeoJSON.Point[]) => ({ + type: 'Feature', + geometry: { type: 'LineString', properties: {}, coordinates: points.map((point) => point.coordinates) } + })) })); const mockedPointsToBezierCurve = pointsToBezierCurve as jest.MockedFunction; @@ -39,11 +44,7 @@ beforeEach(() => { describe('personVisitedPlacesMapConfig', () => { it('should return the correct widget config', () => { - - const options = { - context: jest.fn(), - getFormattedDate: mockGetFormattedDate - }; + const options = { context: jest.fn(), getFormattedDate: mockGetFormattedDate }; const widgetConfig = getPersonVisitedPlacesMapConfig(options); @@ -59,7 +60,6 @@ describe('personVisitedPlacesMapConfig', () => { }); describe('personVisitedPlacesMapConfig title', () => { - const options = { ...widgetFactoryOptions, context: jest.fn().mockImplementation((context: string) => context), @@ -68,7 +68,7 @@ describe('personVisitedPlacesMapConfig title', () => { const widgetTitle = getPersonVisitedPlacesMapConfig(options).title as any; const mockedT = jest.fn().mockReturnValue('translatedString'); - + test('should call translation with correct parameters if no active person', () => { mockedGetActivePerson.mockReturnValueOnce(null); mockedGetActiveJourney.mockReturnValueOnce(null); @@ -83,7 +83,7 @@ describe('personVisitedPlacesMapConfig title', () => { }); test('should call translation with correct parameters if no active journey', () => { - const nickname = 'Jane' + const nickname = 'Jane'; mockedGetActivePerson.mockReturnValueOnce({ _uuid: 'person1', _sequence: 1, nickname }); mockedGetActiveJourney.mockReturnValueOnce(null); expect(widgetTitle(mockedT, interviewAttributesForTestCases, 'path')).toEqual('translatedString'); @@ -110,7 +110,7 @@ describe('personVisitedPlacesMapConfig title', () => { }); test('should call translation with correct parameters if multiple person household and journey with start date', () => { - const nickname = 'Jane' + const nickname = 'Jane'; mockedGetActivePerson.mockReturnValueOnce({ _uuid: 'person1', _sequence: 1, nickname }); mockedGetActiveJourney.mockReturnValueOnce({ _uuid: 'journey1', _sequence: 1, startDate: '2024-11-18' }); mockedGetCountOrSelfDeclared.mockReturnValueOnce(2); @@ -123,33 +123,22 @@ describe('personVisitedPlacesMapConfig title', () => { }); expect(options.context).toHaveBeenCalledWith(undefined); }); - }); describe('personVisitedPlacesMapConfig geojsons', () => { - - const options = { - context: jest.fn().mockImplementation((context: string) => context), - getFormattedDate: mockGetFormattedDate - }; + const options = { context: jest.fn().mockImplementation((context: string) => context), getFormattedDate: mockGetFormattedDate }; const widgetGeojsons = getPersonVisitedPlacesMapConfig(options).geojsons as any; const mockedT = jest.fn().mockReturnValue('translatedString'); const person = { _uuid: 'person1', _sequence: 1 }; const journey = { _uuid: 'journeyId1', _sequence: 1 }; - + test('should return empty features collections when no person', () => { mockedGetActivePerson.mockReturnValueOnce(null); mockedGetActiveJourney.mockReturnValueOnce(journey); expect(widgetGeojsons(interviewAttributesForTestCases)).toEqual({ - points: { - type: 'FeatureCollection', - features: [] - }, - linestrings: { - type: 'FeatureCollection', - features: [] - } + points: { type: 'FeatureCollection', features: [] }, + linestrings: { type: 'FeatureCollection', features: [] } }); expect(mockedGetVisitedPlacesArray).not.toHaveBeenCalled(); expect(mockedGetVisitedPlaceGeography).not.toHaveBeenCalled(); @@ -159,14 +148,8 @@ describe('personVisitedPlacesMapConfig geojsons', () => { mockedGetActivePerson.mockReturnValueOnce(person); mockedGetActiveJourney.mockReturnValueOnce(null); expect(widgetGeojsons(interviewAttributesForTestCases)).toEqual({ - points: { - type: 'FeatureCollection', - features: [] - }, - linestrings: { - type: 'FeatureCollection', - features: [] - } + points: { type: 'FeatureCollection', features: [] }, + linestrings: { type: 'FeatureCollection', features: [] } }); expect(mockedGetVisitedPlacesArray).not.toHaveBeenCalled(); expect(mockedGetVisitedPlaceGeography).not.toHaveBeenCalled(); @@ -177,45 +160,44 @@ describe('personVisitedPlacesMapConfig geojsons', () => { mockedGetActiveJourney.mockReturnValueOnce(journey); mockedGetVisitedPlacesArray.mockReturnValueOnce([]); expect(widgetGeojsons(interviewAttributesForTestCases)).toEqual({ - points: { - type: 'FeatureCollection', - features: [] - }, - linestrings: { - type: 'FeatureCollection', - features: [] - } + points: { type: 'FeatureCollection', features: [] }, + linestrings: { type: 'FeatureCollection', features: [] } }); expect(mockedGetVisitedPlacesArray).toHaveBeenCalledWith({ journey }); expect(mockedGetVisitedPlaceGeography).not.toHaveBeenCalled(); }); test('should return points and lines features if visited places with geography', () => { - const visitedPlaces: VisitedPlace[] = [{ - _uuid: 'place1', - _sequence: 1, - name: 'home', - activity: 'home', - geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [0, 0] } } - }, { - _uuid: 'place2', - _sequence: 2, - name: 'place2', - activity: 'work', - geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [1, 1] } } - }, { - _uuid: 'place3', - _sequence: 3, - name: 'place3', - activity: 'shopping', - geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [0, 1] } } - }, { - _uuid: 'place4', - _sequence: 3, - name: 'home', - activity: 'home', - geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [0, 0] } } - }] + const visitedPlaces: VisitedPlace[] = [ + { + _uuid: 'place1', + _sequence: 1, + name: 'home', + activity: 'home', + geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [0, 0] } } + }, + { + _uuid: 'place2', + _sequence: 2, + name: 'place2', + activity: 'work', + geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [1, 1] } } + }, + { + _uuid: 'place3', + _sequence: 3, + name: 'place3', + activity: 'shopping', + geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [0, 1] } } + }, + { + _uuid: 'place4', + _sequence: 3, + name: 'home', + activity: 'home', + geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [0, 0] } } + } + ]; mockedGetActivePerson.mockReturnValueOnce(person); mockedGetActiveJourney.mockReturnValueOnce(journey); mockedGetVisitedPlacesArray.mockReturnValueOnce(visitedPlaces); @@ -230,18 +212,84 @@ describe('personVisitedPlacesMapConfig geojsons', () => { points: { type: 'FeatureCollection', features: [ - { ...visitedPlaces[0].geography!, properties: { ...visitedPlaces[0].geography!.properties, icon: { url: '/dist/icons/activities/home/home-marker_round.svg', size: [40, 40] }, highlighted: false, label: 'home', sequence: 1 } }, - { ...visitedPlaces[1].geography!, properties: { ...visitedPlaces[1].geography!.properties, icon: { url: '/dist/icons/activities/work/briefcase-marker_round.svg', size: [40, 40] }, highlighted: false, label: 'place2', sequence: 2 } }, - { ...visitedPlaces[2].geography!, properties: { ...visitedPlaces[2].geography!.properties, icon: { url: '/dist/icons/activities/shopping/shopping_cart-marker_round.svg', size: [40, 40] }, highlighted: false, label: 'place3', sequence: 3 } }, - { ...visitedPlaces[3].geography!, properties: { ...visitedPlaces[3].geography!.properties, icon: { url: '/dist/icons/activities/home/home-marker_round.svg', size: [40, 40] }, highlighted: false, label: 'home', sequence: 3 } } + { + ...visitedPlaces[0].geography!, + properties: { + ...visitedPlaces[0].geography!.properties, + icon: { url: '/dist/icons/activities/home/home-marker_round.svg', size: [40, 40] }, + highlighted: false, + label: 'home', + sequence: 1 + } + }, + { + ...visitedPlaces[1].geography!, + properties: { + ...visitedPlaces[1].geography!.properties, + icon: { url: '/dist/icons/activities/work/briefcase-marker_round.svg', size: [40, 40] }, + highlighted: false, + label: 'place2', + sequence: 2 + } + }, + { + ...visitedPlaces[2].geography!, + properties: { + ...visitedPlaces[2].geography!.properties, + icon: { url: '/dist/icons/activities/shopping/shopping_cart-marker_round.svg', size: [40, 40] }, + highlighted: false, + label: 'place3', + sequence: 3 + } + }, + { + ...visitedPlaces[3].geography!, + properties: { + ...visitedPlaces[3].geography!.properties, + icon: { url: '/dist/icons/activities/home/home-marker_round.svg', size: [40, 40] }, + highlighted: false, + label: 'home', + sequence: 3 + } + } ] }, linestrings: { type: 'FeatureCollection', features: [ - { type: 'Feature', geometry: { type: 'LineString', properties: {}, coordinates: [[0, 0], [1, 1]] } }, - { type: 'Feature', geometry: { type: 'LineString', properties: {}, coordinates: [[1, 1], [0, 1]] } }, - { type: 'Feature', geometry: { type: 'LineString', properties: {}, coordinates: [[0, 1], [0, 0]] } } + { + type: 'Feature', + geometry: { + type: 'LineString', + properties: {}, + coordinates: [ + [0, 0], + [1, 1] + ] + } + }, + { + type: 'Feature', + geometry: { + type: 'LineString', + properties: {}, + coordinates: [ + [1, 1], + [0, 1] + ] + } + }, + { + type: 'Feature', + geometry: { + type: 'LineString', + properties: {}, + coordinates: [ + [0, 1], + [0, 0] + ] + } + } ] } }); @@ -249,47 +297,65 @@ describe('personVisitedPlacesMapConfig geojsons', () => { // Validate the function called expect(mockedGetVisitedPlacesArray).toHaveBeenCalledWith({ journey }); expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledTimes(visitedPlaces.length); - expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ visitedPlace: visitedPlaces[0], interview: interviewAttributesForTestCases, person }); - expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ visitedPlace: visitedPlaces[1], interview: interviewAttributesForTestCases, person }); - expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ visitedPlace: visitedPlaces[2], interview: interviewAttributesForTestCases, person }); - expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ visitedPlace: visitedPlaces[3], interview: interviewAttributesForTestCases, person }); + expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ + visitedPlace: visitedPlaces[0], + interview: interviewAttributesForTestCases, + person + }); + expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ + visitedPlace: visitedPlaces[1], + interview: interviewAttributesForTestCases, + person + }); + expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ + visitedPlace: visitedPlaces[2], + interview: interviewAttributesForTestCases, + person + }); + expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ + visitedPlace: visitedPlaces[3], + interview: interviewAttributesForTestCases, + person + }); expect(mockedPointsToBezierCurve).toHaveBeenCalledTimes(3); - expect(mockedPointsToBezierCurve).toHaveBeenCalledWith([visitedPlaces[0].geography!.geometry, visitedPlaces[1].geography!.geometry], { superposedSequence: 0 }); - expect(mockedPointsToBezierCurve).toHaveBeenCalledWith([visitedPlaces[1].geography!.geometry, visitedPlaces[2].geography!.geometry], { superposedSequence: 0 }); - expect(mockedPointsToBezierCurve).toHaveBeenCalledWith([visitedPlaces[2].geography!.geometry, visitedPlaces[3].geography!.geometry], { superposedSequence: 0 }); + expect(mockedPointsToBezierCurve).toHaveBeenCalledWith([visitedPlaces[0].geography!.geometry, visitedPlaces[1].geography!.geometry], { + superposedSequence: 0 + }); + expect(mockedPointsToBezierCurve).toHaveBeenCalledWith([visitedPlaces[1].geography!.geometry, visitedPlaces[2].geography!.geometry], { + superposedSequence: 0 + }); + expect(mockedPointsToBezierCurve).toHaveBeenCalledWith([visitedPlaces[2].geography!.geometry, visitedPlaces[3].geography!.geometry], { + superposedSequence: 0 + }); }); test('should return correct points and lines features if some visited places without geography, or with undefined properties', () => { // Places 0 and 2 have no geography and should be ignored from map - const visitedPlaces: VisitedPlace[] = [{ - _uuid: 'place1', - _sequence: 1, - name: 'home', - activity: 'home', - }, { - _uuid: 'place2', - _sequence: 2, - name: 'place2', - activity: 'work', - geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [1, 1] } } - }, { - _uuid: 'place3', - _sequence: 3, - name: 'place3', - activity: 'shopping', - }, { - _uuid: 'place4', - _sequence: 3, - name: 'home', - activity: 'home', - geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [0, 0] } } - }, { - _uuid: 'place5', - _sequence: 4, - name: 'My work without properties', - activity: 'workUsual', - geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0.5, 0.2] } } as any - }] + const visitedPlaces: VisitedPlace[] = [ + { _uuid: 'place1', _sequence: 1, name: 'home', activity: 'home' }, + { + _uuid: 'place2', + _sequence: 2, + name: 'place2', + activity: 'work', + geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [1, 1] } } + }, + { _uuid: 'place3', _sequence: 3, name: 'place3', activity: 'shopping' }, + { + _uuid: 'place4', + _sequence: 3, + name: 'home', + activity: 'home', + geography: { type: 'Feature', properties: { lastAction: 'mapClicked' }, geometry: { type: 'Point', coordinates: [0, 0] } } + }, + { + _uuid: 'place5', + _sequence: 4, + name: 'My work without properties', + activity: 'workUsual', + geography: { type: 'Feature', geometry: { type: 'Point', coordinates: [0.5, 0.2] } } as any + } + ]; mockedGetActivePerson.mockReturnValueOnce(person); mockedGetActiveJourney.mockReturnValueOnce(journey); mockedGetVisitedPlacesArray.mockReturnValueOnce(visitedPlaces); @@ -305,30 +371,99 @@ describe('personVisitedPlacesMapConfig geojsons', () => { expect(geojsons.points).toEqual({ type: 'FeatureCollection', features: [ - { ...visitedPlaces[1].geography!, properties: { ...visitedPlaces[1].geography!.properties, icon: { url: '/dist/icons/activities/work/briefcase-marker_round.svg', size: [40, 40] }, highlighted: false, label: 'place2', sequence: 2 } }, - { ...visitedPlaces[3].geography!, properties: { ...visitedPlaces[3].geography!.properties, icon: { url: '/dist/icons/activities/home/home-marker_round.svg', size: [40, 40] }, highlighted: false, label: 'home', sequence: 3 } }, - { ...visitedPlaces[4].geography!, properties: { icon: { url: '/dist/icons/activities/work/briefcase-marker_round.svg', size: [40, 40] }, highlighted: false, label: 'My work without properties', sequence: 4 } } + { + ...visitedPlaces[1].geography!, + properties: { + ...visitedPlaces[1].geography!.properties, + icon: { url: '/dist/icons/activities/work/briefcase-marker_round.svg', size: [40, 40] }, + highlighted: false, + label: 'place2', + sequence: 2 + } + }, + { + ...visitedPlaces[3].geography!, + properties: { + ...visitedPlaces[3].geography!.properties, + icon: { url: '/dist/icons/activities/home/home-marker_round.svg', size: [40, 40] }, + highlighted: false, + label: 'home', + sequence: 3 + } + }, + { + ...visitedPlaces[4].geography!, + properties: { + icon: { url: '/dist/icons/activities/work/briefcase-marker_round.svg', size: [40, 40] }, + highlighted: false, + label: 'My work without properties', + sequence: 4 + } + } ] }); expect(geojsons.linestrings).toEqual({ type: 'FeatureCollection', features: [ - { type: 'Feature', geometry: { type: 'LineString', properties: {}, coordinates: [[1, 1], [0, 0]] } }, - { type: 'Feature', geometry: { type: 'LineString', properties: {}, coordinates: [[0, 0], [0.5, 0.2]] } } + { + type: 'Feature', + geometry: { + type: 'LineString', + properties: {}, + coordinates: [ + [1, 1], + [0, 0] + ] + } + }, + { + type: 'Feature', + geometry: { + type: 'LineString', + properties: {}, + coordinates: [ + [0, 0], + [0.5, 0.2] + ] + } + } ] - }) + }); // Validate the function called expect(mockedGetVisitedPlacesArray).toHaveBeenCalledWith({ journey }); expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledTimes(visitedPlaces.length); - expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ visitedPlace: visitedPlaces[0], interview: interviewAttributesForTestCases, person }); - expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ visitedPlace: visitedPlaces[1], interview: interviewAttributesForTestCases, person }); - expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ visitedPlace: visitedPlaces[2], interview: interviewAttributesForTestCases, person }); - expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ visitedPlace: visitedPlaces[3], interview: interviewAttributesForTestCases, person }); - expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ visitedPlace: visitedPlaces[4], interview: interviewAttributesForTestCases, person }); + expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ + visitedPlace: visitedPlaces[0], + interview: interviewAttributesForTestCases, + person + }); + expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ + visitedPlace: visitedPlaces[1], + interview: interviewAttributesForTestCases, + person + }); + expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ + visitedPlace: visitedPlaces[2], + interview: interviewAttributesForTestCases, + person + }); + expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ + visitedPlace: visitedPlaces[3], + interview: interviewAttributesForTestCases, + person + }); + expect(mockedGetVisitedPlaceGeography).toHaveBeenCalledWith({ + visitedPlace: visitedPlaces[4], + interview: interviewAttributesForTestCases, + person + }); expect(mockedPointsToBezierCurve).toHaveBeenCalledTimes(2); - expect(mockedPointsToBezierCurve).toHaveBeenCalledWith([visitedPlaces[1].geography!.geometry, visitedPlaces[3].geography!.geometry], { superposedSequence: 0 }); - expect(mockedPointsToBezierCurve).toHaveBeenCalledWith([visitedPlaces[3].geography!.geometry, visitedPlaces[4].geography!.geometry], { superposedSequence: 0 }); + expect(mockedPointsToBezierCurve).toHaveBeenCalledWith([visitedPlaces[1].geography!.geometry, visitedPlaces[3].geography!.geometry], { + superposedSequence: 0 + }); + expect(mockedPointsToBezierCurve).toHaveBeenCalledWith([visitedPlaces[3].geography!.geometry, visitedPlaces[4].geography!.geometry], { + superposedSequence: 0 + }); }); - }); diff --git a/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/widgetsSwitchPerson.test.ts b/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/widgetsSwitchPerson.test.ts index 9e0217cea..a01f3c34e 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/widgetsSwitchPerson.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/common/__tests__/widgetsSwitchPerson.test.ts @@ -18,7 +18,9 @@ jest.mock('../../../../odSurvey/helpers', () => ({ getActivePerson: jest.fn().mockReturnValue(null), countPersons: jest.fn().mockReturnValue(0) })); -const mockedGetInterviewablePersonsArray = odHelpers.getInterviewablePersonsArray as jest.MockedFunction; +const mockedGetInterviewablePersonsArray = odHelpers.getInterviewablePersonsArray as jest.MockedFunction< + typeof odHelpers.getInterviewablePersonsArray +>; const mockedGetActivePerson = odHelpers.getActivePerson as jest.MockedFunction; const mockedCountPersons = odHelpers.countPersons as jest.MockedFunction; @@ -27,7 +29,6 @@ beforeEach(() => { }); describe('SwitchPersonWidgets', () => { - test('should return the correct widget configs', () => { const widgetConfig = new SwitchPersonWidgetsFactory(widgetFactoryOptions).getWidgetConfigs(); expect(widgetConfig).toEqual({ @@ -59,11 +60,9 @@ describe('SwitchPersonWidgets', () => { } }); }); - }); describe('activePersonTitle widget', () => { - const widgetConfig = new SwitchPersonWidgetsFactory(widgetFactoryOptions).getWidgetConfigs()['activePersonTitle'] as TextWidgetConfig; describe('conditional', () => { @@ -104,16 +103,13 @@ describe('activePersonTitle widget', () => { utilHelpers.translateString(text, { t: mockedT } as any, interviewAttributesForTestCases, 'path'); expect(mockedT).toHaveBeenCalledWith(['customSurvey:ActivePersonTitle', 'survey:ActivePersonTitle'], { context: 'unnamed', name: 1 }); }); - }); - }); describe('buttonSwitchPerson widget', () => { const widgetConfig = new SwitchPersonWidgetsFactory(widgetFactoryOptions).getWidgetConfigs()['buttonSwitchPerson'] as ButtonWidgetConfig; describe('conditional', () => { - test('should return false if there are no interviewable persons', () => { mockedGetInterviewablePersonsArray.mockReturnValue([]); expect(widgetConfig.conditional!(interviewAttributesForTestCases, 'path')).toEqual([false, undefined]); @@ -125,16 +121,23 @@ describe('buttonSwitchPerson widget', () => { }); test('should return true if there is more than one interviewable person', () => { - mockedGetInterviewablePersonsArray.mockReturnValue([{ _uuid: 'personId1', _sequence: 1 }, { _uuid: 'personId1', _sequence: 1 }]); + mockedGetInterviewablePersonsArray.mockReturnValue([ + { _uuid: 'personId1', _sequence: 1 }, + { _uuid: 'personId1', _sequence: 1 } + ]); expect(widgetConfig.conditional!(interviewAttributesForTestCases, 'path')).toEqual([true, undefined]); }); - }); test('action', () => { const action = widgetConfig.action; expect(action).toBeDefined(); - const callbackMocks = { startUpdateInterview: jest.fn(), startAddGroupedObjects: jest.fn(), startRemoveGroupedObjects: jest.fn(), startNavigate: jest.fn() }; + const callbackMocks = { + startUpdateInterview: jest.fn(), + startAddGroupedObjects: jest.fn(), + startRemoveGroupedObjects: jest.fn(), + startNavigate: jest.fn() + }; action(callbackMocks, interviewAttributesForTestCases, 'path', 'section', {}, jest.fn()); expect(callbackMocks.startNavigate).toHaveBeenCalledWith({ requestedSection: { sectionShortname: 'selectPerson' } }); }); @@ -172,7 +175,6 @@ describe('buttonSwitchPerson widget', () => { (interview as any).allWidgetsValid = undefined; expect(conditional!(interview, 'path')).toBe(true); }); - }); test('content', () => { @@ -190,7 +192,5 @@ describe('buttonSwitchPerson widget', () => { utilHelpers.translateString(text, { t: mockedT } as any, interviewAttributesForTestCases, 'path'); expect(mockedT).toHaveBeenCalledWith('main:OK'); }); - }); - }); diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/buttonSaveTripSegments.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/buttonSaveTripSegments.test.ts index 806f4c096..8524f9aa2 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/buttonSaveTripSegments.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/buttonSaveTripSegments.test.ts @@ -20,13 +20,11 @@ const mockedGetPerson = odHelpers.getPerson as jest.MockedFunction; const mockedSelectNextIncompleteTrip = odHelpers.selectNextIncompleteTrip as jest.MockedFunction; - beforeEach(() => { jest.clearAllMocks(); }); describe('getButtonSaveTripSegmentsConfig', () => { - test('should return the correct widget config', () => { const widgetConfig = getButtonSaveTripSegmentsConfig(widgetFactoryOptions); expect(widgetConfig).toEqual({ @@ -42,7 +40,6 @@ describe('getButtonSaveTripSegmentsConfig', () => { conditional: expect.any(Function) }); }); - }); describe('getButtonSaveTripSegmentsConfig labels', () => { @@ -55,7 +52,6 @@ describe('getButtonSaveTripSegmentsConfig labels', () => { utilHelpers.translateString(title, { t: mockedT } as any, interviewAttributesForTestCases, 'path'); expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:SaveTripLabel', 'segments:SaveTripLabel']); }); - }); describe('getButtonSaveTripSegmentsConfig conditional', () => { @@ -77,7 +73,10 @@ describe('getButtonSaveTripSegmentsConfig conditional', () => { }); test('shoud return `false` if the last segment has next mode', () => { - mockedGetResponse.mockReturnValue({ segment1: { _uuid: 'segment1', _sequence: 1, hasNextMode: true }, segment2: { _uuid: 'segment2', _sequence: 2, hasNextMode: true } }); + mockedGetResponse.mockReturnValue({ + segment1: { _uuid: 'segment1', _sequence: 1, hasNextMode: true }, + segment2: { _uuid: 'segment2', _sequence: 2, hasNextMode: true } + }); const conditional = widgetConfig.conditional; expect(conditional).toBeDefined(); expect((conditional as any)(interviewAttributesForTestCases, 'path')).toEqual([false, undefined]); @@ -99,7 +98,6 @@ describe('getButtonSaveTripSegmentsConfig conditional', () => { expect((conditional as any)(interviewAttributesForTestCases, 'path')).toEqual([true, undefined]); expect(mockedGetResponse).toHaveBeenCalledWith(interviewAttributesForTestCases, 'path', {}, '../segments'); }); - }); describe('getButtonSaveTripSegmentsConfig button action', () => { @@ -108,7 +106,13 @@ describe('getButtonSaveTripSegmentsConfig button action', () => { test('test button action', () => { expect(widgetFactoryOptions.buttonActions.validateButtonAction).not.toHaveBeenCalled(); const action = widgetConfig.action; - action({ startUpdateInterview: jest.fn(), startAddGroupedObjects: jest.fn(), startRemoveGroupedObjects: jest.fn(), startNavigate: jest.fn() }, interviewAttributesForTestCases, 'path', 'segments', {}); + action( + { startUpdateInterview: jest.fn(), startAddGroupedObjects: jest.fn(), startRemoveGroupedObjects: jest.fn(), startNavigate: jest.fn() }, + interviewAttributesForTestCases, + 'path', + 'segments', + {} + ); expect(widgetFactoryOptions.buttonActions.validateButtonAction).toHaveBeenCalled(); }); }); @@ -121,8 +125,12 @@ describe('getButtonSaveTripSegmentsConfig save callback', () => { const buttonPath = 'path.to.trip.buttonAction'; const saveCallback = widgetConfig.saveCallback; - const updateCallbacks = - { startUpdateInterview: jest.fn(), startAddGroupedObjects: jest.fn(), startRemoveGroupedObjects: jest.fn(), startNavigate: jest.fn() }; + const updateCallbacks = { + startUpdateInterview: jest.fn(), + startAddGroupedObjects: jest.fn(), + startRemoveGroupedObjects: jest.fn(), + startNavigate: jest.fn() + }; beforeEach(() => { jest.clearAllMocks(); @@ -203,7 +211,7 @@ describe('getButtonSaveTripSegmentsConfig save callback', () => { // Mock function response: 2 segments, active journey and incomplete trip const journey = { _uuid: 'journey', _sequence: 1 }; const incompleteTrip = { _uuid: 'trip1', _sequence: 1 }; - mockedGetResponse.mockReturnValueOnce({ }); + mockedGetResponse.mockReturnValueOnce({}); mockedGetActiveJourney.mockReturnValueOnce(journey); mockedSelectNextIncompleteTrip.mockReturnValueOnce(incompleteTrip); @@ -219,5 +227,4 @@ describe('getButtonSaveTripSegmentsConfig save callback', () => { valuesByPath: { 'response._activeTripId': 'trip1' } }); }); - }); diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/groupPersonTrips.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/groupPersonTrips.test.ts index 8d2803840..f97d9d02f 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/groupPersonTrips.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/groupPersonTrips.test.ts @@ -15,40 +15,33 @@ import { Mode } from '../../../../odSurvey/types'; import { getTripSegmentsIntro } from '../widgetTripSegmentsIntro'; import { getButtonSaveTripSegmentsConfig } from '../buttonSaveTripSegments'; -const segmentSectionConfig = { - type: 'segments' as const, - enabled: true -}; +const segmentSectionConfig = { type: 'segments' as const, enabled: true }; describe('PersonsTripsGroupConfigFactory widgets', () => { - - test.each([ - 'personTrips', - 'segmentIntro', - 'buttonSaveTrip' - ])('should have a widget named %s', (widgetName) => { + test.each(['personTrips', 'segmentIntro', 'buttonSaveTrip'])('should have a widget named %s', (widgetName) => { const widgetConfigs = new PersonTripsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); const widgetNames = Object.keys(widgetConfigs); expect(widgetNames).toContain(widgetName); }); - + describe('should also have all the extra widgets from the segments group', () => { - const testSegmentSectionConfig = { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[]}; + const testSegmentSectionConfig = { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[] }; const segmentGroupConfig = new SegmentsGroupConfigFactory(testSegmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); // Make sure there are widgets, then test each one const segmentGroupWidgetNames = Object.keys(segmentGroupConfig); - test('there should be widgets in the segments group', () => { + test('there should be widgets in the segments group', () => { expect(segmentGroupWidgetNames.length).toBeGreaterThan(0); }); - test.each( - segmentGroupWidgetNames.map(widgetName => ({ widgetName, expected: segmentGroupConfig[widgetName] })) - )('should have the segment group widget named $widgetName', ({ widgetName, expected }: { widgetName: string, expected: WidgetConfig }) => { - const widgetConfigs = new PersonTripsGroupConfigFactory(testSegmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); - const widgetConfig = widgetConfigs[widgetName]; - expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expected)); - }); + test.each(segmentGroupWidgetNames.map((widgetName) => ({ widgetName, expected: segmentGroupConfig[widgetName] })))( + 'should have the segment group widget named $widgetName', + ({ widgetName, expected }: { widgetName: string; expected: WidgetConfig }) => { + const widgetConfigs = new PersonTripsGroupConfigFactory(testSegmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); + const widgetConfig = widgetConfigs[widgetName]; + expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expected)); + } + ); }); test('should not return extra widgets', () => { @@ -65,21 +58,41 @@ describe('PersonsTripsGroupConfigFactory widgets', () => { }); test.each([ - { widgetName: 'segmentIntro', segmentSectionConfig, expected: (config: SegmentSectionConfiguration) => getTripSegmentsIntro(widgetFactoryOptions) }, - { widgetName: 'buttonSaveTrip', segmentSectionConfig: { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[]}, expected: (config: SegmentSectionConfiguration) => getButtonSaveTripSegmentsConfig(widgetFactoryOptions) }, - ])('should return the correct widget config for $widgetName', ({ widgetName, segmentSectionConfig, expected }: { widgetName: string, segmentSectionConfig: SegmentSectionConfiguration, expected: (config: SegmentSectionConfiguration) => WidgetConfig }) => { - const widgetConfigs = new PersonTripsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); - const widgetConfig = widgetConfigs[widgetName]; - const expectedWidgetConfig = expected(segmentSectionConfig); - expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expectedWidgetConfig)); - }); + { + widgetName: 'segmentIntro', + segmentSectionConfig, + expected: (config: SegmentSectionConfiguration) => getTripSegmentsIntro(widgetFactoryOptions) + }, + { + widgetName: 'buttonSaveTrip', + segmentSectionConfig: { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[] }, + expected: (config: SegmentSectionConfiguration) => getButtonSaveTripSegmentsConfig(widgetFactoryOptions) + } + ])( + 'should return the correct widget config for $widgetName', + ({ + widgetName, + segmentSectionConfig, + expected + }: { + widgetName: string; + segmentSectionConfig: SegmentSectionConfiguration; + expected: (config: SegmentSectionConfiguration) => WidgetConfig; + }) => { + const widgetConfigs = new PersonTripsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); + const widgetConfig = widgetConfigs[widgetName]; + const expectedWidgetConfig = expected(segmentSectionConfig); + expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expectedWidgetConfig)); + } + ); }); describe('PersonsTripsGroupConfigFactory main group config', () => { - const widgetConfig = new PersonTripsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs()['personTrips'] as GroupConfig; - - describe('getPersonsTripsGroupConfig', () => { + const widgetConfig = new PersonTripsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs()[ + 'personTrips' + ] as GroupConfig; + describe('getPersonsTripsGroupConfig', () => { test('should return the correct widget config', () => { expect(widgetConfig).toEqual({ type: 'group', @@ -89,14 +102,9 @@ describe('PersonsTripsGroupConfigFactory main group config', () => { showTitle: false, showGroupedObjectDeleteButton: false, showGroupedObjectAddButton: false, - widgets: [ - 'segmentIntro', - 'segments', - 'buttonSaveTrip' - ] + widgets: ['segmentIntro', 'segments', 'buttonSaveTrip'] }); }); - }); describe('getPersonsTripsGroupConfig labels', () => { @@ -107,19 +115,15 @@ describe('PersonsTripsGroupConfigFactory main group config', () => { utilHelpers.translateString(title, { t: mockedT } as any, interviewAttributesForTestCases, 'path'); expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:TripsTitle', 'segments:TripsTitle']); }); - }); describe('getPersonsTripsGroupConfig filter', () => { jest.spyOn(utilHelpers, 'getResponse').mockReturnValue({}); const mockedGetResponse = utilHelpers.getResponse as jest.MockedFunction; - + const filter = widgetConfig.filter; - const groupedObjects = { - trip1: { _uuid: 'trip1', _sequence: 1 }, - trip2: { _uuid: 'trip2', _sequence: 2 } - } + const groupedObjects = { trip1: { _uuid: 'trip1', _sequence: 1 }, trip2: { _uuid: 'trip2', _sequence: 2 } }; beforeEach(() => { jest.clearAllMocks(); @@ -127,13 +131,13 @@ describe('PersonsTripsGroupConfigFactory main group config', () => { test('should return empty element is no active trip ID', () => { mockedGetResponse.mockReturnValue(null); - expect(filter!(interviewAttributesForTestCases, groupedObjects)).toEqual({}) + expect(filter!(interviewAttributesForTestCases, groupedObjects)).toEqual({}); expect(mockedGetResponse).toHaveBeenCalledWith(interviewAttributesForTestCases, '_activeTripId', null); }); test('should return empty object if active trip ID does not exist in group objects', () => { mockedGetResponse.mockReturnValue(null); - expect(filter!(interviewAttributesForTestCases, groupedObjects)).toEqual({}) + expect(filter!(interviewAttributesForTestCases, groupedObjects)).toEqual({}); expect(mockedGetResponse).toHaveBeenCalledWith(interviewAttributesForTestCases, '_activeTripId', null); }); @@ -142,7 +146,5 @@ describe('PersonsTripsGroupConfigFactory main group config', () => { expect(filter!(interviewAttributesForTestCases, groupedObjects)).toEqual({ trip1: groupedObjects.trip1 }); expect(mockedGetResponse).toHaveBeenCalledWith(interviewAttributesForTestCases, '_activeTripId', null); }); - }); - }); diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/groupSegments.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/groupSegments.test.ts index 2d4e710bb..c7dd72ab5 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/groupSegments.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/groupSegments.test.ts @@ -16,24 +16,17 @@ import { getModeWidgetConfig } from '../widgetSegmentMode'; import { getSegmentHasNextModeWidgetConfig } from '../widgetSegmentHasNextMode'; import { Mode } from '../../../../odSurvey/types'; -const segmentSectionConfig = { - type: 'segments' as const, - enabled: true -}; +const segmentSectionConfig = { type: 'segments' as const, enabled: true }; describe('SegmentsGroupConfigFactory widgets', () => { - - test.each([ - 'segments', - 'segmentSameModeAsReverseTrip', - 'segmentModePre', - 'segmentMode', - 'segmentHasNextMode' - ])('should have a widget named %s', (widgetName) => { - const widgetConfigs = new SegmentsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); - const widgetNames = Object.keys(widgetConfigs); - expect(widgetNames).toContain(widgetName); - }); + test.each(['segments', 'segmentSameModeAsReverseTrip', 'segmentModePre', 'segmentMode', 'segmentHasNextMode'])( + 'should have a widget named %s', + (widgetName) => { + const widgetConfigs = new SegmentsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); + const widgetNames = Object.keys(widgetConfigs); + expect(widgetNames).toContain(widgetName); + } + ); test('should not return extra widgets', () => { const widgetConfigs = new SegmentsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); @@ -42,23 +35,49 @@ describe('SegmentsGroupConfigFactory widgets', () => { }); test.each([ - { widgetName: 'segmentSameModeAsReverseTrip', segmentSectionConfig, expected: (config: SegmentSectionConfiguration) => getSameAsReverseTripWidgetConfig(widgetFactoryOptions) }, - { widgetName: 'segmentModePre', segmentSectionConfig: { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[]}, expected: (config: SegmentSectionConfiguration) => getModePreWidgetConfig(config, widgetFactoryOptions) }, - { widgetName: 'segmentMode', segmentSectionConfig: { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[]}, expected: (config: SegmentSectionConfiguration) => getModeWidgetConfig(config, widgetFactoryOptions) }, - { widgetName: 'segmentHasNextMode', segmentSectionConfig, expected: (config: SegmentSectionConfiguration) => getSegmentHasNextModeWidgetConfig(widgetFactoryOptions) } - ])('should return the correct widget config for $widgetName', ({ widgetName, segmentSectionConfig, expected }: { widgetName: string, segmentSectionConfig: SegmentSectionConfiguration, expected: (config: SegmentSectionConfiguration) => WidgetConfig }) => { - const widgetConfigs = new SegmentsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); - const widgetConfig = widgetConfigs[widgetName]; - const expectedWidgetConfig = expected(segmentSectionConfig); - expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expectedWidgetConfig)); - }); + { + widgetName: 'segmentSameModeAsReverseTrip', + segmentSectionConfig, + expected: (config: SegmentSectionConfiguration) => getSameAsReverseTripWidgetConfig(widgetFactoryOptions) + }, + { + widgetName: 'segmentModePre', + segmentSectionConfig: { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[] }, + expected: (config: SegmentSectionConfiguration) => getModePreWidgetConfig(config, widgetFactoryOptions) + }, + { + widgetName: 'segmentMode', + segmentSectionConfig: { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[] }, + expected: (config: SegmentSectionConfiguration) => getModeWidgetConfig(config, widgetFactoryOptions) + }, + { + widgetName: 'segmentHasNextMode', + segmentSectionConfig, + expected: (config: SegmentSectionConfiguration) => getSegmentHasNextModeWidgetConfig(widgetFactoryOptions) + } + ])( + 'should return the correct widget config for $widgetName', + ({ + widgetName, + segmentSectionConfig, + expected + }: { + widgetName: string; + segmentSectionConfig: SegmentSectionConfiguration; + expected: (config: SegmentSectionConfiguration) => WidgetConfig; + }) => { + const widgetConfigs = new SegmentsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); + const widgetConfig = widgetConfigs[widgetName]; + const expectedWidgetConfig = expected(segmentSectionConfig); + expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expectedWidgetConfig)); + } + ); }); describe('SegmentsGroupConfigFactory segments GroupConfig widget', () => { const widgetConfig = new SegmentsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs()['segments'] as GroupConfig; test('should return the correct widget config', () => { - expect(widgetConfig).toEqual({ type: 'group', path: 'segments', @@ -69,17 +88,11 @@ describe('SegmentsGroupConfigFactory segments GroupConfig widget', () => { showGroupedObjectAddButton: expect.any(Function), groupedObjectAddButtonLabel: expect.any(Function), addButtonLocation: 'bottom' as const, - widgets: [ - 'segmentSameModeAsReverseTrip', - 'segmentModePre', - 'segmentMode', - 'segmentHasNextMode' - ] + widgets: ['segmentSameModeAsReverseTrip', 'segmentModePre', 'segmentMode', 'segmentHasNextMode'] }); }); describe('getSegmentsGroupConfig labels', () => { - test('should return the right label for title', () => { const mockedT = jest.fn(); const title = widgetConfig.title; @@ -107,7 +120,7 @@ describe('SegmentsGroupConfigFactory segments GroupConfig widget', () => { expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:AddButtonLabel', 'segments:AddButtonLabel'], { count: 0 }); // Call the function with a path with segments - jest.spyOn(utilHelpers, 'getResponse').mockReturnValue({ segment1: { _uuid: 'segment1', _sequence: 1}}); + jest.spyOn(utilHelpers, 'getResponse').mockReturnValue({ segment1: { _uuid: 'segment1', _sequence: 1 } }); utilHelpers.translateString(addButtonLabel, { t: mockedT } as any, interviewAttributesForTestCases, 'path'); expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:AddButtonLabel', 'segments:AddButtonLabel'], { count: 1 }); }); @@ -119,7 +132,6 @@ describe('SegmentsGroupConfigFactory segments GroupConfig widget', () => { }); describe('getSegmentsGroupConfig show add button', () => { - jest.spyOn(utilHelpers, 'getResponse').mockReturnValue({}); const mockedGetResponse = utilHelpers.getResponse as jest.MockedFunction; @@ -136,7 +148,10 @@ describe('SegmentsGroupConfigFactory segments GroupConfig widget', () => { }); test('shoud show the add button if the last segment has next mode', () => { - mockedGetResponse.mockReturnValue({ segment1: { _uuid: 'segment1', _sequence: 1, hasNextMode: true }, segment2: { _uuid: 'segment2', _sequence: 2, hasNextMode: true }}); + mockedGetResponse.mockReturnValue({ + segment1: { _uuid: 'segment1', _sequence: 1, hasNextMode: true }, + segment2: { _uuid: 'segment2', _sequence: 2, hasNextMode: true } + }); const showAddButton = widgetConfig.showGroupedObjectAddButton; expect(showAddButton).toBeDefined(); expect((showAddButton as any)(interviewAttributesForTestCases, 'path')).toBe(true); @@ -144,7 +159,7 @@ describe('SegmentsGroupConfigFactory segments GroupConfig widget', () => { }); test('shoud not show add button if the last segment has no next mode', () => { - mockedGetResponse.mockReturnValue({ segment1: { _uuid: 'segment1', _sequence: 1, hasNextMode: false }}); + mockedGetResponse.mockReturnValue({ segment1: { _uuid: 'segment1', _sequence: 1, hasNextMode: false } }); const showAddButton = widgetConfig.showGroupedObjectAddButton; expect(showAddButton).toBeDefined(); expect((showAddButton as any)(interviewAttributesForTestCases, 'path')).toBe(false); @@ -152,17 +167,15 @@ describe('SegmentsGroupConfigFactory segments GroupConfig widget', () => { }); test('shoud not show add button if the last segment has next mode not set', () => { - mockedGetResponse.mockReturnValue({ segment1: { _uuid: 'segment1', _sequence: 1 }}); + mockedGetResponse.mockReturnValue({ segment1: { _uuid: 'segment1', _sequence: 1 } }); const showAddButton = widgetConfig.showGroupedObjectAddButton; expect(showAddButton).toBeDefined(); expect((showAddButton as any)(interviewAttributesForTestCases, 'path')).toBe(false); expect(mockedGetResponse).toHaveBeenCalledWith(interviewAttributesForTestCases, 'path', {}); }); - }); describe('getSegmentsGroupConfig show delete button', () => { - jest.spyOn(utilHelpers, 'getResponse').mockReturnValue({}); const mockedGetResponse = utilHelpers.getResponse as jest.MockedFunction; @@ -193,14 +206,15 @@ describe('SegmentsGroupConfigFactory segments GroupConfig widget', () => { expect((showDeleteButton as any)(interviewAttributesForTestCases, 'path')).toBe(false); expect(mockedGetResponse).toHaveBeenCalledWith(interviewAttributesForTestCases, 'path', null); }); - }); }); test('SegmentsGroupConfigFactory segments GroupConfig widget with additional widget names', () => { const segmentSectionConfigWithWidgets: SegmentSectionConfiguration = _cloneDeep(segmentSectionConfig); segmentSectionConfigWithWidgets.additionalSegmentWidgetNames = ['customWidget1', 'customWidget2']; - const widgetConfig = new SegmentsGroupConfigFactory(segmentSectionConfigWithWidgets, widgetFactoryOptions).getWidgetConfigs()['segments'] as GroupConfig; + const widgetConfig = new SegmentsGroupConfigFactory(segmentSectionConfigWithWidgets, widgetFactoryOptions).getWidgetConfigs()[ + 'segments' + ] as GroupConfig; expect(widgetConfig).toEqual({ type: 'group', path: 'segments', @@ -211,13 +225,6 @@ test('SegmentsGroupConfigFactory segments GroupConfig widget with additional wid showGroupedObjectAddButton: expect.any(Function), groupedObjectAddButtonLabel: expect.any(Function), addButtonLocation: 'bottom' as const, - widgets: [ - 'segmentSameModeAsReverseTrip', - 'segmentModePre', - 'segmentMode', - 'customWidget1', - 'customWidget2', - 'segmentHasNextMode' - ] + widgets: ['segmentSameModeAsReverseTrip', 'segmentModePre', 'segmentMode', 'customWidget1', 'customWidget2', 'segmentHasNextMode'] }); }); diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/helpers.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/helpers.test.ts index 8ae958c08..dae4b72e8 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/helpers.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/helpers.test.ts @@ -14,7 +14,6 @@ import { Journey, Person } from '../../../types'; import { modeValues, modePreValues, Mode } from '../../../../odSurvey/types'; describe('getPreviousTripSingleSegment', () => { - test('No active trip', () => { // Prepare test data const interview = _cloneDeep(interviewAttributesForTestCases); @@ -72,7 +71,9 @@ describe('getPreviousTripSingleSegment', () => { // Get person and test the function const person = odHelpers.getPerson({ interview }) as Person; - expect(helpers.getPreviousTripSingleSegment({ interview, person })).toEqual(interview.response.household!.persons!.personId2.journeys!.journeyId2.trips!.tripId1P2.segments!.segmentId1P2T1); + expect(helpers.getPreviousTripSingleSegment({ interview, person })).toEqual( + interview.response.household!.persons!.personId2.journeys!.journeyId2.trips!.tripId1P2.segments!.segmentId1P2T1 + ); }); test('Previous trip, multiple segments', () => { @@ -89,12 +90,11 @@ describe('getPreviousTripSingleSegment', () => { }); describe('isSimpleChainSingleModeReturnTrip', () => { - each([ ['origin', 'household.persons.personId3.journeys.journeyId3.trips.tripId2P3._originVisitedPlaceUuid'], ['destination', 'household.persons.personId3.journeys.journeyId3.trips.tripId2P3._destinationVisitedPlaceUuid'], ['previous origin', 'household.persons.personId3.journeys.journeyId3.trips.tripId1P3._originVisitedPlaceUuid'], - ['geography', 'home.geography'], + ['geography', 'home.geography'] ]).test('undefined data for %s', (_field, pathOfValueToUndefine) => { // Prepare test data, trip 2 of person 3 should be a simple chain const interview = _cloneDeep(interviewAttributesForTestCases); @@ -106,22 +106,17 @@ describe('isSimpleChainSingleModeReturnTrip', () => { const journey = odHelpers.getActiveJourney({ interview, person }) as Journey; // Add a segment to trip1 to make sure it would return `true` if everything was right otherwise - journey.trips!.tripId1P3.segments = { - segmentId1P3T1: { - _uuid: 'segmentId1P3T1', - _sequence: 1, - _isNew: false, - mode: 'walk' - } - }; + journey.trips!.tripId1P3.segments = { segmentId1P3T1: { _uuid: 'segmentId1P3T1', _sequence: 1, _isNew: false, mode: 'walk' } }; - expect(helpers.isSimpleChainSingleModeReturnTrip({ - interview, - journey, - person, - trip: journey.trips!.tripId2P3, - previousTrip: journey.trips!.tripId1P3 - })).toEqual(false); + expect( + helpers.isSimpleChainSingleModeReturnTrip({ + interview, + journey, + person, + trip: journey.trips!.tripId2P3, + previousTrip: journey.trips!.tripId1P3 + }) + ).toEqual(false); }); test('Previous trip is not a simple chain', () => { @@ -134,22 +129,17 @@ describe('isSimpleChainSingleModeReturnTrip', () => { const journey = odHelpers.getActiveJourney({ interview, person }) as Journey; // Add a segment to trip1 with simple mode - journey.trips!.tripId1P2.segments = { - segmentId1P2T1: { - _uuid: 'segmentId1P2T1', - _sequence: 1, - _isNew: false, - mode: 'walk' - } - }; + journey.trips!.tripId1P2.segments = { segmentId1P2T1: { _uuid: 'segmentId1P2T1', _sequence: 1, _isNew: false, mode: 'walk' } }; - expect(helpers.isSimpleChainSingleModeReturnTrip({ - interview, - journey, - person, - trip: journey.trips!.tripId2P2, - previousTrip: journey.trips!.tripId1P2 - })).toEqual(false); + expect( + helpers.isSimpleChainSingleModeReturnTrip({ + interview, + journey, + person, + trip: journey.trips!.tripId2P2, + previousTrip: journey.trips!.tripId1P2 + }) + ).toEqual(false); }); test('Previous trip is simple chain and has single not simple mode', () => { @@ -162,22 +152,17 @@ describe('isSimpleChainSingleModeReturnTrip', () => { const journey = odHelpers.getActiveJourney({ interview, person }) as Journey; // Add a segment to trip1 with not simle mode - journey.trips!.tripId1P3.segments = { - segmentId1P3T1: { - _uuid: 'segmentId1P3T1', - _sequence: 1, - _isNew: false, - mode: 'transitFerry' - } - }; + journey.trips!.tripId1P3.segments = { segmentId1P3T1: { _uuid: 'segmentId1P3T1', _sequence: 1, _isNew: false, mode: 'transitFerry' } }; - expect(helpers.isSimpleChainSingleModeReturnTrip({ - interview, - journey, - person, - trip: journey.trips!.tripId2P3, - previousTrip: journey.trips!.tripId1P3 - })).toEqual(false); + expect( + helpers.isSimpleChainSingleModeReturnTrip({ + interview, + journey, + person, + trip: journey.trips!.tripId2P3, + previousTrip: journey.trips!.tripId1P3 + }) + ).toEqual(false); }); test('Previous trip is simlpe chain and has 2 simple modes', () => { @@ -191,27 +176,19 @@ describe('isSimpleChainSingleModeReturnTrip', () => { // Add 2 segments with simple modes to trip1 with not simle mode journey.trips!.tripId1P3.segments = { - segmentId1P3T1: { - _uuid: 'segmentId1P3T1', - _sequence: 1, - _isNew: false, - mode: 'walk' - }, - segmentId2P3T1: { - _uuid: 'segmentId2P3T1', - _sequence: 1, - _isNew: false, - mode: 'bicycle' - } + segmentId1P3T1: { _uuid: 'segmentId1P3T1', _sequence: 1, _isNew: false, mode: 'walk' }, + segmentId2P3T1: { _uuid: 'segmentId2P3T1', _sequence: 1, _isNew: false, mode: 'bicycle' } }; - expect(helpers.isSimpleChainSingleModeReturnTrip({ - interview, - journey, - person, - trip: journey.trips!.tripId2P3, - previousTrip: journey.trips!.tripId1P3 - })).toEqual(false); + expect( + helpers.isSimpleChainSingleModeReturnTrip({ + interview, + journey, + person, + trip: journey.trips!.tripId2P3, + previousTrip: journey.trips!.tripId1P3 + }) + ).toEqual(false); }); test('Previous trip is simple chain and has single simple mode', () => { @@ -224,22 +201,17 @@ describe('isSimpleChainSingleModeReturnTrip', () => { const journey = odHelpers.getActiveJourney({ interview, person }) as Journey; // Add a segment to trip1 with single simple mode - journey.trips!.tripId1P3.segments = { - segmentId1P3T1: { - _uuid: 'segmentId1P3T1', - _sequence: 1, - _isNew: false, - mode: 'walk' - } - }; + journey.trips!.tripId1P3.segments = { segmentId1P3T1: { _uuid: 'segmentId1P3T1', _sequence: 1, _isNew: false, mode: 'walk' } }; - expect(helpers.isSimpleChainSingleModeReturnTrip({ - interview, - journey, - person, - trip: journey.trips!.tripId2P3, - previousTrip: journey.trips!.tripId1P3 - })).toEqual(true); + expect( + helpers.isSimpleChainSingleModeReturnTrip({ + interview, + journey, + person, + trip: journey.trips!.tripId2P3, + previousTrip: journey.trips!.tripId1P3 + }) + ).toEqual(true); }); test('simple chain/simple mode, but moving activity at origin', () => { @@ -252,25 +224,20 @@ describe('isSimpleChainSingleModeReturnTrip', () => { const journey = odHelpers.getActiveJourney({ interview, person }) as Journey; // Add a segment to trip1 with single simple mode - journey.trips!.tripId1P3.segments = { - segmentId1P3T1: { - _uuid: 'segmentId1P3T1', - _sequence: 1, - _isNew: false, - mode: 'walk' - } - }; + journey.trips!.tripId1P3.segments = { segmentId1P3T1: { _uuid: 'segmentId1P3T1', _sequence: 1, _isNew: false, mode: 'walk' } }; // Change activity of origin to a moving one journey.visitedPlaces!.schoolPlace1P3.activity = 'workOnTheRoad'; - expect(helpers.isSimpleChainSingleModeReturnTrip({ - interview, - journey, - person, - trip: journey.trips!.tripId2P3, - previousTrip: journey.trips!.tripId1P3 - })).toEqual(false); + expect( + helpers.isSimpleChainSingleModeReturnTrip({ + interview, + journey, + person, + trip: journey.trips!.tripId2P3, + previousTrip: journey.trips!.tripId1P3 + }) + ).toEqual(false); }); test('simple chain/simple mode, but moving activity at destination', () => { @@ -283,31 +250,25 @@ describe('isSimpleChainSingleModeReturnTrip', () => { const journey = odHelpers.getActiveJourney({ interview, person }) as Journey; // Add a segment to trip1 with single simple mode - journey.trips!.tripId1P3.segments = { - segmentId1P3T1: { - _uuid: 'segmentId1P3T1', - _sequence: 1, - _isNew: false, - mode: 'walk' - } - }; + journey.trips!.tripId1P3.segments = { segmentId1P3T1: { _uuid: 'segmentId1P3T1', _sequence: 1, _isNew: false, mode: 'walk' } }; // Change activity destinations to a moving one journey.visitedPlaces!.homePlace1P3.activity = 'workOnTheRoad'; journey.visitedPlaces!.homePlace2P3.activity = 'workOnTheRoad'; - expect(helpers.isSimpleChainSingleModeReturnTrip({ - interview, - journey, - person, - trip: journey.trips!.tripId2P3, - previousTrip: journey.trips!.tripId1P3 - })).toEqual(false); + expect( + helpers.isSimpleChainSingleModeReturnTrip({ + interview, + journey, + person, + trip: journey.trips!.tripId2P3, + previousTrip: journey.trips!.tripId1P3 + }) + ).toEqual(false); }); }); describe('shouldShowSameAsReverseTripQuestion', () => { - // Prepare test data with default active person/journey/trip, tripId2P1 is return trip of a simple chain const baseTestInterview = _cloneDeep(interviewAttributesForTestCases); baseTestInterview.response._activePersonId = 'personId1'; @@ -320,9 +281,7 @@ describe('shouldShowSameAsReverseTripQuestion', () => { // Add a segment for tripId2P1, tests will initialize it const baseTestSegment = { _uuid: 'segmentId1P1T2', _sequence: 1, _isNew: true }; - baseTestInterview.response.household!.persons!.personId1.journeys!.journeyId1.trips!.tripId2P1.segments = { - segmentId1P1T2: baseTestSegment - }; + baseTestInterview.response.household!.persons!.personId1.journeys!.journeyId1.trips!.tripId2P1.segments = { segmentId1P1T2: baseTestSegment }; test('Segment is not new, but simple chain', () => { // Prepare interview data, setting segment as not new @@ -376,11 +335,9 @@ describe('shouldShowSameAsReverseTripQuestion', () => { const result = helpers.shouldShowSameAsReverseTripQuestion!({ interview: baseTestInterview, segment: baseTestSegment }); expect(result).toEqual(true); }); - }); describe('conditionalPersonMayHaveDisability', () => { - test('Person has disability set to "yes"', () => { // Prepare test data const interview = _cloneDeep(interviewAttributesForTestCases); @@ -449,7 +406,6 @@ describe('conditionalPersonMayHaveDisability', () => { }); describe('conditionalHhMayHaveDisability', () => { - test('At least one person has disability set to "yes"', () => { // Prepare test data const interview = _cloneDeep(interviewAttributesForTestCases); @@ -555,11 +511,7 @@ describe('Mode/modePre filtering based on configuration', () => { }); test('getFilteredModes should filter with modesExclude', () => { - const segmentConfig = { - type: 'segments' as const, - enabled: true, - modesExclude: ['plane', 'ferryWithCar', 'snowmobile'] as Mode[] - }; + const segmentConfig = { type: 'segments' as const, enabled: true, modesExclude: ['plane', 'ferryWithCar', 'snowmobile'] as Mode[] }; const filteredModes = helpers.getFilteredModes(segmentConfig); // Should exclude those 3 modes @@ -572,11 +524,7 @@ describe('Mode/modePre filtering based on configuration', () => { }); test('getFilteredModes should handle modesIncludeOnly with non-existent modes', () => { - const segmentConfig = { - type: 'segments' as const, - enabled: true, - modesIncludeOnly: ['walk', 'bicycle', 'nonExistentMode' as any] as any - }; + const segmentConfig = { type: 'segments' as const, enabled: true, modesIncludeOnly: ['walk', 'bicycle', 'nonExistentMode' as any] as any }; const filteredModes = helpers.getFilteredModes(segmentConfig); // Should only include the valid modes diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/sectionSegment.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/sectionSegment.test.ts index eef24d4e6..89a691c54 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/sectionSegment.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/sectionSegment.test.ts @@ -19,14 +19,9 @@ import { getPersonVisitedPlacesMapConfig } from '../../common/widgetPersonVisite import { getButtonValidateAndGotoNextSection } from '../../common/buttonValidateAndGotoNextSection'; import { SwitchPersonWidgetsFactory } from '../../common/widgetsSwitchPerson'; -jest.mock('uuid', () => ({ - v4: jest.fn().mockReturnValue('newTripId') -})); +jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('newTripId') })); const mockUuidv4 = uuidv4 as jest.MockedFunction; -const segmentSectionConfig = { - type: 'segments' as const, - enabled: true -}; +const segmentSectionConfig = { type: 'segments' as const, enabled: true }; beforeEach(() => { jest.clearAllMocks(); @@ -38,15 +33,12 @@ const mockedSelectNextIncompleteTrip = jest.spyOn(odHelpers, 'selectNextIncomple const activeJourney = { _uuid: 'testJourney1', _sequence: 1 }; const activePerson = { _uuid: 'testPerson1', _sequence: 1, journeys: { testJourney1: activeJourney } }; const interviewWithTestPerson = _cloneDeep(interviewAttributesForTestCases); -interviewWithTestPerson.response.household!.persons = { - testPerson1: activePerson -}; +interviewWithTestPerson.response.household!.persons = { testPerson1: activePerson }; // Set active person and journey: interviewWithTestPerson.response._activePersonId = activePerson._uuid; interviewWithTestPerson.response._activeJourneyId = activeJourney._uuid; describe('SegmentsSectionFactory#getSectionConfig', () => { - test('should return the correct widget config when section enabled', () => { const sectionFactory = new SegmentsSectionFactory(segmentSectionConfig, widgetFactoryOptions); const widgetConfig = sectionFactory.getSectionConfig(); @@ -58,9 +50,7 @@ describe('SegmentsSectionFactory#getSectionConfig', () => { onSectionEntry: expect.any(Function), template: 'tripsAndSegmentsWithMap', title: expect.any(Function), - customStyle: { - maxWidth: '120rem' - }, + customStyle: { maxWidth: '120rem' }, widgets: [ 'activePersonTitle', 'buttonSwitchPerson', @@ -73,60 +63,60 @@ describe('SegmentsSectionFactory#getSectionConfig', () => { }); test('should return the correct widget config when section disabled', () => { - expect(() => new SegmentsSectionFactory({ type: 'segments', enabled: false }, { ...widgetFactoryOptions, iconMapper: {} })).toThrow('Segments section configuration requested but the section is not enabled'); + expect(() => new SegmentsSectionFactory({ type: 'segments', enabled: false }, { ...widgetFactoryOptions, iconMapper: {} })).toThrow( + 'Segments section configuration requested but the section is not enabled' + ); }); - }); describe('SegmentsSectionFactory#getWidgetConfigs', () => { - - test.each([ - 'personTripsTitle', - 'personTrips', - 'personVisitedPlacesMap', - 'buttonConfirmNextSection' - ])('should have a widget named %s', (widgetName) => { - const widgetConfigs = new SegmentsSectionFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); - const widgetNames = Object.keys(widgetConfigs); - expect(widgetNames).toContain(widgetName); - }); + test.each(['personTripsTitle', 'personTrips', 'personVisitedPlacesMap', 'buttonConfirmNextSection'])( + 'should have a widget named %s', + (widgetName) => { + const widgetConfigs = new SegmentsSectionFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); + const widgetNames = Object.keys(widgetConfigs); + expect(widgetNames).toContain(widgetName); + } + ); describe('should have all the extra widgets from the person trips group', () => { - const testSegmentSectionConfig = { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[]}; + const testSegmentSectionConfig = { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[] }; const personTripsGroupConfig = new PersonTripsGroupConfigFactory(testSegmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); // Make sure there are widgets, then test each one const personTripsGroupWidgetNames = Object.keys(personTripsGroupConfig); - test('there should be widgets in the person trips group', () => { + test('there should be widgets in the person trips group', () => { expect(personTripsGroupWidgetNames.length).toBeGreaterThan(0); }); - test.each( - personTripsGroupWidgetNames.map(widgetName => ({ widgetName, expected: personTripsGroupConfig[widgetName] })) - )('should have the person trips group widget named $widgetName', ({ widgetName, expected }: { widgetName: string, expected: WidgetConfig }) => { - const widgetConfigs = new SegmentsSectionFactory(testSegmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); - const widgetConfig = widgetConfigs[widgetName]; - expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expected)); - }); + test.each(personTripsGroupWidgetNames.map((widgetName) => ({ widgetName, expected: personTripsGroupConfig[widgetName] })))( + 'should have the person trips group widget named $widgetName', + ({ widgetName, expected }: { widgetName: string; expected: WidgetConfig }) => { + const widgetConfigs = new SegmentsSectionFactory(testSegmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); + const widgetConfig = widgetConfigs[widgetName]; + expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expected)); + } + ); }); describe('should have all the extra widgets from the switch person set', () => { - const testSegmentSectionConfig = { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[]}; + const testSegmentSectionConfig = { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[] }; const switchPersonWidgetConfig = new SwitchPersonWidgetsFactory(widgetFactoryOptions).getWidgetConfigs(); // Make sure there are widgets, then test each one const switchPersonWidgetNames = Object.keys(switchPersonWidgetConfig); - test('there should be widgets in the switch person group', () => { + test('there should be widgets in the switch person group', () => { expect(switchPersonWidgetNames.length).toBeGreaterThan(0); }); - test.each( - switchPersonWidgetNames.map(widgetName => ({ widgetName, expected: switchPersonWidgetConfig[widgetName] })) - )('should have the switch person widget named $widgetName', ({ widgetName, expected }: { widgetName: string, expected: WidgetConfig }) => { - const widgetConfigs = new SegmentsSectionFactory(testSegmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); - const widgetConfig = widgetConfigs[widgetName]; - expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expected)); - }); + test.each(switchPersonWidgetNames.map((widgetName) => ({ widgetName, expected: switchPersonWidgetConfig[widgetName] })))( + 'should have the switch person widget named $widgetName', + ({ widgetName, expected }: { widgetName: string; expected: WidgetConfig }) => { + const widgetConfigs = new SegmentsSectionFactory(testSegmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); + const widgetConfig = widgetConfigs[widgetName]; + expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expected)); + } + ); }); test('should not return extra widgets', () => { @@ -134,7 +124,7 @@ describe('SegmentsSectionFactory#getWidgetConfigs', () => { const switchPersonsWidgetConfigs = new SwitchPersonWidgetsFactory(widgetFactoryOptions).getWidgetConfigs(); const switchPersonsWidgetNames = Object.keys(switchPersonsWidgetConfigs); - + // Widgets from segment groups are included in persons trips group, so only count those const tripsGroupWidgetConfigs = new PersonTripsGroupConfigFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); const tripsGroupWidgetNames = Object.keys(tripsGroupWidgetConfigs); @@ -145,15 +135,38 @@ describe('SegmentsSectionFactory#getWidgetConfigs', () => { }); test.each([ - { widgetName: 'personTripsTitle', segmentSectionConfig, expected: (config: SegmentSectionConfiguration) => getPersonsTripsTitleWidgetConfig(widgetFactoryOptions) }, - { widgetName: 'personVisitedPlacesMap', segmentSectionConfig: { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[]}, expected: (config: SegmentSectionConfiguration) => getPersonVisitedPlacesMapConfig(widgetFactoryOptions) }, - { widgetName: 'buttonConfirmNextSection', segmentSectionConfig: { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[]}, expected: (config: SegmentSectionConfiguration) => getButtonValidateAndGotoNextSection('survey:ConfirmAndContinue', widgetFactoryOptions)}, - ])('should return the correct widget config for $widgetName', ({ widgetName, segmentSectionConfig, expected }: { widgetName: string, segmentSectionConfig: SegmentSectionConfiguration, expected: (config: SegmentSectionConfiguration) => WidgetConfig }) => { - const widgetConfigs = new SegmentsSectionFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); - const widgetConfig = widgetConfigs[widgetName]; - const expectedWidgetConfig = expected(segmentSectionConfig); - expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expectedWidgetConfig)); - }); + { + widgetName: 'personTripsTitle', + segmentSectionConfig, + expected: (config: SegmentSectionConfiguration) => getPersonsTripsTitleWidgetConfig(widgetFactoryOptions) + }, + { + widgetName: 'personVisitedPlacesMap', + segmentSectionConfig: { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[] }, + expected: (config: SegmentSectionConfiguration) => getPersonVisitedPlacesMapConfig(widgetFactoryOptions) + }, + { + widgetName: 'buttonConfirmNextSection', + segmentSectionConfig: { ...segmentSectionConfig, modesIncludeOnly: ['walking', 'bicycle'] as Mode[] }, + expected: (config: SegmentSectionConfiguration) => getButtonValidateAndGotoNextSection('survey:ConfirmAndContinue', widgetFactoryOptions) + } + ])( + 'should return the correct widget config for $widgetName', + ({ + widgetName, + segmentSectionConfig, + expected + }: { + widgetName: string; + segmentSectionConfig: SegmentSectionConfiguration; + expected: (config: SegmentSectionConfiguration) => WidgetConfig; + }) => { + const widgetConfigs = new SegmentsSectionFactory(segmentSectionConfig, widgetFactoryOptions).getWidgetConfigs(); + const widgetConfig = widgetConfigs[widgetName]; + const expectedWidgetConfig = expected(segmentSectionConfig); + expect(maskFunctions(widgetConfig)).toEqual(maskFunctions(expectedWidgetConfig)); + } + ); }); describe('sectionConfig functionalities', () => { @@ -168,38 +181,37 @@ describe('sectionConfig functionalities', () => { utilHelpers.translateString(title, { t: mockedT } as any, interviewWithTestPerson, 'path'); expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:SegmentsTitle', 'segments:SegmentsTitle']); }); - }); describe('getSegmentsSectionConfig isSectionVisible', () => { const sectionFactory = new SegmentsSectionFactory(segmentSectionConfig, widgetFactoryOptions); const widgetConfig = sectionFactory.getSectionConfig(); const iterationContext = ['testPerson1']; - + beforeEach(() => { jest.clearAllMocks(); }); test('should return false if no iteration context', () => { const result = widgetConfig.isSectionVisible!(interviewWithTestPerson, undefined); - + expect(result).toBe(false); }); test('should return false if no active journey', () => { const testInterview = _cloneDeep(interviewWithTestPerson); testInterview.response._activeJourneyId = undefined; - + const result = widgetConfig.isSectionVisible!(testInterview, iterationContext); - + expect(result).toBe(false); }); test('should return true if there is an active journey', () => { const testInterview = _cloneDeep(interviewWithTestPerson); - + const result = widgetConfig.isSectionVisible!(testInterview, iterationContext); - + expect(result).toBe(true); }); }); @@ -208,7 +220,7 @@ describe('sectionConfig functionalities', () => { const sectionFactory = new SegmentsSectionFactory(segmentSectionConfig, widgetFactoryOptions); const widgetConfig = sectionFactory.getSectionConfig(); const iterationContext = ['testPerson1']; - + beforeEach(() => { jest.clearAllMocks(); }); @@ -216,23 +228,23 @@ describe('sectionConfig functionalities', () => { test('should return false if unexisting person', () => { const testInterview = _cloneDeep(interviewWithTestPerson); const testIterationContext = ['unexistingPerson']; - + const result = widgetConfig.isSectionCompleted!(testInterview, testIterationContext); - + expect(result).toBe(false); }); test('should return false if no iteration context', () => { const result = widgetConfig.isSectionCompleted!(interviewWithTestPerson, undefined); - + expect(result).toBe(false); }); test('should return true if no next incomplete trip', () => { mockedSelectNextIncompleteTrip.mockReturnValueOnce(null); - + const result = widgetConfig.isSectionCompleted!(interviewWithTestPerson, iterationContext); - + expect(result).toBe(true); expect(mockedSelectNextIncompleteTrip).toHaveBeenCalledWith({ journey: activeJourney }); }); @@ -240,9 +252,9 @@ describe('sectionConfig functionalities', () => { test('should return false if there is a next incomplete trip', () => { const incompleteTrip = { _uuid: 'tripId1', _sequence: 1 }; mockedSelectNextIncompleteTrip.mockReturnValueOnce(incompleteTrip); - + const result = widgetConfig.isSectionCompleted!(interviewWithTestPerson, iterationContext); - + expect(result).toBe(false); expect(mockedSelectNextIncompleteTrip).toHaveBeenCalledWith({ journey: activeJourney }); }); @@ -259,15 +271,15 @@ describe('sectionConfig functionalities', () => { test('should return undefined if unexisting person', () => { const testIterationContext = ['unexistingPerson']; - + const result = widgetConfig.onSectionEntry!(interviewWithTestPerson, testIterationContext); - + expect(result).toBeUndefined(); }); - test('should return undefined if no iteration context', () => { + test('should return undefined if no iteration context', () => { const result = widgetConfig.onSectionEntry!(interviewWithTestPerson, undefined); - + expect(result).toBeUndefined(); }); @@ -290,7 +302,7 @@ describe('sectionConfig functionalities', () => { mockedSelectNextIncompleteTrip.mockReturnValueOnce(null); const result = widgetConfig.onSectionEntry!(testInterview, iterationContext); - + expect(result).toEqual({ 'response.household.persons.testPerson1.journeys.testJourney1.trips.tripId1': newTrip, 'validations.household.persons.testPerson1.journeys.testJourney1.trips.tripId1': {}, @@ -301,7 +313,7 @@ describe('sectionConfig functionalities', () => { test('should delete trips when the number of trips is greater than the number of visited places', () => { const testInterview = _cloneDeep(interviewWithTestPerson); - + // 2 places const places = [ { _uuid: 'testPlace1', _sequence: 1, activity: 'home' }, @@ -324,7 +336,7 @@ describe('sectionConfig functionalities', () => { }; // Should remove the last 2 trips mockedSelectNextIncompleteTrip.mockReturnValueOnce(null); - + const result = widgetConfig.onSectionEntry!(testInterview, iterationContext); expect(result).toEqual({ 'response._activeTripId': null, @@ -351,7 +363,7 @@ describe('sectionConfig functionalities', () => { { _uuid: 'tripId1', _sequence: 1, _originVisitedPlaceUuid: 'testPlace1', _destinationVisitedPlaceUuid: 'testPlace2' }, { _uuid: 'tripId2', _sequence: 2, _originVisitedPlaceUuid: 'testPlace2', _destinationVisitedPlaceUuid: 'testPlace3' }, { _uuid: 'tripId3', _sequence: 3, _originVisitedPlaceUuid: 'testPlace3', _destinationVisitedPlaceUuid: 'testPlace4' } - ] + ]; testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1.trips = { tripId1: trips[0], tripId2: trips[1], @@ -361,14 +373,13 @@ describe('sectionConfig functionalities', () => { mockedSelectNextIncompleteTrip.mockReturnValueOnce(trips[2]); const result = widgetConfig.onSectionEntry!(testInterview, iterationContext); - + expect(result).toEqual({ 'response._activeTripId': null, [`response.household.persons.${activePerson._uuid}.journeys.${activeJourney._uuid}.trips.tripId2`]: undefined, [`response.household.persons.${activePerson._uuid}.journeys.${activeJourney._uuid}.trips.tripId3`]: undefined, [`validations.household.persons.${activePerson._uuid}.journeys.${activeJourney._uuid}.trips.tripId2`]: undefined, - [`validations.household.persons.${activePerson._uuid}.journeys.${activeJourney._uuid}.trips.tripId3`]: undefined, - + [`validations.household.persons.${activePerson._uuid}.journeys.${activeJourney._uuid}.trips.tripId3`]: undefined }); }); @@ -390,13 +401,10 @@ describe('sectionConfig functionalities', () => { { _uuid: 'tripId1', _sequence: 1, _originVisitedPlaceUuid: 'testPlace1', _destinationVisitedPlaceUuid: 'oldPlace2' }, { _uuid: 'tripId2', _sequence: 2, _originVisitedPlaceUuid: 'oldPlace2', _destinationVisitedPlaceUuid: 'oldPlace3' } ]; - testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1.trips = { - tripId1: trips[0], - tripId2: trips[1] - }; + testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1.trips = { tripId1: trips[0], tripId2: trips[1] }; const result = widgetConfig.onSectionEntry!(testInterview, iterationContext); - + expect(result).toEqual({ 'response.household.persons.testPerson1.journeys.testJourney1.trips.tripId1._originVisitedPlaceUuid': places[0]._uuid, 'response.household.persons.testPerson1.journeys.testJourney1.trips.tripId1._destinationVisitedPlaceUuid': places[1]._uuid, @@ -426,20 +434,15 @@ describe('sectionConfig functionalities', () => { { _uuid: 'tripId1', _sequence: 1, _originVisitedPlaceUuid: 'testPlace1', _destinationVisitedPlaceUuid: 'testPlace2' }, { _uuid: 'tripId2', _sequence: 2, _originVisitedPlaceUuid: 'testPlace2', _destinationVisitedPlaceUuid: 'testPlace3' } ]; - testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1.trips = { - tripId1: trips[0], - tripId2: trips[1] - }; + testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1.trips = { tripId1: trips[0], tripId2: trips[1] }; // Trip2 is incomplete const incompleteTrip = trips[1]; mockedSelectNextIncompleteTrip.mockReturnValueOnce(incompleteTrip); const expectedJourney = _cloneDeep(testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1); const result = widgetConfig.onSectionEntry!(testInterview, iterationContext); - - expect(result).toEqual({ - 'response._activeTripId': incompleteTrip._uuid - }); + + expect(result).toEqual({ 'response._activeTripId': incompleteTrip._uuid }); expect(mockedSelectNextIncompleteTrip).toHaveBeenCalledWith({ journey: expectedJourney }); }); @@ -461,23 +464,17 @@ describe('sectionConfig functionalities', () => { { _uuid: 'tripId1', _sequence: 1, _originVisitedPlaceUuid: 'testPlace1', _destinationVisitedPlaceUuid: 'testPlace2' }, { _uuid: 'tripId2', _sequence: 2, _originVisitedPlaceUuid: 'testPlace2', _destinationVisitedPlaceUuid: 'testPlace3' } ]; - testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1.trips = { - tripId1: trips[0], - tripId2: trips[1] - }; + testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1.trips = { tripId1: trips[0], tripId2: trips[1] }; mockedSelectNextIncompleteTrip.mockReturnValueOnce(null); const expectedJourney = _cloneDeep(testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1); const result = widgetConfig.onSectionEntry!(testInterview, iterationContext); - - expect(result).toEqual({ - 'response._activeTripId': null - }); + + expect(result).toEqual({ 'response._activeTripId': null }); expect(mockedSelectNextIncompleteTrip).toHaveBeenCalledWith({ journey: expectedJourney }); }); test('should add a new trip and select it if new trips have been added since last complete trip', () => { - // 4 places const places = [ { _uuid: 'testPlace1', _sequence: 1, activity: 'home' }, @@ -493,25 +490,31 @@ describe('sectionConfig functionalities', () => { [places[2]._uuid]: places[2], [places[3]._uuid]: places[3] }; - + // only 1 trip, with different origins and destination, the second trip is missing - const trips = [ - { _uuid: 'tripId1', _sequence: 1, _originVisitedPlaceUuid: 'testPlace1', _destinationVisitedPlaceUuid: 'testPlace2' } - ]; - testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1.trips = { - tripId1: trips[0] - }; + const trips = [{ _uuid: 'tripId1', _sequence: 1, _originVisitedPlaceUuid: 'testPlace1', _destinationVisitedPlaceUuid: 'testPlace2' }]; + testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1.trips = { tripId1: trips[0] }; mockedSelectNextIncompleteTrip.mockReturnValueOnce(null); const expectedJourney = _cloneDeep(testInterview.response.household!.persons!.testPerson1.journeys!.testJourney1); // Should add a new trip mockUuidv4.mockReturnValueOnce('tripId2' as any); mockUuidv4.mockReturnValueOnce('tripId3' as any); - const newTrip2 = { _uuid: 'tripId2', _sequence: 2, _originVisitedPlaceUuid: places[1]._uuid, _destinationVisitedPlaceUuid: places[2]._uuid }; - const newTrip3 = { _uuid: 'tripId3', _sequence: 3, _originVisitedPlaceUuid: places[2]._uuid, _destinationVisitedPlaceUuid: places[3]._uuid }; + const newTrip2 = { + _uuid: 'tripId2', + _sequence: 2, + _originVisitedPlaceUuid: places[1]._uuid, + _destinationVisitedPlaceUuid: places[2]._uuid + }; + const newTrip3 = { + _uuid: 'tripId3', + _sequence: 3, + _originVisitedPlaceUuid: places[2]._uuid, + _destinationVisitedPlaceUuid: places[3]._uuid + }; const result = widgetConfig.onSectionEntry!(testInterview, iterationContext); - + expect(result).toEqual({ 'response.household.persons.testPerson1.journeys.testJourney1.trips.tripId2': newTrip2, 'validations.household.persons.testPerson1.journeys.testJourney1.trips.tripId2': {}, @@ -523,5 +526,4 @@ describe('sectionConfig functionalities', () => { expect(mockUuidv4).toHaveBeenCalledTimes(2); }); }); - }); diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetPersonTripsTitle.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetPersonTripsTitle.test.ts index c9c55e636..0fdbe3418 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetPersonTripsTitle.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetPersonTripsTitle.test.ts @@ -27,27 +27,18 @@ beforeEach(() => { describe('getPersonsTripsTitleWidgetConfig', () => { it('should return the correct widget config', () => { - const widgetConfig = getPersonsTripsTitleWidgetConfig(widgetFactoryOptions); - expect(widgetConfig).toEqual({ - type: 'text', - align: 'left', - text: expect.any(Function) - }); + expect(widgetConfig).toEqual({ type: 'text', align: 'left', text: expect.any(Function) }); }); }); describe('personsTripsTitleWidgetConfig text', () => { - - const options = { - ...widgetFactoryOptions, - context: jest.fn().mockImplementation((context: string) => context) - }; + const options = { ...widgetFactoryOptions, context: jest.fn().mockImplementation((context: string) => context) }; const widgetText = getPersonsTripsTitleWidgetConfig(options).text as any; const mockedT = jest.fn().mockReturnValue('translatedString'); - + test('should call translation with correct parameters if no active person', () => { mockedGetActivePerson.mockReturnValueOnce(null); mockedGetActiveJourney.mockReturnValueOnce(null); @@ -62,7 +53,7 @@ describe('personsTripsTitleWidgetConfig text', () => { }); test('should call translation with correct parameters if no active journey', () => { - const nickname = 'Jane' + const nickname = 'Jane'; mockedGetActivePerson.mockReturnValueOnce({ _uuid: 'person1', _sequence: 1, nickname }); mockedGetActiveJourney.mockReturnValueOnce(null); expect(widgetText(mockedT, interviewAttributesForTestCases, 'path')).toEqual('translatedString'); @@ -89,7 +80,7 @@ describe('personsTripsTitleWidgetConfig text', () => { }); test('should call translation with correct parameters if multiple person household and journey with start date', () => { - const nickname = 'Jane' + const nickname = 'Jane'; mockedGetActivePerson.mockReturnValueOnce({ _uuid: 'person1', _sequence: 1, nickname }); mockedGetActiveJourney.mockReturnValueOnce({ _uuid: 'journey1', _sequence: 1, startDate: '2024-11-18' }); mockedGetCountOrSelfDeclared.mockReturnValueOnce(2); @@ -102,5 +93,4 @@ describe('personsTripsTitleWidgetConfig text', () => { }); expect(options.context).toHaveBeenCalledWith(undefined); }); - }); diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSameAsReverseTrip.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSameAsReverseTrip.test.ts index d29e47e50..444ed5320 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSameAsReverseTrip.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSameAsReverseTrip.test.ts @@ -15,15 +15,13 @@ import * as surveyHelper from '../../../../odSurvey/helpers'; import { shouldShowSameAsReverseTripQuestion, getPreviousTripSingleSegment } from '../helpers'; -jest.mock('../helpers', () => ({ - shouldShowSameAsReverseTripQuestion: jest.fn(), - getPreviousTripSingleSegment: jest.fn() -})); -const mockedShouldShowSameAsReverseTripQuestion = shouldShowSameAsReverseTripQuestion as jest.MockedFunction; +jest.mock('../helpers', () => ({ shouldShowSameAsReverseTripQuestion: jest.fn(), getPreviousTripSingleSegment: jest.fn() })); +const mockedShouldShowSameAsReverseTripQuestion = shouldShowSameAsReverseTripQuestion as jest.MockedFunction< + typeof shouldShowSameAsReverseTripQuestion +>; const mockedGetPreviousTripSingleSegment = getPreviousTripSingleSegment as jest.MockedFunction; describe('getSameAsReverseTripWidgetConfig', () => { - test('should return the correct widget config', () => { const widgetConfig = getSameAsReverseTripWidgetConfig(); expect(widgetConfig).toEqual({ @@ -34,20 +32,13 @@ describe('getSameAsReverseTripWidgetConfig', () => { datatype: 'boolean', label: expect.any(Function), choices: [ - expect.objectContaining({ - value: true, - label: expect.any(Function) - }), - expect.objectContaining({ - value: false, - label: expect.any(Function), - }), + expect.objectContaining({ value: true, label: expect.any(Function) }), + expect.objectContaining({ value: false, label: expect.any(Function) }) ], validations: expect.any(Function), conditional: expect.any(Function) }); }); - }); describe('sameAsReverseTripWidget choice labels', () => { @@ -68,7 +59,6 @@ describe('sameAsReverseTripWidget choice labels', () => { }); describe('sameAsReverseTripWidget conditional', () => { - const widgetConfig = getSameAsReverseTripWidgetConfig({}) as QuestionWidgetConfig & InputRadioType; const conditional = widgetConfig.conditional; const segmentPath = 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1'; @@ -95,7 +85,6 @@ describe('sameAsReverseTripWidget conditional', () => { }); describe('sameAsReverseTripWidget validations', () => { - // Prepare test data with active person/journey/trip const interview = _cloneDeep(interviewAttributesForTestCases); @@ -103,25 +92,46 @@ describe('sameAsReverseTripWidget validations', () => { const validations = widgetConfig.validations; test('Valid response', () => { - expect(validations!(true, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.sameModeAsReverseTrip')) - .toEqual([{ validation: false, errorMessage: expect.anything() }]); - expect(validations!(false, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.sameModeAsReverseTrip')) - .toEqual([{ validation: false, errorMessage: expect.anything() }]); + expect( + validations!( + true, + null, + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.sameModeAsReverseTrip' + ) + ).toEqual([{ validation: false, errorMessage: expect.anything() }]); + expect( + validations!( + false, + null, + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.sameModeAsReverseTrip' + ) + ).toEqual([{ validation: false, errorMessage: expect.anything() }]); }); test('empty response', () => { - expect(validations!(null, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.sameModeAsReverseTrip')) - .toEqual([{ validation: true, errorMessage: expect.anything() }]); + expect( + validations!( + null, + null, + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.sameModeAsReverseTrip' + ) + ).toEqual([{ validation: true, errorMessage: expect.anything() }]); }); test('should return the right error message', () => { - const validation = validations!(null, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.sameModeAsReverseTrip'); + const validation = validations!( + null, + null, + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.sameModeAsReverseTrip' + ); const mockedT = jest.fn(); translateString(validation[0].errorMessage, { t: mockedT } as any, interview, 'path'); expect(mockedT).toHaveBeenCalledWith(['customSurvey:ResponseIsRequired', 'survey:ResponseIsRequired']); }); - - }); describe('sameAsReverseTripWidget label', () => { @@ -145,7 +155,13 @@ describe('sameAsReverseTripWidget label', () => { baseTestInterview.response._activeTripId = 'tripId2P1'; // Return the previous segment - mockedGetPreviousTripSingleSegment.mockReturnValueOnce({ _isNew: false, modePre: 'walk', mode: 'walk', _uuid: 'segmentId1P1T1', _sequence: 1 }); + mockedGetPreviousTripSingleSegment.mockReturnValueOnce({ + _isNew: false, + modePre: 'walk', + mode: 'walk', + _uuid: 'segmentId1P1T1', + _sequence: 1 + }); // Add a context const context = 'currentContext'; diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentHasNextMode.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentHasNextMode.test.ts index d6cafb9c4..8f47ef2f5 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentHasNextMode.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentHasNextMode.test.ts @@ -12,19 +12,15 @@ import { getResponse, setResponse, translateString } from '../../../../../utils/ import * as surveyHelper from '../../../../odSurvey/helpers'; import { shouldShowSameAsReverseTripQuestion, getPreviousTripSingleSegment } from '../helpers'; -jest.mock('../helpers', () => ({ - shouldShowSameAsReverseTripQuestion: jest.fn().mockReturnValue(false), - getPreviousTripSingleSegment: jest.fn() -})); -const mockedShouldShowSameAsReverseTripQuestion = shouldShowSameAsReverseTripQuestion as jest.MockedFunction; +jest.mock('../helpers', () => ({ shouldShowSameAsReverseTripQuestion: jest.fn().mockReturnValue(false), getPreviousTripSingleSegment: jest.fn() })); +const mockedShouldShowSameAsReverseTripQuestion = shouldShowSameAsReverseTripQuestion as jest.MockedFunction< + typeof shouldShowSameAsReverseTripQuestion +>; const mockedGetPreviousTripSingleSegment = getPreviousTripSingleSegment as jest.MockedFunction; describe('getSegmentHasNextModeWidgetConfig', () => { it('should return the correct widget config', () => { - - const options = { - context: jest.fn() - }; + const options = { context: jest.fn() }; const widgetConfig = getSegmentHasNextModeWidgetConfig(options); @@ -36,14 +32,8 @@ describe('getSegmentHasNextModeWidgetConfig', () => { datatype: 'boolean', label: expect.any(Function), choices: [ - expect.objectContaining({ - value: true, - label: expect.any(Function) - }), - expect.objectContaining({ - value: false, - label: expect.any(Function), - }), + expect.objectContaining({ value: true, label: expect.any(Function) }), + expect.objectContaining({ value: false, label: expect.any(Function) }) ], validations: expect.any(Function), conditional: expect.any(Function) @@ -59,22 +49,28 @@ describe('segmentHasNextMode validations', () => { const validations = widgetConfig.validations; test('should return no error if value is not empty', () => { - expect(validations!(true, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.hasNextMode')) - .toEqual([{ validation: false, errorMessage: expect.anything() }]); + expect( + validations!(true, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.hasNextMode') + ).toEqual([{ validation: false, errorMessage: expect.anything() }]); }); test('should return an error if value is empty', () => { - expect(validations!(null, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.hasNextMode')) - .toEqual([{ validation: true, errorMessage: expect.anything() }]); + expect( + validations!(null, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.hasNextMode') + ).toEqual([{ validation: true, errorMessage: expect.anything() }]); }); test('should return the right error message', () => { - const validation = validations!('carDriver', null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.hasNextMode'); + const validation = validations!( + 'carDriver', + null, + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.hasNextMode' + ); const mockedT = jest.fn(); translateString(validation[0].errorMessage, { t: mockedT } as any, interview, 'path'); expect(mockedT).toHaveBeenCalledWith(['customSurvey:ResponseIsRequired', 'survey:ResponseIsRequired']); }); - }); describe('segmentHasNextMode conditional', () => { @@ -205,7 +201,11 @@ describe('segmentHasNextMode label', () => { count: 1 }); expect(mockedT).toHaveBeenCalledTimes(2); - expect(mockedGetPlaceName).toHaveBeenCalledWith({ t: mockedT, visitedPlace: interview.response.household!.persons!.personId2!.journeys!.journeyId2!.visitedPlaces!.otherWorkPlace1P2, interview }); + expect(mockedGetPlaceName).toHaveBeenCalledWith({ + t: mockedT, + visitedPlace: interview.response.household!.persons!.personId2!.journeys!.journeyId2!.visitedPlaces!.otherWorkPlace1P2, + interview + }); expect(mockedGetCountOrSelfDeclared).toHaveBeenCalledWith({ interview, person: interview.response.household!.persons!.personId2 }); }); @@ -227,14 +227,7 @@ describe('segmentHasNextMode label', () => { _sequence: 2, _originVisitedPlaceUuid: 'unexistingOrigin', _destinationVisitedPlaceUuid: 'unexistingDestination', - segments: { - segmentId1P2T2: { - _uuid: 'segmentId1P2T2', - _isNew: false, - _sequence: 1, - modePre: 'walk' - } - } + segments: { segmentId1P2T2: { _uuid: 'segmentId1P2T2', _isNew: false, _sequence: 1, modePre: 'walk' } } }; // Test label function @@ -279,7 +272,7 @@ describe('segmentHasNextMode label', () => { test('undefined context function', () => { // New widget config without context function - const testWidgetConfig = getSegmentHasNextModeWidgetConfig({ }) as QuestionWidgetConfig & InputRadioType; + const testWidgetConfig = getSegmentHasNextModeWidgetConfig({}) as QuestionWidgetConfig & InputRadioType; const label = testWidgetConfig.label; // Prepare mocked data diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentMode.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentMode.test.ts index 9b6f6f302..2714050b8 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentMode.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentMode.test.ts @@ -16,26 +16,21 @@ import { Mode, ModePre, modePreToModeMap, modeToModePreMap, modeValues } from '. import { shouldShowSameAsReverseTripQuestion, getPreviousTripSingleSegment } from '../helpers'; import { modeToIconMapping } from '../modeIconMapping'; -const segmentSectionConfig = { - type: 'segments' as const, - enabled: true -}; +const segmentSectionConfig = { type: 'segments' as const, enabled: true }; jest.mock('../helpers', () => ({ ...jest.requireActual('../helpers'), shouldShowSameAsReverseTripQuestion: jest.fn().mockReturnValue(false), getPreviousTripSingleSegment: jest.fn() })); -const mockedShouldShowSameAsReverseTripQuestion = shouldShowSameAsReverseTripQuestion as jest.MockedFunction; +const mockedShouldShowSameAsReverseTripQuestion = shouldShowSameAsReverseTripQuestion as jest.MockedFunction< + typeof shouldShowSameAsReverseTripQuestion +>; const mockedGetPreviousTripSingleSegment = getPreviousTripSingleSegment as jest.MockedFunction; describe('getModeWidgetConfig', () => { it('should return the correct widget config', () => { - - const options = { - ...widgetFactoryOptions, - context: jest.fn() - }; + const options = { ...widgetFactoryOptions, context: jest.fn() }; const widgetConfig = getModeWidgetConfig(segmentSectionConfig, options); @@ -48,12 +43,14 @@ describe('getModeWidgetConfig', () => { iconSize: '2.25em', columns: 2, label: expect.any(Function), - choices: modeValues.map((mode) => expect.objectContaining({ - value: mode, - label: expect.any(Function), - conditional: expect.any(Function), - iconPath: modeToIconMapping[mode] - })), + choices: modeValues.map((mode) => + expect.objectContaining({ + value: mode, + label: expect.any(Function), + conditional: expect.any(Function), + iconPath: modeToIconMapping[mode] + }) + ), validations: expect.any(Function), conditional: expect.any(Function) }); @@ -69,7 +66,9 @@ describe('Mode choices conditionals', () => { interview.response._activePersonId = 'personId1'; interview.response._activeJourneyId = 'journeyId1'; interview.response._activeTripId = 'tripId1P1'; - setResponse(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments', { segmentId1P1T1: { _uuid: 'segmentId1P1T1', _sequence: 1 } }); + setResponse(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments', { + segmentId1P1T1: { _uuid: 'segmentId1P1T1', _sequence: 1 } + }); // Spy on a few functions to return disability conditions jest.spyOn(surveyHelper, 'personMayHaveDisability'); @@ -85,62 +84,88 @@ describe('Mode choices conditionals', () => { // modePreToModeMap to get the values, but filter out the modes that have // specific conditionals and that will be tested separately each( - modeValues.filter((mode) => !['wheelchair', 'mobilityScooter', 'paratransit'].includes(mode)).flatMap((mode) => Object.keys(modePreToModeMap).map((modePre) => [mode, modePre, modeToModePreMap[mode].includes(modePre as any)])) + modeValues + .filter((mode) => !['wheelchair', 'mobilityScooter', 'paratransit'].includes(mode)) + .flatMap((mode) => Object.keys(modePreToModeMap).map((modePre) => [mode, modePre, modeToModePreMap[mode].includes(modePre as any)])) ).test('Test modePre conditional for mode %s with modePre %s: %s', (choiceValue, modePreValue, expected) => { // Find the right choice choice const modeChoice = choices.find((choice) => choice.value === choiceValue); expect(modeChoice).toBeDefined(); setResponse(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.modePre', modePreValue); - const modeResult = modeChoice?.conditional?.(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.mode'); + const modeResult = modeChoice?.conditional?.( + interview, + 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.mode' + ); expect(modeResult).toEqual(expected); }); // Test specific conditional for modes, where person may have disability each( - ['wheelchair' as Mode, 'mobilityScooter' as Mode].flatMap((mode: Mode) => Object.keys(modePreToModeMap).flatMap((modePre) => [[true, mode, modePre, modeToModePreMap[mode].includes(modePre as ModePre)], [false, mode, modePre, modeToModePreMap[mode].includes(modePre as ModePre)]])) - ).test('Test modePre with person may have disability (%s) conditional for mode %s with modePre %s: %s', (personMayHaveDisability, choiceValue, modePreValue, expectedIfTrue) => { - // Spy on the personMayHaveDisability function - if (expectedIfTrue) { - mockedPersonMayHaveDisability.mockReturnValueOnce(personMayHaveDisability); + ['wheelchair' as Mode, 'mobilityScooter' as Mode].flatMap((mode: Mode) => + Object.keys(modePreToModeMap).flatMap((modePre) => [ + [true, mode, modePre, modeToModePreMap[mode].includes(modePre as ModePre)], + [false, mode, modePre, modeToModePreMap[mode].includes(modePre as ModePre)] + ]) + ) + ).test( + 'Test modePre with person may have disability (%s) conditional for mode %s with modePre %s: %s', + (personMayHaveDisability, choiceValue, modePreValue, expectedIfTrue) => { + // Spy on the personMayHaveDisability function + if (expectedIfTrue) { + mockedPersonMayHaveDisability.mockReturnValueOnce(personMayHaveDisability); + } + + // Find the right choice choice + const modeChoice = choices.find((choice) => choice.value === choiceValue); + expect(modeChoice).toBeDefined(); + // Set the mode pre value + setResponse(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.modePre', modePreValue); + const modeResult = modeChoice?.conditional?.( + interview, + 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.mode' + ); + expect(modeResult).toEqual(personMayHaveDisability ? expectedIfTrue : false); + if (expectedIfTrue) { + expect(mockedPersonMayHaveDisability).toHaveBeenCalledWith({ person: interview.response.household!.persons!.personId1 }); + } else { + expect(mockedPersonMayHaveDisability).not.toHaveBeenCalled(); + } } - - // Find the right choice choice - const modeChoice = choices.find((choice) => choice.value === choiceValue); - expect(modeChoice).toBeDefined(); - // Set the mode pre value - setResponse(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.modePre', modePreValue); - const modeResult = modeChoice?.conditional?.(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.mode'); - expect(modeResult).toEqual(personMayHaveDisability ? expectedIfTrue : false); - if (expectedIfTrue) { - expect(mockedPersonMayHaveDisability).toHaveBeenCalledWith({ person: interview.response.household!.persons!.personId1 }); - } else { - expect(mockedPersonMayHaveDisability).not.toHaveBeenCalled(); - } - }); + ); // Test specific conditional for modes, where they may be disabilities in the household each( - ['paratransit' as Mode].flatMap((mode: Mode) => Object.keys(modePreToModeMap).flatMap((modePre) => [[true, mode, modePre, modeToModePreMap[mode].includes(modePre as ModePre)], [false, mode, modePre, modeToModePreMap[mode].includes(modePre as ModePre)]])) - ).test('Test modePre with hh may have disability (%s) conditional for mode %s with modePre %s: %s', (hhMayHaveDisability, choiceValue, modePreValue, expectedIfTrue) => { - // Spy on the householdMayHaveDisability function - if (expectedIfTrue) { - mockedHhMayHaveDisability.mockReturnValueOnce(hhMayHaveDisability); - } - - // Find the right choice choice - const modeChoice = choices.find((choice) => choice.value === choiceValue); - expect(modeChoice).toBeDefined(); - // Set the mode pre value - setResponse(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.modePre', modePreValue); - const modeResult = modeChoice?.conditional?.(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.mode'); - expect(modeResult).toEqual(hhMayHaveDisability ? expectedIfTrue : false); - if (expectedIfTrue) { - expect(mockedHhMayHaveDisability).toHaveBeenCalledWith({ interview }); - } else { - expect(mockedHhMayHaveDisability).not.toHaveBeenCalled(); + ['paratransit' as Mode].flatMap((mode: Mode) => + Object.keys(modePreToModeMap).flatMap((modePre) => [ + [true, mode, modePre, modeToModePreMap[mode].includes(modePre as ModePre)], + [false, mode, modePre, modeToModePreMap[mode].includes(modePre as ModePre)] + ]) + ) + ).test( + 'Test modePre with hh may have disability (%s) conditional for mode %s with modePre %s: %s', + (hhMayHaveDisability, choiceValue, modePreValue, expectedIfTrue) => { + // Spy on the householdMayHaveDisability function + if (expectedIfTrue) { + mockedHhMayHaveDisability.mockReturnValueOnce(hhMayHaveDisability); + } + + // Find the right choice choice + const modeChoice = choices.find((choice) => choice.value === choiceValue); + expect(modeChoice).toBeDefined(); + // Set the mode pre value + setResponse(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.modePre', modePreValue); + const modeResult = modeChoice?.conditional?.( + interview, + 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.mode' + ); + expect(modeResult).toEqual(hhMayHaveDisability ? expectedIfTrue : false); + if (expectedIfTrue) { + expect(mockedHhMayHaveDisability).toHaveBeenCalledWith({ interview }); + } else { + expect(mockedHhMayHaveDisability).not.toHaveBeenCalled(); + } } - }); - + ); }); describe('Mode choices labels', () => { @@ -149,16 +174,16 @@ describe('Mode choices labels', () => { const widgetConfig = getModeWidgetConfig(segmentSectionConfig, widgetFactoryOptions) as QuestionWidgetConfig & InputRadioType; const choices = widgetConfig.choices as RadioChoiceType[]; - each( - modeValues.map((mode) => [mode, [`customSurvey:segments:mode:${_upperFirst(mode)}`, `segments:mode:${_upperFirst(mode)}`]]) - ).test('should return the right label for %s choice', (choiceValue, expectedLabel) => { - const mockedT = jest.fn(); - const choice = choices.find((choice) => choice.value === choiceValue); - expect(choice).toBeDefined(); - translateString(choice?.label, { t: mockedT } as any, interview, 'path'); - expect(mockedT).toHaveBeenCalledWith(expectedLabel); - }); - + each(modeValues.map((mode) => [mode, [`customSurvey:segments:mode:${_upperFirst(mode)}`, `segments:mode:${_upperFirst(mode)}`]])).test( + 'should return the right label for %s choice', + (choiceValue, expectedLabel) => { + const mockedT = jest.fn(); + const choice = choices.find((choice) => choice.value === choiceValue); + expect(choice).toBeDefined(); + translateString(choice?.label, { t: mockedT } as any, interview, 'path'); + expect(mockedT).toHaveBeenCalledWith(expectedLabel); + } + ); }); describe('Mode validations', () => { @@ -169,22 +194,28 @@ describe('Mode validations', () => { const validations = widgetConfig.validations; test('should return no error if value is not empty', () => { - expect(validations!(null, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.mode')) - .toEqual([{ validation: true, errorMessage: expect.anything() }]); + expect( + validations!(null, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.mode') + ).toEqual([{ validation: true, errorMessage: expect.anything() }]); }); test('should return an error if value is empty', () => { - expect(validations!('carDriver', null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.mode')) - .toEqual([{ validation: false, errorMessage: expect.anything() }]); + expect( + validations!('carDriver', null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.mode') + ).toEqual([{ validation: false, errorMessage: expect.anything() }]); }); test('should return the right error message', () => { - const validation = validations!('carDriver', null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.mode'); + const validation = validations!( + 'carDriver', + null, + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.mode' + ); const mockedT = jest.fn(); translateString(validation[0].errorMessage, { t: mockedT } as any, interview, 'path'); expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:ModeIsRequired', 'segments:ModeIsRequired']); }); - }); describe('Mode conditional', () => { @@ -301,7 +332,8 @@ describe('Widget label', () => { const mockedGetContext = jest.fn(); // Prepare common data - const widgetConfig = getModeWidgetConfig(segmentSectionConfig, { ...widgetFactoryOptions, context: mockedGetContext }) as QuestionWidgetConfig & InputRadioType; + const widgetConfig = getModeWidgetConfig(segmentSectionConfig, { ...widgetFactoryOptions, context: mockedGetContext }) as QuestionWidgetConfig & + InputRadioType; const label = widgetConfig.label; const p2t2segmentsPath = 'household.persons.personId2.journeys.journeyId2.trips.tripId2P2.segments'; @@ -322,9 +354,7 @@ describe('Widget label', () => { // Test label function translateString(label, { t: mockedT } as any, interview, `${p2t2segmentsPath}.segmentId1P2T2.mode`); - expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:ModeSpecify', 'segments:ModeSpecify'], { - context - }); + expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:ModeSpecify', 'segments:ModeSpecify'], { context }); expect(mockedT).toHaveBeenCalledTimes(1); }); @@ -337,9 +367,7 @@ describe('Widget label', () => { // Test label function translateString(label, { t: mockedT } as any, interview, `${p2t2segmentsPath}.segmentId1P2T2.mode`); - expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:ModeSpecify', 'segments:ModeSpecify'], { - context: undefined - }); + expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:ModeSpecify', 'segments:ModeSpecify'], { context: undefined }); expect(mockedT).toHaveBeenCalledTimes(1); }); }); @@ -363,11 +391,7 @@ describe('Mode filtering based on configuration', () => { test('should exclude configured modes when modesExclude is set', () => { const excludedModes = ['plane', 'ferryWithCar', 'snowmobile'] as Mode[]; - const segmentConfig = { - type: 'segments' as const, - enabled: true, - modesExclude: excludedModes - }; + const segmentConfig = { type: 'segments' as const, enabled: true, modesExclude: excludedModes }; const widgetConfig = getModeWidgetConfig(segmentConfig, widgetFactoryOptions) as QuestionWidgetConfig & InputRadioType; const choices = widgetConfig.choices as RadioChoiceType[]; @@ -420,11 +444,7 @@ describe('Mode filtering based on configuration', () => { }); test('should throw an error when there is no mode', () => { - const segmentConfig = { - type: 'segments' as const, - enabled: true, - modesIncludeOnly: [] as Mode[] - }; + const segmentConfig = { type: 'segments' as const, enabled: true, modesIncludeOnly: [] as Mode[] }; expect(() => { getModeWidgetConfig(segmentConfig, widgetFactoryOptions); }).toThrow('No available modes to create mode widget configuration'); diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentModePre.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentModePre.test.ts index da6a2709e..a765224c3 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentModePre.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetSegmentModePre.test.ts @@ -14,26 +14,21 @@ import * as surveyHelper from '../../../../odSurvey/helpers'; import { shouldShowSameAsReverseTripQuestion, getPreviousTripSingleSegment } from '../helpers'; import { Mode } from '../../../../odSurvey/types'; -const segmentSectionConfig = { - type: 'segments' as const, - enabled: true -}; +const segmentSectionConfig = { type: 'segments' as const, enabled: true }; jest.mock('../helpers', () => ({ ...jest.requireActual('../helpers'), shouldShowSameAsReverseTripQuestion: jest.fn(), getPreviousTripSingleSegment: jest.fn() })); -const mockedShouldShowSameAsReverseTripQuestion = shouldShowSameAsReverseTripQuestion as jest.MockedFunction; +const mockedShouldShowSameAsReverseTripQuestion = shouldShowSameAsReverseTripQuestion as jest.MockedFunction< + typeof shouldShowSameAsReverseTripQuestion +>; const mockedGetPreviousTripSingleSegment = getPreviousTripSingleSegment as jest.MockedFunction; describe('getModePreWidgetConfig', () => { it('should return the correct widget config', () => { - - const options = { - ...widgetFactoryOptions, - context: jest.fn() - }; + const options = { ...widgetFactoryOptions, context: jest.fn() }; const widgetConfig = getModePreWidgetConfig(segmentSectionConfig, options); @@ -53,52 +48,28 @@ describe('getModePreWidgetConfig', () => { conditional: expect.any(Function), iconPath: '/dist/icons/modes/car/car_driver_without_passenger.svg' }), - expect.objectContaining({ - value: 'carPassenger', - label: expect.any(Function), - iconPath: '/dist/icons/modes/car/car_passenger.svg' - }), - expect.objectContaining({ - value: 'walk', - label: expect.any(Function), - iconPath: '/dist/icons/modes/foot/foot.svg' - }), + expect.objectContaining({ value: 'carPassenger', label: expect.any(Function), iconPath: '/dist/icons/modes/car/car_passenger.svg' }), + expect.objectContaining({ value: 'walk', label: expect.any(Function), iconPath: '/dist/icons/modes/foot/foot.svg' }), expect.objectContaining({ value: 'bicycle', label: expect.any(Function), iconPath: '/dist/icons/modes/bicycle/bicycle_with_rider.svg' }), - expect.objectContaining({ - value: 'transit', - label: expect.any(Function), - iconPath: '/dist/icons/modes/bus/bus_city.svg' - }), + expect.objectContaining({ value: 'transit', label: expect.any(Function), iconPath: '/dist/icons/modes/bus/bus_city.svg' }), expect.objectContaining({ value: 'taxi', label: expect.any(Function), iconPath: '/dist/icons/modes/taxi/taxi_no_steering_wheel.svg' }), - expect.objectContaining({ - value: 'ferry', - label: expect.any(Function), - iconPath: '/dist/icons/modes/boat/ferry_without_car.svg' - }), + expect.objectContaining({ value: 'ferry', label: expect.any(Function), iconPath: '/dist/icons/modes/boat/ferry_without_car.svg' }), expect.objectContaining({ value: 'paratransit', label: expect.any(Function), conditional: expect.any(Function), iconPath: '/dist/icons/modes/minibus/minibus_with_wheelchair.svg' }), - expect.objectContaining({ - value: 'other', - label: expect.any(Function), - iconPath: '/dist/icons/modes/other/air_balloon.svg' - }), - expect.objectContaining({ - value: 'dontKnow', - label: expect.any(Function), - iconPath: '/dist/icons/modes/other/question_mark.svg' - }), + expect.objectContaining({ value: 'other', label: expect.any(Function), iconPath: '/dist/icons/modes/other/air_balloon.svg' }), + expect.objectContaining({ value: 'dontKnow', label: expect.any(Function), iconPath: '/dist/icons/modes/other/question_mark.svg' }), expect.objectContaining({ value: 'preferNotToAnswer', label: expect.any(Function), @@ -129,7 +100,10 @@ describe('Mode choices conditionals', () => { // Find the carDriver choice const carDriverChoice = choices.find((choice) => choice.value === 'carDriver'); expect(carDriverChoice).toBeDefined(); - const carDriverResult = carDriverChoice?.conditional?.(interview, 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.modePre'); + const carDriverResult = carDriverChoice?.conditional?.( + interview, + 'household.persons.personId1.journeys.journeyId1.trips.tripId1P1.segments.segmentId1P1T1.modePre' + ); expect(carDriverResult).toEqual(true); }); @@ -143,7 +117,10 @@ describe('Mode choices conditionals', () => { // Find the carDriver choice const carDriverChoice = choices.find((choice) => choice.value === 'carDriver'); expect(carDriverChoice).toBeDefined(); - const carDriverResult = carDriverChoice?.conditional?.(interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre'); + const carDriverResult = carDriverChoice?.conditional?.( + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre' + ); expect(carDriverResult).toEqual(false); }); @@ -157,7 +134,10 @@ describe('Mode choices conditionals', () => { // Find the carDriver choice const carDriverChoice = choices.find((choice) => choice.value === 'carDriver'); expect(carDriverChoice).toBeDefined(); - const carDriverResult = carDriverChoice?.conditional?.(interview, 'household.persons.personId3.journeys.journeyId3.trips.tripId1P2.segments.segmentId1P3T1.modePre'); + const carDriverResult = carDriverChoice?.conditional?.( + interview, + 'household.persons.personId3.journeys.journeyId3.trips.tripId1P2.segments.segmentId1P3T1.modePre' + ); expect(carDriverResult).toEqual(true); }); @@ -172,7 +152,10 @@ describe('Mode choices conditionals', () => { // Find the carDriver choice const carDriverChoice = choices.find((choice) => choice.value === 'carDriver'); expect(carDriverChoice).toBeDefined(); - const carDriverResult = carDriverChoice?.conditional?.(interview, 'household.persons.personId3.journeys.journeyId3.trips.tripId1P2.segments.segmentId1P3T1.modePre'); + const carDriverResult = carDriverChoice?.conditional?.( + interview, + 'household.persons.personId3.journeys.journeyId3.trips.tripId1P2.segments.segmentId1P3T1.modePre' + ); expect(carDriverResult).toEqual(false); }); @@ -183,7 +166,10 @@ describe('Mode choices conditionals', () => { // Find the carDriver choice const carDriverChoice = choices.find((choice) => choice.value === 'carDriver'); expect(carDriverChoice).toBeDefined(); - const carDriverResult = carDriverChoice?.conditional?.(interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre'); + const carDriverResult = carDriverChoice?.conditional?.( + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre' + ); expect(carDriverResult).toEqual(true); }); @@ -195,7 +181,10 @@ describe('Mode choices conditionals', () => { // Find the paratransit choice const paratransitChoice = choices.find((choice) => choice.value === 'paratransit'); expect(paratransitChoice).toBeDefined(); - const paratransitResult = paratransitChoice?.conditional?.(interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre'); + const paratransitResult = paratransitChoice?.conditional?.( + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre' + ); expect(paratransitResult).toEqual(true); expect(mockedHhMayHaveDisability).toHaveBeenLastCalledWith({ interview }); }); @@ -208,11 +197,13 @@ describe('Mode choices conditionals', () => { // Find the paratransit choice const paratransitChoice = choices.find((choice) => choice.value === 'paratransit'); expect(paratransitChoice).toBeDefined(); - const paratransitResult = paratransitChoice?.conditional?.(interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre'); + const paratransitResult = paratransitChoice?.conditional?.( + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre' + ); expect(paratransitResult).toEqual(false); expect(mockedHhMayHaveDisability).toHaveBeenLastCalledWith({ interview }); }); - }); describe('Mode choices labels', () => { @@ -251,7 +242,6 @@ describe('Mode choices labels', () => { translateString(choice?.label, { t: mockedT } as any, interview, 'path'); expect(mockedT).toHaveBeenCalledWith(['customSurvey:segments:modePre:WalkOrMobilityHelp', 'segments:modePre:WalkOrMobilityHelp']); }); - }); describe('Mode validations', () => { @@ -262,22 +252,33 @@ describe('Mode validations', () => { const validations = widgetConfig.validations; test('should return an error if value is empty', () => { - expect(validations!(null, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre')) - .toEqual([{ validation: true, errorMessage: expect.anything() }]); + expect( + validations!(null, null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre') + ).toEqual([{ validation: true, errorMessage: expect.anything() }]); }); test('should return no error if value is not empty', () => { - expect(validations!('carDriver', null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre')) - .toEqual([{ validation: false, errorMessage: expect.anything() }]); + expect( + validations!( + 'carDriver', + null, + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre' + ) + ).toEqual([{ validation: false, errorMessage: expect.anything() }]); }); test('should return the right error message', () => { - const validation = validations!('carDriver', null, interview, 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre'); + const validation = validations!( + 'carDriver', + null, + interview, + 'household.persons.personId2.journeys.journeyId2.trips.tripId1P2.segments.segmentId1P2T1.modePre' + ); const mockedT = jest.fn(); translateString(validation[0].errorMessage, { t: mockedT } as any, interview, 'path'); expect(mockedT).toHaveBeenCalledWith(['survey:segments:ModeIsRequired', 'segments:ModeIsRequired']); }); - }); describe('ModePre conditional', () => { @@ -362,7 +363,10 @@ describe('ModePre label', () => { const mockedGetContext = jest.fn(); // Prepare common data - const widgetConfig = getModePreWidgetConfig(segmentSectionConfig, { ...widgetFactoryOptions, context: mockedGetContext }) as QuestionWidgetConfig & InputRadioType; + const widgetConfig = getModePreWidgetConfig(segmentSectionConfig, { + ...widgetFactoryOptions, + context: mockedGetContext + }) as QuestionWidgetConfig & InputRadioType; const label = widgetConfig.label; const p2t2segmentsPath = 'household.persons.personId2.journeys.journeyId2.trips.tripId2P2.segments'; @@ -393,8 +397,16 @@ describe('ModePre label', () => { count: 1 }); expect(mockedT).toHaveBeenCalledTimes(1); - expect(mockedGetPlaceName).toHaveBeenCalledWith({ t: mockedT, visitedPlace: interview.response.household!.persons!.personId2!.journeys!.journeyId2!.visitedPlaces!.shoppingPlace1P2, interview }); - expect(mockedGetPlaceName).toHaveBeenCalledWith({ t: mockedT, visitedPlace: interview.response.household!.persons!.personId2!.journeys!.journeyId2!.visitedPlaces!.otherWorkPlace1P2, interview }); + expect(mockedGetPlaceName).toHaveBeenCalledWith({ + t: mockedT, + visitedPlace: interview.response.household!.persons!.personId2!.journeys!.journeyId2!.visitedPlaces!.shoppingPlace1P2, + interview + }); + expect(mockedGetPlaceName).toHaveBeenCalledWith({ + t: mockedT, + visitedPlace: interview.response.household!.persons!.personId2!.journeys!.journeyId2!.visitedPlaces!.otherWorkPlace1P2, + interview + }); expect(mockedGetCountOrSelfDeclared).toHaveBeenCalledWith({ interview, person: interview.response.household!.persons!.personId2 }); }); @@ -416,14 +428,7 @@ describe('ModePre label', () => { _sequence: 2, _originVisitedPlaceUuid: 'unexistingOrigin', _destinationVisitedPlaceUuid: 'unexistingDestination', - segments: { - segmentId1P2T2: { - _uuid: 'segmentId1P2T2', - _isNew: false, - _sequence: 1, - modePre: 'walk' - } - } + segments: { segmentId1P2T2: { _uuid: 'segmentId1P2T2', _isNew: false, _sequence: 1, modePre: 'walk' } } }; // Test label function @@ -476,7 +481,10 @@ describe('ModePre label', () => { test('undefined context function', () => { // New widget config without context function - const testWidgetConfig = getModePreWidgetConfig(segmentSectionConfig, { ...widgetFactoryOptions, context: undefined }) as QuestionWidgetConfig & InputRadioType; + const testWidgetConfig = getModePreWidgetConfig(segmentSectionConfig, { + ...widgetFactoryOptions, + context: undefined + }) as QuestionWidgetConfig & InputRadioType; const label = testWidgetConfig.label; // Prepare mocked data @@ -502,13 +510,8 @@ describe('ModePre label', () => { }); describe('ModePre filtering based on configuration', () => { - test('should only include modePre categories that have available modes', () => { - const segmentConfig = { - type: 'segments' as const, - enabled: true, - modesIncludeOnly: ['walk', 'bicycle', 'bicycleElectric'] as Mode[] - }; + const segmentConfig = { type: 'segments' as const, enabled: true, modesIncludeOnly: ['walk', 'bicycle', 'bicycleElectric'] as Mode[] }; const widgetConfig = getModePreWidgetConfig(segmentConfig, widgetFactoryOptions) as QuestionWidgetConfig & InputRadioType; const choices = widgetConfig.choices as RadioChoiceType[]; @@ -522,11 +525,7 @@ describe('ModePre filtering based on configuration', () => { }); test('should include transit when transit modes are available', () => { - const segmentConfig = { - type: 'segments' as const, - enabled: true, - modesIncludeOnly: ['transitBus', 'transitRRT', 'walk'] as Mode[] - }; + const segmentConfig = { type: 'segments' as const, enabled: true, modesIncludeOnly: ['transitBus', 'transitRRT', 'walk'] as Mode[] }; const widgetConfig = getModePreWidgetConfig(segmentConfig, widgetFactoryOptions) as QuestionWidgetConfig & InputRadioType; const choices = widgetConfig.choices as RadioChoiceType[]; @@ -537,11 +536,7 @@ describe('ModePre filtering based on configuration', () => { }); test('should include both walk and other when wheelchair/mobilityScooter are available', () => { - const segmentConfig = { - type: 'segments' as const, - enabled: true, - modesIncludeOnly: ['wheelchair', 'mobilityScooter'] as Mode[] - }; + const segmentConfig = { type: 'segments' as const, enabled: true, modesIncludeOnly: ['wheelchair', 'mobilityScooter'] as Mode[] }; const widgetConfig = getModePreWidgetConfig(segmentConfig, widgetFactoryOptions) as QuestionWidgetConfig & InputRadioType; const choices = widgetConfig.choices as RadioChoiceType[]; @@ -566,11 +561,7 @@ describe('ModePre filtering based on configuration', () => { }); test('should preserve modePre conditionals with filtered modes', () => { - const segmentConfig = { - type: 'segments' as const, - enabled: true, - modesIncludeOnly: ['paratransit', 'walk'] as Mode[] - }; + const segmentConfig = { type: 'segments' as const, enabled: true, modesIncludeOnly: ['paratransit', 'walk'] as Mode[] }; const widgetConfig = getModePreWidgetConfig(segmentConfig, widgetFactoryOptions) as QuestionWidgetConfig & InputRadioType; const choices = widgetConfig.choices as RadioChoiceType[]; @@ -593,14 +584,9 @@ describe('ModePre filtering based on configuration', () => { }); test('should throw an error when there is no mode', () => { - const segmentConfig = { - type: 'segments' as const, - enabled: true, - modesIncludeOnly: [] as Mode[] - }; + const segmentConfig = { type: 'segments' as const, enabled: true, modesIncludeOnly: [] as Mode[] }; expect(() => { getModePreWidgetConfig(segmentConfig, widgetFactoryOptions); }).toThrow('No available modes to create modePre widget configuration'); }); - }); diff --git a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetTripSegmentsIntro.test.ts b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetTripSegmentsIntro.test.ts index a180a8722..5d23ce368 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetTripSegmentsIntro.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/segments/__tests__/widgetTripSegmentsIntro.test.ts @@ -30,33 +30,24 @@ const mockedGetCountOrSelfDeclared = odHelpers.getCountOrSelfDeclared as jest.Mo beforeEach(() => { jest.clearAllMocks(); -}) +}); describe('getTripSegmentsIntro', () => { it('should return the correct widget config', () => { - - const options = { - context: jest.fn() - }; + const options = { context: jest.fn() }; const widgetConfig = getTripSegmentsIntro(options); - expect(widgetConfig).toEqual({ - type: 'text', - text: expect.any(Function) - }); + expect(widgetConfig).toEqual({ type: 'text', text: expect.any(Function) }); }); }); describe('tripSegmentsIntro text', () => { - - const options = { - context: jest.fn() - }; + const options = { context: jest.fn() }; const widgetText = getTripSegmentsIntro(options).text as any; const mockedT = jest.fn().mockReturnValue('translatedString'); - + test('should return empty if no person', () => { mockedGetPerson.mockReturnValueOnce(null); expect(widgetText(mockedT, interviewAttributesForTestCases, 'path')).toEqual(''); @@ -159,5 +150,4 @@ describe('tripSegmentsIntro text', () => { }); expect(options.context).toHaveBeenCalledWith('work'); }); - }); diff --git a/packages/evolution-common/src/services/questionnaire/sections/visitedPlaces/__tests__/activityIconMapping.test.ts b/packages/evolution-common/src/services/questionnaire/sections/visitedPlaces/__tests__/activityIconMapping.test.ts index cb7c34185..2ec055959 100644 --- a/packages/evolution-common/src/services/questionnaire/sections/visitedPlaces/__tests__/activityIconMapping.test.ts +++ b/packages/evolution-common/src/services/questionnaire/sections/visitedPlaces/__tests__/activityIconMapping.test.ts @@ -48,5 +48,4 @@ describe('activityIconMapping', () => { expect(getActivityMarkerIcon('school' as Activity, 'square')).toBe('/dist/icons/activities/school/graduation_cap-marker_square.svg'); }); }); - }); diff --git a/packages/evolution-common/src/services/questionnaire/types/__tests__/NavigationTypes.test.ts b/packages/evolution-common/src/services/questionnaire/types/__tests__/NavigationTypes.test.ts index e985ef035..c859802c3 100644 --- a/packages/evolution-common/src/services/questionnaire/types/__tests__/NavigationTypes.test.ts +++ b/packages/evolution-common/src/services/questionnaire/types/__tests__/NavigationTypes.test.ts @@ -18,18 +18,12 @@ describe('sectionToUrlPath', () => { }); it('should return the full path with iterationContext', () => { - const section: NavigationSection = { - sectionShortname: 'visitedPlaces', - iterationContext: ['person', '123'] - }; + const section: NavigationSection = { sectionShortname: 'visitedPlaces', iterationContext: ['person', '123'] }; expect(sectionToUrlPath(section)).toBe('visitedPlaces/person/123'); }); it('should handle iterationContext with multiple parts', () => { - const section: NavigationSection = { - sectionShortname: 'segments', - iterationContext: ['person', '123', 'journey', '456'] - }; + const section: NavigationSection = { sectionShortname: 'segments', iterationContext: ['person', '123', 'journey', '456'] }; expect(sectionToUrlPath(section)).toBe('segments/person/123/journey/456'); }); }); diff --git a/packages/evolution-common/src/services/questionnaire/types/__tests__/SectionConfig.test.ts b/packages/evolution-common/src/services/questionnaire/types/__tests__/SectionConfig.test.ts index 69da4e6f1..75fd4a8a0 100644 --- a/packages/evolution-common/src/services/questionnaire/types/__tests__/SectionConfig.test.ts +++ b/packages/evolution-common/src/services/questionnaire/types/__tests__/SectionConfig.test.ts @@ -8,22 +8,15 @@ import each from 'jest-each'; import { getAndValidateSurveySections, SurveySectionsConfig } from '../SectionConfig'; describe('getAndValidateSurveySections', () => { - - afterEach(() => { + afterEach(() => { jest.clearAllMocks(); }); test('should set default values for minimal section config', () => { - const minimalSection: SurveySectionsConfig = { - section1: { - previousSection: null, - nextSection: null, - widgets: [] - } - }; - + const minimalSection: SurveySectionsConfig = { section1: { previousSection: null, nextSection: null, widgets: [] } }; + const result = getAndValidateSurveySections(minimalSection); - + expect(result.section1).toEqual({ sectionName: 'section1', type: 'section', @@ -33,19 +26,14 @@ describe('getAndValidateSurveySections', () => { navMenu: { type: 'inNav', menuName: '' } }); }); - + test('should set default values when title is provided', () => { const sectionWithTitle: SurveySectionsConfig = { - section1: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: null, - nextSection: null, - widgets: ['widget1'] - } + section1: { title: { en: 'Test Section', fr: 'Section de test' }, previousSection: null, nextSection: null, widgets: ['widget1'] } }; - + const result = getAndValidateSurveySections(sectionWithTitle); - + expect(result.section1).toEqual({ sectionName: 'section1', type: 'section', @@ -53,13 +41,10 @@ describe('getAndValidateSurveySections', () => { previousSection: null, nextSection: null, widgets: ['widget1'], - navMenu: { - type: 'inNav', - menuName: { en: 'Test Section', fr: 'Section de test' } - } + navMenu: { type: 'inNav', menuName: { en: 'Test Section', fr: 'Section de test' } } }); }); - + test('should preserve custom navMenu when provided', () => { const sectionWithCustomNavMenu: SurveySectionsConfig = { mainSection: { @@ -73,73 +58,45 @@ describe('getAndValidateSurveySections', () => { previousSection: 'mainSection', nextSection: null, widgets: ['widget1'], - navMenu: { - type: 'hidden', - parentSection: 'mainSection' - } + navMenu: { type: 'hidden', parentSection: 'mainSection' } } }; - + const result = getAndValidateSurveySections(sectionWithCustomNavMenu); - - expect(result.section1.navMenu).toEqual({ - type: 'hidden', - parentSection: 'mainSection' - }); + + expect(result.section1.navMenu).toEqual({ type: 'hidden', parentSection: 'mainSection' }); }); - + test('should handle multiple sections correctly', () => { const multipleSections: SurveySectionsConfig = { - section1: { - title: { en: 'Section 1', fr: 'Section 1' }, - previousSection: null, - nextSection: 'section2', - widgets: ['widget1'] - }, + section1: { title: { en: 'Section 1', fr: 'Section 1' }, previousSection: null, nextSection: 'section2', widgets: ['widget1'] }, section2: { title: { en: 'Section 2', fr: 'Section 2' }, previousSection: 'section1', nextSection: null, widgets: ['widget2'], - navMenu: { - type: 'inNav', - menuName: { en: 'Custom Menu Name', fr: 'Nom de menu personnalisé' } - } + navMenu: { type: 'inNav', menuName: { en: 'Custom Menu Name', fr: 'Nom de menu personnalisé' } } } }; - + const result = getAndValidateSurveySections(multipleSections); - + expect(Object.keys(result)).toHaveLength(2); - expect(result.section1.navMenu).toEqual({ - type: 'inNav', - menuName: { en: 'Section 1', fr: 'Section 1' } - }); - expect(result.section2.navMenu).toEqual({ - type: 'inNav', - menuName: { en: 'Custom Menu Name', fr: 'Nom de menu personnalisé' } - }); + expect(result.section1.navMenu).toEqual({ type: 'inNav', menuName: { en: 'Section 1', fr: 'Section 1' } }); + expect(result.section2.navMenu).toEqual({ type: 'inNav', menuName: { en: 'Custom Menu Name', fr: 'Nom de menu personnalisé' } }); }); - + test('should preserve all properties in a complete section config', () => { const mockPreload = jest.fn(); const mockConditionalFn = () => true; const completeSection: SurveySectionsConfig = { - intro: { - title: { en: 'Introduction', fr: 'Introduction' }, - previousSection: null, - nextSection: 'section1', - widgets: ['widget1'] - }, + intro: { title: { en: 'Introduction', fr: 'Introduction' }, previousSection: null, nextSection: 'section1', widgets: ['widget1'] }, section1: { title: { en: 'Complete Section', fr: 'Section complète' }, previousSection: 'intro', nextSection: 'outro', widgets: ['widget1', 'widget2'], - navMenu: { - type: 'hidden', - parentSection: 'intro' - }, + navMenu: { type: 'hidden', parentSection: 'intro' }, enableConditional: mockConditionalFn, completionConditional: true, template: 'tripsAndSegmentsWithMap', @@ -148,21 +105,12 @@ describe('getAndValidateSurveySections', () => { isSectionVisible: jest.fn(), isSectionCompleted: jest.fn() }, - outro: { - title: { en: 'Outro', fr: 'Conclusion' }, - previousSection: 'section1', - nextSection: null, - widgets: ['widget3'] - } + outro: { title: { en: 'Outro', fr: 'Conclusion' }, previousSection: 'section1', nextSection: null, widgets: ['widget3'] } }; - + const result = getAndValidateSurveySections(completeSection); - - expect(result.section1).toEqual({ - sectionName: 'section1', - ...completeSection.section1, - type: 'section' - }); + + expect(result.section1).toEqual({ sectionName: 'section1', ...completeSection.section1, type: 'section' }); }); test('should set default nav menu hidden and repeated block section when in a repeated block', () => { @@ -175,7 +123,7 @@ describe('getAndValidateSurveySections', () => { repeatedBlock: { iterationRule: { type: 'builtin', path: 'interviewablePersons' }, activeSurveyObjectPath: '_activePersonId', - sections: ['section3'], + sections: ['section3'] } }, section3: { @@ -184,37 +132,27 @@ describe('getAndValidateSurveySections', () => { widgets: ['widget2'], nextSection: 'section1' }, - end: { - title: { en: 'Test Section 3', fr: 'Section de test 3' }, - previousSection: 'section1', - nextSection: null, - widgets: [] - } + end: { title: { en: 'Test Section 3', fr: 'Section de test 3' }, previousSection: 'section1', nextSection: null, widgets: [] } }; - + const result = getAndValidateSurveySections(sectionsWithoutNav); - expect(result).toEqual(expect.objectContaining({ - section3: expect.objectContaining({ - sectionName: 'section3', - type: 'section', - navMenu: { - type: 'hidden', - parentSection: 'section1' - }, - repeatedBlockSection: 'section1' - }), - section1: expect.objectContaining({ - sectionName: 'section1', - type: 'repeatedBlock', - navMenu: { - type: 'inNav', - menuName: { en: 'Test Section', fr: 'Section de test' } - }, - repeatedBlock: sectionsWithoutNav.section1.repeatedBlock + expect(result).toEqual( + expect.objectContaining({ + section3: expect.objectContaining({ + sectionName: 'section3', + type: 'section', + navMenu: { type: 'hidden', parentSection: 'section1' }, + repeatedBlockSection: 'section1' + }), + section1: expect.objectContaining({ + sectionName: 'section1', + type: 'repeatedBlock', + navMenu: { type: 'inNav', menuName: { en: 'Test Section', fr: 'Section de test' } }, + repeatedBlock: sectionsWithoutNav.section1.repeatedBlock + }) }) - })); - + ); }); test('should preserve nav menu when in a repeated block', () => { @@ -227,7 +165,7 @@ describe('getAndValidateSurveySections', () => { repeatedBlock: { iterationRule: { type: 'builtin', path: 'interviewablePersons' }, activeSurveyObjectPath: '_activePersonId', - sections: ['section3'], + sections: ['section3'] } }, section3: { @@ -237,158 +175,171 @@ describe('getAndValidateSurveySections', () => { widgets: ['widget2'], navMenu: { type: 'inNav', menuName: 'Test nav menu' } }, - end: { - title: { en: 'Test Section 3', fr: 'Section de test 3' }, - previousSection: 'section1', - nextSection: null, - widgets: [] - } + end: { title: { en: 'Test Section 3', fr: 'Section de test 3' }, previousSection: 'section1', nextSection: null, widgets: [] } }; - + const result = getAndValidateSurveySections(sectionWithCustomNavMenu); - + expect(result.section3.navMenu).toEqual({ type: 'inNav', menuName: 'Test nav menu' }); }); describe('should throw errors for undefined sections/errors in configuration', () => { each([ - ['nextSection', { - section1: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: null, - nextSection: 'section2', - widgets: ['widget1'] - } - }, - 'Section "section2" is referenced in "nextSection" of section "section1", but it is not defined in the sections config.'], - ['previousSection', { - section1: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: 'section2', - nextSection: null, - widgets: ['widget1'] - } - }, - 'Section "section2" is referenced in "previousSection" of section "section1", but it is not defined in the sections config.'], - ['repeatedBlock, missing section', { - section1: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: null, - nextSection: 'section3', - widgets: ['widget1'], - repeatedBlock: { - iterationRule: { type: 'builtin', path: 'interviewablePersons' }, - activeSurveyObjectPath: '_activePersonId', - sections: ['section2', 'section3'], + [ + 'nextSection', + { + section1: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: null, + nextSection: 'section2', + widgets: ['widget1'] } }, - section3: { - title: { en: 'Test Section 3', fr: 'Section de test 3' }, - previousSection: null, - nextSection: 'section1' - } - }, - 'Section "section2" is referenced in "repeatedBlock" of section "section1", but it is not defined in the sections config.'], - ['repeatedBlock, undefined selectionSectionId', { - section1: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: null, - nextSection: 'section3', - widgets: ['widget1'], - repeatedBlock: { - iterationRule: { type: 'builtin', path: 'interviewablePersons' }, - activeSurveyObjectPath: '_activePersonId', - selectionSectionId: 'section2', - sections: ['section3'], + 'Section "section2" is referenced in "nextSection" of section "section1", but it is not defined in the sections config.' + ], + [ + 'previousSection', + { + section1: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: 'section2', + nextSection: null, + widgets: ['widget1'] } }, - section3: { - title: { en: 'Test Section 3', fr: 'Section de test 3' }, - previousSection: null, - nextSection: 'section1' - } - }, - 'Section "section2" is referenced in "selectionSectionId" of section "section1", but it is not defined in the sections config.'], - ['navMenu parent does not exists', { - section1: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: null, - nextSection: null, - widgets: ['widget1'], - navMenu: { type: 'hidden', parentSection: 'mainSection' } - } - }, - 'Parent section "mainSection" is referenced in "navMenu" of section "section1", but it is not defined in the sections config.'], - ['navMenu parent not in nav', { - section1: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: null, - nextSection: 'section3', - widgets: ['widget1'], - navMenu: { type: 'hidden', parentSection: 'section3' } + 'Section "section2" is referenced in "previousSection" of section "section1", but it is not defined in the sections config.' + ], + [ + 'repeatedBlock, missing section', + { + section1: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: null, + nextSection: 'section3', + widgets: ['widget1'], + repeatedBlock: { + iterationRule: { type: 'builtin', path: 'interviewablePersons' }, + activeSurveyObjectPath: '_activePersonId', + sections: ['section2', 'section3'] + } + }, + section3: { title: { en: 'Test Section 3', fr: 'Section de test 3' }, previousSection: null, nextSection: 'section1' } }, - section3: { - title: { en: 'Test Section 3', fr: 'Section de test 3' }, - previousSection: 'section1', - nextSection: null, - navMenu: { type: 'hidden', parentSection: 'section3' } - } - }, - 'Parent section "section3" referenced in "navMenu" of section "section1" is not visible in the navigation menu.'], - ['No first section', { - section1: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: 'section2', - nextSection: 'section2', - widgets: ['widget1'] + 'Section "section2" is referenced in "repeatedBlock" of section "section1", but it is not defined in the sections config.' + ], + [ + 'repeatedBlock, undefined selectionSectionId', + { + section1: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: null, + nextSection: 'section3', + widgets: ['widget1'], + repeatedBlock: { + iterationRule: { type: 'builtin', path: 'interviewablePersons' }, + activeSurveyObjectPath: '_activePersonId', + selectionSectionId: 'section2', + sections: ['section3'] + } + }, + section3: { title: { en: 'Test Section 3', fr: 'Section de test 3' }, previousSection: null, nextSection: 'section1' } }, - section2: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: 'section1', - nextSection: null, - widgets: ['widget1'] - } - }, - 'No first section defined. Make sure at least one section has "previousSection" set to null.'], - ['No last section', { - section1: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: null, - nextSection: 'section2', - widgets: ['widget1'] + 'Section "section2" is referenced in "selectionSectionId" of section "section1", but it is not defined in the sections config.' + ], + [ + 'navMenu parent does not exists', + { + section1: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: null, + nextSection: null, + widgets: ['widget1'], + navMenu: { type: 'hidden', parentSection: 'mainSection' } + } }, - section2: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: 'section1', - nextSection: 'section1', - widgets: ['widget1'] - } - }, - 'No last section defined. Make sure at least one section has "nextSection" set to null.'], - ['No next section for repeated block', { - section1: { - title: { en: 'Test Section', fr: 'Section de test' }, - previousSection: null, - nextSection: null, - widgets: ['widget1'], - repeatedBlock: { - iterationRule: { type: 'builtin' as const, path: 'interviewablePersons' as const }, - activeSurveyObjectPath: '_activePersonId', - selectionSectionId: 'section2', - sections: ['section3'], + 'Parent section "mainSection" is referenced in "navMenu" of section "section1", but it is not defined in the sections config.' + ], + [ + 'navMenu parent not in nav', + { + section1: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: null, + nextSection: 'section3', + widgets: ['widget1'], + navMenu: { type: 'hidden', parentSection: 'section3' } + }, + section3: { + title: { en: 'Test Section 3', fr: 'Section de test 3' }, + previousSection: 'section1', + nextSection: null, + navMenu: { type: 'hidden', parentSection: 'section3' } } }, - section3: { - title: { en: 'Test Section 3', fr: 'Section de test 3' }, - previousSection: null, - nextSection: 'section1', - widgets: [] - } - }, - 'Section "section1" cannot have a nextSection set to null when it is a repeated block.'] + 'Parent section "section3" referenced in "navMenu" of section "section1" is not visible in the navigation menu.' + ], + [ + 'No first section', + { + section1: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: 'section2', + nextSection: 'section2', + widgets: ['widget1'] + }, + section2: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: 'section1', + nextSection: null, + widgets: ['widget1'] + } + }, + 'No first section defined. Make sure at least one section has "previousSection" set to null.' + ], + [ + 'No last section', + { + section1: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: null, + nextSection: 'section2', + widgets: ['widget1'] + }, + section2: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: 'section1', + nextSection: 'section1', + widgets: ['widget1'] + } + }, + 'No last section defined. Make sure at least one section has "nextSection" set to null.' + ], + [ + 'No next section for repeated block', + { + section1: { + title: { en: 'Test Section', fr: 'Section de test' }, + previousSection: null, + nextSection: null, + widgets: ['widget1'], + repeatedBlock: { + iterationRule: { type: 'builtin' as const, path: 'interviewablePersons' as const }, + activeSurveyObjectPath: '_activePersonId', + selectionSectionId: 'section2', + sections: ['section3'] + } + }, + section3: { + title: { en: 'Test Section 3', fr: 'Section de test 3' }, + previousSection: null, + nextSection: 'section1', + widgets: [] + } + }, + 'Section "section1" cannot have a nextSection set to null when it is a repeated block.' + ] ]).test('%s', (_location, sections, expectedError) => { expect(() => getAndValidateSurveySections(sections)).toThrow(expectedError); }); }); - -}); \ No newline at end of file +}); diff --git a/packages/evolution-common/src/services/questionnaire/types/__tests__/WidgetConfig.test.ts b/packages/evolution-common/src/services/questionnaire/types/__tests__/WidgetConfig.test.ts index 191c75901..7e35f0465 100644 --- a/packages/evolution-common/src/services/questionnaire/types/__tests__/WidgetConfig.test.ts +++ b/packages/evolution-common/src/services/questionnaire/types/__tests__/WidgetConfig.test.ts @@ -8,14 +8,7 @@ import { WidgetConfig, isInputTypeWithArrayValue } from '../WidgetConfig'; // Simply make sure various objects are recognize as WidgetConfig object. Any fault should result in compilation error. test('Test text assignation', () => { - const widgetConfig: WidgetConfig = { - type: 'text', - align: 'center', - text: { - fr: 'Texte en français', - en: 'English text' - } - }; + const widgetConfig: WidgetConfig = { type: 'text', align: 'center', text: { fr: 'Texte en français', en: 'English text' } }; expect(widgetConfig).toBeDefined(); }); @@ -30,21 +23,10 @@ test('Test input string assignation', () => { maxLength: 20, defaultValue: 'test', containsHtml: true, - label: { - fr: 'Texte en français', - en: 'English text' - } + label: { fr: 'Texte en français', en: 'English text' } }; - widgetConfig = { - type: 'question', - path: 'test.foo' as any, - inputType: 'string', - label: { - fr: 'Texte en français', - en: 'English text' - } - }; + widgetConfig = { type: 'question', path: 'test.foo' as any, inputType: 'string', label: { fr: 'Texte en français', en: 'English text' } }; expect(widgetConfig).toBeDefined(); }); @@ -61,10 +43,7 @@ test('Test input text assignation', () => { rows: 5, defaultValue: 'test', containsHtml: true, - label: { - fr: 'Texte en français', - en: 'English text' - } + label: { fr: 'Texte en français', en: 'English text' } }; widgetConfig = { @@ -72,10 +51,7 @@ test('Test input text assignation', () => { path: 'comments' as any, inputType: 'text', datatype: 'text', - label: { - fr: 'Texte en français', - en: 'English text' - } + label: { fr: 'Texte en français', en: 'English text' } }; expect(widgetConfig).toBeDefined(); diff --git a/packages/evolution-common/src/services/widgets/conditionals/__tests__/checkConditionals.test.ts b/packages/evolution-common/src/services/widgets/conditionals/__tests__/checkConditionals.test.ts index aa7de991b..86ab973e8 100644 --- a/packages/evolution-common/src/services/widgets/conditionals/__tests__/checkConditionals.test.ts +++ b/packages/evolution-common/src/services/widgets/conditionals/__tests__/checkConditionals.test.ts @@ -16,345 +16,80 @@ const interview = { _isString: 'a', _isTrueBoolean: true, _isNumber1: 1, - household: { - size: 1, - persons: { - 'bb6c33d8-5b68-4eb4-a031-2ffad0fd2ba5': { - age: 33 - } - } - } + household: { size: 1, persons: { 'bb6c33d8-5b68-4eb4-a031-2ffad0fd2ba5': { age: 33 } } } } }; each([ - [ - '­wrongPath === \'null\', should return true.', - [ - { - path: 'wrongPath', - comparisonOperator: '===', - value: 'null' - } - ], - [true, null] - ], - [ - '­wrongPath === \'something\', should return false.', - [ - { - path: 'wrongPath', - comparisonOperator: '===', - value: 'something' - } - ], - [false, null] - ], + ['­wrongPath === \'null\', should return true.', [{ path: 'wrongPath', comparisonOperator: '===', value: 'null' }], [true, null]], + ['­wrongPath === \'something\', should return false.', [{ path: 'wrongPath', comparisonOperator: '===', value: 'something' }], [false, null]], [ '_isString === interview.response._isString, should return true.', - [ - { - path: '_isString', - comparisonOperator: '===', - value: interview.response._isString - } - ], + [{ path: '_isString', comparisonOperator: '===', value: interview.response._isString }], [true, null] ], [ '_isString !== interview.response._isString, should return false.', - [ - { - path: '_isString', - comparisonOperator: '!==', - value: interview.response._isString - } - ], - [false, null] - ], - [ - '_isNull !== \'null\', should return false.', - [ - { - path: '_isNull', - comparisonOperator: '!==', - value: 'null' - } - ], - [false, null] - ], - [ - '_isNull === \'null\', should return true.', - [ - { - path: '_isNull', - comparisonOperator: '===', - value: 'null' - } - ], - [true, null] - ], - [ - '_isArray !== \'null\', should return true.', - [ - { - path: '_isArray', - comparisonOperator: '!==', - value: 'null' - } - ], - [true, null] - ], - [ - '_isEmptyArray === \'null\', should return true.', - [ - { - path: '_isEmptyArray', - comparisonOperator: '===', - value: 'null' - } - ], - [true, null] - ], - [ - '_isNull >= \'null\', should return false.', - [ - { - path: '_isNull', - comparisonOperator: '>=', - value: 'null' - } - ], + [{ path: '_isString', comparisonOperator: '!==', value: interview.response._isString }], [false, null] ], + ['_isNull !== \'null\', should return false.', [{ path: '_isNull', comparisonOperator: '!==', value: 'null' }], [false, null]], + ['_isNull === \'null\', should return true.', [{ path: '_isNull', comparisonOperator: '===', value: 'null' }], [true, null]], + ['_isArray !== \'null\', should return true.', [{ path: '_isArray', comparisonOperator: '!==', value: 'null' }], [true, null]], + ['_isEmptyArray === \'null\', should return true.', [{ path: '_isEmptyArray', comparisonOperator: '===', value: 'null' }], [true, null]], + ['_isNull >= \'null\', should return false.', [{ path: '_isNull', comparisonOperator: '>=', value: 'null' }], [false, null]], [ '_isArray === \'a\', should return true, because \'a\' is inside array.', - [ - { - path: '_isArray', - comparisonOperator: '===', - value: 'a' - } - ], + [{ path: '_isArray', comparisonOperator: '===', value: 'a' }], [true, null] ], [ '_isArray !== \'d\', should return true, because \'d\' is not inside array.', - [ - { - path: '_isArray', - comparisonOperator: '!==', - value: 'd' - } - ], + [{ path: '_isArray', comparisonOperator: '!==', value: 'd' }], [true, null] ], [ 'Wrong comparison operator with array, should return false.', - [ - { - path: '_isArray', - comparisonOperator: 'wrongComparisonOperator', - value: 'a' - } - ], + [{ path: '_isArray', comparisonOperator: 'wrongComparisonOperator', value: 'a' }], [false, null] ], [ '_isTrueBoolean === interview.response._isTrueBoolean, should return true.', - [ - { - path: '_isTrueBoolean', - comparisonOperator: '===', - value: interview.response._isTrueBoolean - } - ], + [{ path: '_isTrueBoolean', comparisonOperator: '===', value: interview.response._isTrueBoolean }], [true, null] ], [ 'Wrong comparison operator with boolean, should return false.', - [ - { - path: '_isTrueBoolean', - comparisonOperator: 'wrongComparisonOperator', - value: interview.response._isTrueBoolean - } - ], - [false, null] - ], - [ - '_isNumber1 === 1, should return true.', - [ - { - path: '_isNumber1', - comparisonOperator: '===', - value: 1 - } - ], - [true, null] - ], - [ - '_isNumber1 === 2, should return false.', - [ - { - path: '_isNumber1', - comparisonOperator: '===', - value: 2 - } - ], - [false, null] - ], - [ - '_isNumber1 !== 2, should return true.', - [ - { - path: '_isNumber1', - comparisonOperator: '!==', - value: 2 - } - ], - [true, null] - ], - [ - '_isNumber1 !== 1, should return false.', - [ - { - path: '_isNumber1', - comparisonOperator: '!==', - value: 1 - } - ], + [{ path: '_isTrueBoolean', comparisonOperator: 'wrongComparisonOperator', value: interview.response._isTrueBoolean }], [false, null] ], - [ - '_isNumber1 > 0, should return true.', - [ - { - path: '_isNumber1', - comparisonOperator: '>', - value: 0 - } - ], - [true, null] - ], - [ - '_isNumber1 > 1, should return false.', - [ - { - path: '_isNumber1', - comparisonOperator: '>', - value: 1 - } - ], - [false, null] - ], - [ - '_isNumber1 < 2, should return true.', - [ - { - path: '_isNumber1', - comparisonOperator: '<', - value: 2 - } - ], - [true, null] - ], - [ - '_isNumber1 < 1, should return false.', - [ - { - path: '_isNumber1', - comparisonOperator: '<', - value: 1 - } - ], - [false, null] - ], - [ - '_isNumber1 >= 1, should return true.', - [ - { - path: '_isNumber1', - comparisonOperator: '>=', - value: 1 - } - ], - [true, null] - ], - [ - '_isNumber1 >= 2, should return false.', - [ - { - path: '_isNumber1', - comparisonOperator: '>=', - value: 2 - } - ], - [false, null] - ], - [ - '_isNumber1 >= 0, should return true.', - [ - { - path: '_isNumber1', - comparisonOperator: '>=', - value: 0 - } - ], - [true, null] - ], - [ - '_isNumber1 <= 2, should return true.', - [ - { - path: '_isNumber1', - comparisonOperator: '<=', - value: 2 - } - ], - [true, null] - ], - [ - '_isNumber1 <= 1, should return true.', - [ - { - path: '_isNumber1', - comparisonOperator: '<=', - value: 1 - } - ], - [true, null] - ], + ['_isNumber1 === 1, should return true.', [{ path: '_isNumber1', comparisonOperator: '===', value: 1 }], [true, null]], + ['_isNumber1 === 2, should return false.', [{ path: '_isNumber1', comparisonOperator: '===', value: 2 }], [false, null]], + ['_isNumber1 !== 2, should return true.', [{ path: '_isNumber1', comparisonOperator: '!==', value: 2 }], [true, null]], + ['_isNumber1 !== 1, should return false.', [{ path: '_isNumber1', comparisonOperator: '!==', value: 1 }], [false, null]], + ['_isNumber1 > 0, should return true.', [{ path: '_isNumber1', comparisonOperator: '>', value: 0 }], [true, null]], + ['_isNumber1 > 1, should return false.', [{ path: '_isNumber1', comparisonOperator: '>', value: 1 }], [false, null]], + ['_isNumber1 < 2, should return true.', [{ path: '_isNumber1', comparisonOperator: '<', value: 2 }], [true, null]], + ['_isNumber1 < 1, should return false.', [{ path: '_isNumber1', comparisonOperator: '<', value: 1 }], [false, null]], + ['_isNumber1 >= 1, should return true.', [{ path: '_isNumber1', comparisonOperator: '>=', value: 1 }], [true, null]], + ['_isNumber1 >= 2, should return false.', [{ path: '_isNumber1', comparisonOperator: '>=', value: 2 }], [false, null]], + ['_isNumber1 >= 0, should return true.', [{ path: '_isNumber1', comparisonOperator: '>=', value: 0 }], [true, null]], + ['_isNumber1 <= 2, should return true.', [{ path: '_isNumber1', comparisonOperator: '<=', value: 2 }], [true, null]], + ['_isNumber1 <= 1, should return true.', [{ path: '_isNumber1', comparisonOperator: '<=', value: 1 }], [true, null]], [ 'Wrong comparison operator with number, should return false.', - [ - { - path: '_isNumber1', - comparisonOperator: 'wrongComparisonOperator', - value: 1 - } - ], + [{ path: '_isNumber1', comparisonOperator: 'wrongComparisonOperator', value: 1 }], [false, null] ], [ '_isNumber1 === { object: \'invalid\' }, should return false.', - [ - { - path: '_isNumber1', - comparisonOperator: '===', - value: { object: 'invalid' } - } - ], + [{ path: '_isNumber1', comparisonOperator: '===', value: { object: 'invalid' } }], [false, null] ], [ '_isNumber1 === 1 && _isNumber1 === 0, should return false.', [ - { - path: '_isNumber1', - comparisonOperator: '===', - value: 1 - }, + { path: '_isNumber1', comparisonOperator: '===', value: 1 }, { path: '_isNumber1', comparisonOperator: '===', value: 0, logicalOperator: '&&' } ], [false, null] @@ -362,11 +97,7 @@ each([ [ '_isNumber1 === 1 || _isNumber1 === 0, should return true.', [ - { - path: '_isNumber1', - comparisonOperator: '===', - value: 1 - }, + { path: '_isNumber1', comparisonOperator: '===', value: 1 }, { path: '_isNumber1', comparisonOperator: '===', value: 0, logicalOperator: '||' } ], [true, null] @@ -374,11 +105,7 @@ each([ [ '_isNumber1 === 1 && (_isNumber1 === 0 || _isNumber1 === 1), should return true.', [ - { - path: '_isNumber1', - comparisonOperator: '===', - value: 1 - }, + { path: '_isNumber1', comparisonOperator: '===', value: 1 }, { path: '_isNumber1', comparisonOperator: '===', value: 0, logicalOperator: '&&', parentheses: '(' }, { path: '_isNumber1', comparisonOperator: '===', value: 1, logicalOperator: '||', parentheses: ')' } ], @@ -387,11 +114,7 @@ each([ [ '_isNumber1 === 1 || (_isNumber1 === 0 && _isNumber1 === 1), should return true.', [ - { - path: '_isNumber1', - comparisonOperator: '===', - value: 1 - }, + { path: '_isNumber1', comparisonOperator: '===', value: 1 }, { path: '_isNumber1', comparisonOperator: '===', value: 0, logicalOperator: '||', parentheses: '(' }, { path: '_isNumber1', comparisonOperator: '===', value: 1, logicalOperator: '&&', parentheses: ')' } ], @@ -399,24 +122,12 @@ each([ ], [ 'household.persons.{_activePersonId}.age === 33, should return true.', - [ - { - path: 'household.persons.{_activePersonId}.age', - comparisonOperator: '===', - value: 33 - } - ], + [{ path: 'household.persons.{_activePersonId}.age', comparisonOperator: '===', value: 33 }], [true, null] ], [ 'household.persons.{wrongField}.age === 33, should return false.', - [ - { - path: 'household.persons.{wrongField}.age', - comparisonOperator: '===', - value: 33 - } - ], + [{ path: 'household.persons.{wrongField}.age', comparisonOperator: '===', value: 33 }], [false, null] ] // TODO: Uncomment the following test when the checkConditionals function is fixed for this case diff --git a/packages/evolution-common/src/services/widgets/validations/__tests__/validations.test.ts b/packages/evolution-common/src/services/widgets/validations/__tests__/validations.test.ts index 30be60f46..3ef01b893 100644 --- a/packages/evolution-common/src/services/widgets/validations/__tests__/validations.test.ts +++ b/packages/evolution-common/src/services/widgets/validations/__tests__/validations.test.ts @@ -10,7 +10,7 @@ import projectConfig from '../../../../config/project.config'; // Mock the project config to be able to change the postalCodeRegion jest.mock('../../../../config/project.config', () => ({ - postalCodeRegion: 'canada' // Default for tests + postalCodeRegion: 'canada' // Default for tests })); describe('postalCodeValidation', () => { @@ -26,22 +26,17 @@ describe('postalCodeValidation', () => { expect((result[0].errorMessage as any)(mockTranslation)).toEqual('survey:errors:postalCodeRequired'); }); }); - + describe('Canadian postal code validation', () => { beforeEach(() => { projectConfig.postalCodeRegion = 'canada'; }); - - test.each([ - 'A1A 1A1', - 'A1A1A1', - 'H3Z 2Y7', - 'V8C 1A5' - ])('should accept valid Canadian postal code: %s', (postalCode) => { + + test.each(['A1A 1A1', 'A1A1A1', 'H3Z 2Y7', 'V8C 1A5'])('should accept valid Canadian postal code: %s', (postalCode) => { const result = validations.postalCodeValidation(postalCode, undefined, {} as any, 'postalCode'); expect(result[1].validation).toBe(false); // Should be valid }); - + test.each([ ['D1A 1A1', 'D is not used in Canadian postal codes'], ['F1A 1A1', 'F is not used in Canadian postal codes'], @@ -65,22 +60,17 @@ describe('postalCodeValidation', () => { expect(mockTranslation).toHaveBeenLastCalledWith('survey:errors:postalCodeInvalid', { context: 'canada' }); }); }); - + describe('Quebec postal code validation', () => { beforeEach(() => { projectConfig.postalCodeRegion = 'quebec'; }); - - test.each([ - 'G1A 1A1', - 'H3Z 2Y7', - 'J7V 9Z9', - 'K2V 1A1' - ])('should accept valid Quebec postal code: %s', (postalCode) => { + + test.each(['G1A 1A1', 'H3Z 2Y7', 'J7V 9Z9', 'K2V 1A1'])('should accept valid Quebec postal code: %s', (postalCode) => { const result = validations.postalCodeValidation(postalCode, undefined, {} as any, 'postalCode'); expect(result[1].validation).toBe(false); // Should be valid }); - + test.each([ ['D1A 1A1', 'D is not used in Canadian postal codes'], ['F1A 1A1', 'F is not used in Canadian postal codes'], @@ -108,12 +98,12 @@ describe('postalCodeValidation', () => { expect(mockTranslation).toHaveBeenLastCalledWith('survey:errors:postalCodeInvalid', { context: 'quebec' }); }); }); - + describe('Other postal code validation', () => { beforeEach(() => { projectConfig.postalCodeRegion = 'other'; }); - + test.each([ ['A1A 1A1', 'Canadian'], ['H3Z 2Y7', 'Quebec'], @@ -139,7 +129,7 @@ describe('getPostalCodeRegex', () => { const regex = validations.getPostalCodeRegex(); expect(regex.test(postalCode)).toBe(expected); }); - + test.each([ ['H2X 1A1', true, 'Valid Quebec postal code'], ['A1A 1A1', false, 'Not a Quebec postal code'] @@ -148,7 +138,7 @@ describe('getPostalCodeRegex', () => { const regex = validations.getPostalCodeRegex(); expect(regex.test(postalCode)).toBe(expected); }); - + test.each([ ['12345', true, 'US-style postal code'], ['ABC-123', true, 'Made up postal code'], @@ -172,7 +162,7 @@ describe('phoneValidation', () => { // Valid format with spaces and dashes '223- 456- 7890', ' 223 - 456 - 7890 ', - '223\t\t456\t7890\t', // tabs + '223\t\t456\t7890\t', // tabs '223\u00A0456\u00A0\u00A07890\u00A0', // nbsp '\u202F223\u202F456\u202F\u202F7890\u202F', // narrow nbsp '\u2009223\u2009456\u2009\u20097890\u2009', // thin nbsp @@ -196,7 +186,7 @@ describe('phoneValidation', () => { expect(result[0].validation).toBe(false); }); - // Test cases for valid international phone numbers + // Test cases for valid international phone numbers test.each([ // International phone numbers '+44 20 7946 0958', // UK number @@ -216,7 +206,7 @@ describe('phoneValidation', () => { '+44\t\t20\t7946\t0958\t', // UK number with tabs '+44\u00A020\u00A07946\u00A00958\u00A0', // UK number with nbsp '\u202F+44\u202F20\u202F7946\u202F0958\u202F', // UK number with narrow spaces - '\u2009+44\u200920\u20097946\u20090958\u2009', // UK number with thin spaces + '\u2009+44\u200920\u20097946\u20090958\u2009' // UK number with thin spaces ])('should return no validation errors for valid international phone number: %s', (phoneNumber) => { const result = validations.phoneValidation(phoneNumber, undefined, {} as any, 'phoneNumber'); // For valid phone numbers, the validation property should be false @@ -275,24 +265,19 @@ describe('accessCodeValidation', () => { expect((result[0].errorMessage as any)(mockTranslation)).toEqual('survey:errors:accessCodeRequired'); }); }); - + describe('eight digits access code validation', () => { - - test.each([ - '2345-2345', - '1234 1234', - '12341234' - ])('should accept valid 8-digits access codes: %s', (accessCode) => { + test.each(['2345-2345', '1234 1234', '12341234'])('should accept valid 8-digits access codes: %s', (accessCode) => { const result = validations.accessCodeValidation(accessCode, undefined, {} as any, 'accessCode'); expect(result[0].validation).toBe(false); // Empty validation passes expect(result[1].validation).toBe(false); // Should be valid }); - + test.each([ ['2345-abcd', 'Contains letters'], ['2345 45', 'Too short'], ['123412341234', 'Too long'], - ['12-345678', 'Misplaced dash'], + ['12-345678', 'Misplaced dash'] ])('should reject invalid 8-digits access code: %s (%s)', (accessCode, _reason) => { const result = validations.accessCodeValidation(accessCode, undefined, {} as any, 'accessCode'); expect(result[0].validation).toBe(false); // Empty validation passes @@ -302,5 +287,4 @@ describe('accessCodeValidation', () => { expect(mockTranslation).toHaveBeenLastCalledWith('survey:errors:accessCodeInvalid'); }); }); - -}); \ No newline at end of file +}); diff --git a/packages/evolution-common/src/utils/__tests__/ColorUtils.test.ts b/packages/evolution-common/src/utils/__tests__/ColorUtils.test.ts index 6812f31a3..353a469ad 100644 --- a/packages/evolution-common/src/utils/__tests__/ColorUtils.test.ts +++ b/packages/evolution-common/src/utils/__tests__/ColorUtils.test.ts @@ -8,8 +8,9 @@ import { hexToRgb } from '../ColorUtils'; describe('hexToRgb', () => { - - const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { return; }); + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { + return; + }); afterEach(() => consoleErrorSpy.mockClear()); test.each([ @@ -41,5 +42,4 @@ describe('hexToRgb', () => { expect(hexToRgb(input)).toBeUndefined(); expect(consoleErrorSpy).toHaveBeenCalledTimes(1); }); - }); diff --git a/packages/evolution-common/src/utils/__tests__/ConstructorUtils.test.ts b/packages/evolution-common/src/utils/__tests__/ConstructorUtils.test.ts index c92874f92..18731ab19 100644 --- a/packages/evolution-common/src/utils/__tests__/ConstructorUtils.test.ts +++ b/packages/evolution-common/src/utils/__tests__/ConstructorUtils.test.ts @@ -17,12 +17,7 @@ beforeEach(() => { describe('ConstructorUtils', () => { describe('initializeAttributes', () => { test('should initialize attributes and custom attributes correctly', () => { - const params = { - attr1: 'value1', - attr2: 'value2', - customAttr1: 'customValue1', - customAttr2: 'customValue2', - }; + const params = { attr1: 'value1', attr2: 'value2', customAttr1: 'customValue1', customAttr2: 'customValue2' }; const attributeNames = ['attr1', 'attr2']; const { attributes, customAttributes } = ConstructorUtils.initializeAttributes(params, attributeNames); expect(attributes).toEqual({ attr1: 'value1', attr2: 'value2' }); @@ -30,12 +25,7 @@ describe('ConstructorUtils', () => { }); test('should not include _surveyObjectsRegistry in customAttributes', () => { - const params = { - attr1: 'value1', - attr2: 'value2', - customAttr1: 'customValue1', - _surveyObjectsRegistry: surveyObjectsRegistry, - }; + const params = { attr1: 'value1', attr2: 'value2', customAttr1: 'customValue1', _surveyObjectsRegistry: surveyObjectsRegistry }; const attributeNames = ['attr1', 'attr2']; const { attributes, customAttributes } = ConstructorUtils.initializeAttributes(params, attributeNames); expect(attributes).toEqual({ attr1: 'value1', attr2: 'value2' }); @@ -49,16 +39,12 @@ describe('ConstructorUtils', () => { customAttr1: 'customValue1', customAttr2: 'customValue2', _surveyObjectsRegistry: surveyObjectsRegistry, - customAttr3: 'customValue3', + customAttr3: 'customValue3' }; const attributeNames = ['attr1']; const { attributes, customAttributes } = ConstructorUtils.initializeAttributes(params, attributeNames); expect(attributes).toEqual({ attr1: 'value1' }); - expect(customAttributes).toEqual({ - customAttr1: 'customValue1', - customAttr2: 'customValue2', - customAttr3: 'customValue3' - }); + expect(customAttributes).toEqual({ customAttr1: 'customValue1', customAttr2: 'customValue2', customAttr3: 'customValue3' }); expect(customAttributes).not.toHaveProperty('_surveyObjectsRegistry'); }); }); @@ -102,9 +88,7 @@ describe('ConstructorUtils', () => { describe('initializeComposedArrayAttributes', () => { test('should initialize composed array attributes correctly', () => { - const params = { - composedAttr: [{ id: 1 }, { id: 2 }], - }; + const params = { composedAttr: [{ id: 1 }, { id: 2 }] }; const unserializeFunc = (item) => ({ id: item.id, name: `Item ${item.id}` }); const composedAttributes = ConstructorUtils.initializeComposedArrayAttributes( params.composedAttr, @@ -113,55 +97,37 @@ describe('ConstructorUtils', () => { ); expect(composedAttributes).toEqual([ { id: 1, name: 'Item 1' }, - { id: 2, name: 'Item 2' }, + { id: 2, name: 'Item 2' } ]); }); test('should return an empty array if composed array attribute is undefined', () => { const params = undefined; const unserializeFunc = (item) => item; - const composedAttributes = ConstructorUtils.initializeComposedArrayAttributes( - params, - unserializeFunc, - surveyObjectsRegistry - ); + const composedAttributes = ConstructorUtils.initializeComposedArrayAttributes(params, unserializeFunc, surveyObjectsRegistry); expect(composedAttributes).toEqual([]); }); test('should return an empty array if at least on element of array attribute is undefined', () => { const params = [undefined]; const unserializeFunc = (item) => item; - const composedAttributes = ConstructorUtils.initializeComposedArrayAttributes( - params, - unserializeFunc, - surveyObjectsRegistry - ); + const composedAttributes = ConstructorUtils.initializeComposedArrayAttributes(params, unserializeFunc, surveyObjectsRegistry); expect(composedAttributes).toEqual([]); }); }); describe('initializeComposedAttribute', () => { test('should initialize composed attributes correctly', () => { - const params = { - composedAttr: { id: 1 }, - }; + const params = { composedAttr: { id: 1 } }; const unserializeFunc = (item) => ({ id: item.id, name: `Item ${item.id}` }); - const composedAttributes = ConstructorUtils.initializeComposedAttribute( - params.composedAttr, - unserializeFunc, - surveyObjectsRegistry - ); + const composedAttributes = ConstructorUtils.initializeComposedAttribute(params.composedAttr, unserializeFunc, surveyObjectsRegistry); expect(composedAttributes).toEqual({ id: 1, name: 'Item 1' }); }); test('should return an empty array if composed attribute is undefined', () => { const params = undefined; const unserializeFunc = (item) => item; - const composedAttributes = ConstructorUtils.initializeComposedAttribute( - params, - unserializeFunc, - surveyObjectsRegistry - ); + const composedAttributes = ConstructorUtils.initializeComposedAttribute(params, unserializeFunc, surveyObjectsRegistry); expect(composedAttributes).toEqual(undefined); }); }); diff --git a/packages/evolution-common/src/utils/__tests__/DateTimeUtils.test.ts b/packages/evolution-common/src/utils/__tests__/DateTimeUtils.test.ts index 90cabc0c6..e590d50fb 100644 --- a/packages/evolution-common/src/utils/__tests__/DateTimeUtils.test.ts +++ b/packages/evolution-common/src/utils/__tests__/DateTimeUtils.test.ts @@ -182,11 +182,7 @@ describe('DateTimeUtils', () => { describe('secondsToMillisecondsTimestamp', () => { describe('should convert valid timestamps', () => { test.each([ - { - description: 'zero (Unix epoch)', - seconds: 0, - expected: 0 - }, + { description: 'zero (Unix epoch)', seconds: 0, expected: 0 }, { description: 'positive timestamp', seconds: 1704067200, // 2024-01-01T00:00:00Z @@ -197,11 +193,7 @@ describe('DateTimeUtils', () => { seconds: -86400, // 1969-12-31 expected: -86400000 }, - { - description: 'fractional seconds', - seconds: 1704067200.5, - expected: 1704067200500 - }, + { description: 'fractional seconds', seconds: 1704067200.5, expected: 1704067200500 }, { description: 'large timestamp (year 2100)', seconds: 4102444800, // 2100-01-01T00:00:00Z @@ -215,26 +207,10 @@ describe('DateTimeUtils', () => { describe('should return undefined for invalid inputs', () => { test.each([ - { - description: 'undefined', - seconds: undefined, - expected: undefined - }, - { - description: 'NaN', - seconds: NaN, - expected: undefined - }, - { - description: 'Infinity', - seconds: Infinity, - expected: undefined - }, - { - description: '-Infinity', - seconds: -Infinity, - expected: undefined - } + { description: 'undefined', seconds: undefined, expected: undefined }, + { description: 'NaN', seconds: NaN, expected: undefined }, + { description: 'Infinity', seconds: Infinity, expected: undefined }, + { description: '-Infinity', seconds: -Infinity, expected: undefined } ])('$description', ({ seconds, expected }) => { const result = secondsToMillisecondsTimestamp(seconds); expect(result).toBe(expected); @@ -285,11 +261,7 @@ describe('DateTimeUtils', () => { isoString: '2025-01-01T12:30:45.123Z', expected: new Date('2025-01-01T12:30:45.123Z').getTime() }, - { - description: 'date-only ISO string', - isoString: '2025-01-01', - expected: new Date('2025-01-01').getTime() - }, + { description: 'date-only ISO string', isoString: '2025-01-01', expected: new Date('2025-01-01').getTime() }, { description: 'ISO string without timezone (assumes local)', isoString: '2025-01-01T00:00:00', @@ -303,46 +275,14 @@ describe('DateTimeUtils', () => { describe('should return undefined for invalid inputs', () => { test.each([ - { - description: 'undefined', - isoString: undefined, - expected: undefined - }, - { - description: 'empty string', - isoString: '', - expected: undefined - }, - { - description: 'invalid date string', - isoString: 'invalid-date-string', - expected: undefined - }, - { - description: 'malformed ISO string (invalid month)', - isoString: '2025-13-01T00:00:00-05:00', - expected: undefined - }, - { - description: 'malformed ISO string (invalid day)', - isoString: '2025-01-32T00:00:00-05:00', - expected: undefined - }, - { - description: 'malformed ISO string (invalid hour)', - isoString: '2025-01-01T25:00:00-05:00', - expected: undefined - }, - { - description: 'completely invalid format', - isoString: '2025-00-00T99:99:99-05:00', - expected: undefined - }, - { - description: 'random text', - isoString: 'not a date', - expected: undefined - } + { description: 'undefined', isoString: undefined, expected: undefined }, + { description: 'empty string', isoString: '', expected: undefined }, + { description: 'invalid date string', isoString: 'invalid-date-string', expected: undefined }, + { description: 'malformed ISO string (invalid month)', isoString: '2025-13-01T00:00:00-05:00', expected: undefined }, + { description: 'malformed ISO string (invalid day)', isoString: '2025-01-32T00:00:00-05:00', expected: undefined }, + { description: 'malformed ISO string (invalid hour)', isoString: '2025-01-01T25:00:00-05:00', expected: undefined }, + { description: 'completely invalid format', isoString: '2025-00-00T99:99:99-05:00', expected: undefined }, + { description: 'random text', isoString: 'not a date', expected: undefined } ])('$description', ({ isoString, expected }) => { const result = parseISODateToTimestamp(isoString); expect(result).toBe(expected); @@ -374,5 +314,4 @@ describe('DateTimeUtils', () => { }); }); }); - }); diff --git a/packages/evolution-common/src/utils/__tests__/DateUtils.test.ts b/packages/evolution-common/src/utils/__tests__/DateUtils.test.ts index fdce2ca44..0980fc043 100644 --- a/packages/evolution-common/src/utils/__tests__/DateUtils.test.ts +++ b/packages/evolution-common/src/utils/__tests__/DateUtils.test.ts @@ -5,13 +5,7 @@ * License text available at https://opensource.org/licenses/MIT */ -import { - parseDate, - getDateFromDateString, - getDateStringFromDate, - getWeekdayFromDate, - getUnixEpochFromDate, -} from '../DateUtils'; +import { parseDate, getDateFromDateString, getDateStringFromDate, getWeekdayFromDate, getUnixEpochFromDate } from '../DateUtils'; // deprecated: describe('parseDate function', () => { @@ -20,7 +14,7 @@ describe('parseDate function', () => { [undefined, undefined, 'returns undefined for undefined input'], ['', undefined, 'returns undefined for empty string input'], [null, undefined, 'returns undefined for null input'], - [new Date('2020-01-01'), new Date('2020-01-01'), 'returns the same Date object for Date input'], + [new Date('2020-01-01'), new Date('2020-01-01'), 'returns the same Date object for Date input'] ])('%s: %s', (input, expected, _) => { expect(parseDate(input)).toEqual(expected); }); @@ -28,7 +22,7 @@ describe('parseDate function', () => { // Test cases for string input test.each([ ['2020-01-01', true, 'returns a valid Date object for valid date string input'], - ['invalid-date', false, 'returns an invalid Date object for invalid date string input'], + ['invalid-date', false, 'returns an invalid Date object for invalid date string input'] ])('%s: %s', (input, isValid, _) => { const result = parseDate(input); expect(result instanceof Date).toBeTruthy(); @@ -36,18 +30,19 @@ describe('parseDate function', () => { }); // Test case for logging error on invalid date string input - test.each([ - ['invalid-date', true, 'logs an error for invalid date string input when showErrorOnValidate is true'], - ])('%s with showErrorOnValidate=%s: %s', (input, showErrorOnValidate, _) => { - const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); - parseDate(input, showErrorOnValidate); - if (showErrorOnValidate) { - expect(consoleSpy).toHaveBeenCalledWith('DateUtils parseDate: invalid date'); - } else { - expect(consoleSpy).not.toHaveBeenCalled(); + test.each([['invalid-date', true, 'logs an error for invalid date string input when showErrorOnValidate is true']])( + '%s with showErrorOnValidate=%s: %s', + (input, showErrorOnValidate, _) => { + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); + parseDate(input, showErrorOnValidate); + if (showErrorOnValidate) { + expect(consoleSpy).toHaveBeenCalledWith('DateUtils parseDate: invalid date'); + } else { + expect(consoleSpy).not.toHaveBeenCalled(); + } + consoleSpy.mockRestore(); } - consoleSpy.mockRestore(); - }); + ); }); describe('getDateFromDateString', () => { @@ -99,7 +94,6 @@ describe('getDateStringFromDate', () => { const result = getDateStringFromDate(date); expect(result).toEqual('2023-01-01'); }); - }); describe('getWeekdayFromDate', () => { diff --git a/packages/evolution-common/src/utils/__tests__/ParamsValidatorUtils.test.ts b/packages/evolution-common/src/utils/__tests__/ParamsValidatorUtils.test.ts index 86b893c92..f8176e699 100644 --- a/packages/evolution-common/src/utils/__tests__/ParamsValidatorUtils.test.ts +++ b/packages/evolution-common/src/utils/__tests__/ParamsValidatorUtils.test.ts @@ -8,10 +8,9 @@ import { ParamsValidatorUtils } from '../ParamsValidatorUtils'; import { v4 as uuidV4 } from 'uuid'; -class TestClass { } +class TestClass {} describe('ParamsValidatorUtils', () => { - describe('isRecord', () => { test('should return no errors for a valid plain object', () => { const errors = ParamsValidatorUtils.isRecord('attr', {}, 'TestClass'); @@ -96,14 +95,12 @@ describe('ParamsValidatorUtils', () => { }); describe('isInstanceOf', () => { - class ParentClass {} class ChildClass extends ParentClass {} test('should return no errors for an instance of TestClass', () => { const instance = new TestClass(); - const errors = ParamsValidatorUtils.isInstanceOf('attr', instance, 'TestClass', TestClass - ); + const errors = ParamsValidatorUtils.isInstanceOf('attr', instance, 'TestClass', TestClass); expect(errors).toEqual([]); }); @@ -292,7 +289,8 @@ describe('ParamsValidatorUtils', () => { }); test('should return an error for a non-array value', () => { - const errors = ParamsValidatorUtils.isArray('attr', 'invalid', 'TestClass'); expect(errors).toHaveLength(1); + const errors = ParamsValidatorUtils.isArray('attr', 'invalid', 'TestClass'); + expect(errors).toHaveLength(1); expect(errors[0].message).toContain('should be an array'); }); }); @@ -456,14 +454,7 @@ describe('ParamsValidatorUtils', () => { describe('isGeojsonPoint', () => { test('should return no errors for a valid GeoJSON Point', () => { - const point: GeoJSON.Feature = { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: [0, 0] - } - }; + const point: GeoJSON.Feature = { type: 'Feature', properties: {}, geometry: { type: 'Point', coordinates: [0, 0] } }; const errors = ParamsValidatorUtils.isGeojsonPoint('attr', point, 'TestClass'); expect(errors).toEqual([]); }); @@ -480,13 +471,21 @@ describe('ParamsValidatorUtils', () => { }); test('should return an error for an empty GeoJSON Point coordinates', () => { - const errors = ParamsValidatorUtils.isGeojsonPoint('attr', { type: 'Feature', geometry: { type: 'Point', coordinates: [] } }, 'TestClass'); + const errors = ParamsValidatorUtils.isGeojsonPoint( + 'attr', + { type: 'Feature', geometry: { type: 'Point', coordinates: [] } }, + 'TestClass' + ); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('should be a valid geojson point'); }); test('should return an error for an undefined GeoJSON Point coordinates', () => { - const errors = ParamsValidatorUtils.isGeojsonPoint('attr', { type: 'Feature', geometry: { type: 'Point', coordinates: undefined } }, 'TestClass'); + const errors = ParamsValidatorUtils.isGeojsonPoint( + 'attr', + { type: 'Feature', geometry: { type: 'Point', coordinates: undefined } }, + 'TestClass' + ); expect(errors).toHaveLength(1); expect(errors[0].message).toContain('should be a valid geojson point'); }); @@ -499,7 +498,10 @@ describe('ParamsValidatorUtils', () => { properties: {}, geometry: { type: 'LineString', - coordinates: [[0, 0], [1, 1]] + coordinates: [ + [0, 0], + [1, 1] + ] } }; const errors = ParamsValidatorUtils.isGeojsonLineString('attr', lineString, 'TestClass'); @@ -524,8 +526,15 @@ describe('ParamsValidatorUtils', () => { type: 'Feature', properties: {}, geometry: { - type: 'Polygon', coordinates: [ - [[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]] + type: 'Polygon', + coordinates: [ + [ + [0, 0], + [1, 0], + [1, 1], + [0, 1], + [0, 0] + ] ] } }; @@ -552,7 +561,10 @@ describe('ParamsValidatorUtils', () => { properties: {}, geometry: { type: 'MultiPoint', - coordinates: [[0, 0], [1, 1]] + coordinates: [ + [0, 0], + [1, 1] + ] } }; const errors = ParamsValidatorUtils.isGeojsonMultiPoint('attr', multiPoint, 'TestClass'); @@ -579,8 +591,14 @@ describe('ParamsValidatorUtils', () => { geometry: { type: 'MultiLineString', coordinates: [ - [[0, 0], [1, 1]], - [[2, 2], [3, 3]] + [ + [0, 0], + [1, 1] + ], + [ + [2, 2], + [3, 3] + ] ] } }; @@ -609,10 +627,22 @@ describe('ParamsValidatorUtils', () => { type: 'MultiPolygon', coordinates: [ [ - [[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]] + [ + [0, 0], + [1, 0], + [1, 1], + [0, 1], + [0, 0] + ] ], [ - [[2, 2], [3, 2], [3, 3], [2, 3], [2, 2]] + [ + [2, 2], + [3, 2], + [3, 3], + [2, 3], + [2, 2] + ] ] ] } @@ -637,16 +667,7 @@ describe('ParamsValidatorUtils', () => { test('should return no errors for a valid GeoJSON FeatureCollection', () => { const featureCollection: GeoJSON.FeatureCollection = { type: 'FeatureCollection', - features: [ - { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [0, 0] - }, - properties: {} - } - ] + features: [{ type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }] }; const errors = ParamsValidatorUtils.isGeojsonFeatureCollection('attr', featureCollection, 'TestClass'); expect(errors).toEqual([]); @@ -658,10 +679,7 @@ describe('ParamsValidatorUtils', () => { }); test('should return no errors for a valid empty GeoJSON FeatureCollection', () => { - const featureCollection: GeoJSON.FeatureCollection = { - type: 'FeatureCollection', - features: [] - }; + const featureCollection: GeoJSON.FeatureCollection = { type: 'FeatureCollection', features: [] }; const errors = ParamsValidatorUtils.isGeojsonFeatureCollection('attr', featureCollection, 'TestClass'); expect(errors).toEqual([]); }); diff --git a/packages/evolution-common/src/utils/__tests__/PhysicsUtils.test.ts b/packages/evolution-common/src/utils/__tests__/PhysicsUtils.test.ts index 85a81922a..856bfa940 100644 --- a/packages/evolution-common/src/utils/__tests__/PhysicsUtils.test.ts +++ b/packages/evolution-common/src/utils/__tests__/PhysicsUtils.test.ts @@ -9,62 +9,34 @@ import { getBirdDistanceMeters, getBirdSpeedKph } from '../PhysicsUtils'; import * as PhysicsUtils from '../PhysicsUtils'; describe('getBirdDistanceMeters', () => { - - const origin = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [45.5, -75.5] - }, - properties: {} - } as GeoJSON.Feature; + const origin = { type: 'Feature', geometry: { type: 'Point', coordinates: [45.5, -75.5] }, properties: {} } as GeoJSON.Feature; const destination = { type: 'Feature', - geometry: { - type: 'Point', - coordinates: [45.4, -75.6] - }, + geometry: { type: 'Point', coordinates: [45.4, -75.6] }, properties: {} } as GeoJSON.Feature; - const invalidPoint1 = { - type: 'Feature', - geometry: undefined, - properties: {} - } as any; + const invalidPoint1 = { type: 'Feature', geometry: undefined, properties: {} } as any; - const invalidPoint2 = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [45.4, -75.6] - } - } as any; + const invalidPoint2 = { type: 'Feature', geometry: { type: 'Point', coordinates: [45.4, -75.6] } } as any; - const invalidPoint3 = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [] - }, - properties: {} - } as any; + const invalidPoint3 = { type: 'Feature', geometry: { type: 'Point', coordinates: [] }, properties: {} } as any; - const invalidPoint4 = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: undefined - }, - properties: {} - } as any; + const invalidPoint4 = { type: 'Feature', geometry: { type: 'Point', coordinates: undefined }, properties: {} } as any; const polygon = { type: 'Feature', geometry: { type: 'Polygon', - coordinates: [[[45.5, -75.5], [45.4, -75.6], [45.4, -75.4], [45.5, -75.5]]] + coordinates: [ + [ + [45.5, -75.5], + [45.4, -75.6], + [45.4, -75.4], + [45.5, -75.5] + ] + ] }, properties: {} } as GeoJSON.Feature; @@ -103,61 +75,34 @@ describe('getBirdDistanceMeters', () => { }); describe('getBirdSpeedKph', () => { - const origin = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [45.5, -75.5] - }, - properties: {} - } as GeoJSON.Feature; + const origin = { type: 'Feature', geometry: { type: 'Point', coordinates: [45.5, -75.5] }, properties: {} } as GeoJSON.Feature; const destination = { type: 'Feature', - geometry: { - type: 'Point', - coordinates: [45.4, -75.6] - }, + geometry: { type: 'Point', coordinates: [45.4, -75.6] }, properties: {} } as GeoJSON.Feature; - const invalidPoint1 = { - type: 'Feature', - geometry: undefined, - properties: {} - } as any; + const invalidPoint1 = { type: 'Feature', geometry: undefined, properties: {} } as any; - const invalidPoint2 = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [45.4, -75.6] - } - } as any; + const invalidPoint2 = { type: 'Feature', geometry: { type: 'Point', coordinates: [45.4, -75.6] } } as any; - const invalidPoint3 = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [] - }, - properties: {} - } as any; + const invalidPoint3 = { type: 'Feature', geometry: { type: 'Point', coordinates: [] }, properties: {} } as any; - const invalidPoint4 = { - type: 'Feature', - geometry: { - type: 'Point', - coordinates: undefined - }, - properties: {} - } as any; + const invalidPoint4 = { type: 'Feature', geometry: { type: 'Point', coordinates: undefined }, properties: {} } as any; const polygon = { type: 'Feature', geometry: { type: 'Polygon', - coordinates: [[[45.5, -75.5], [45.4, -75.6], [45.4, -75.4], [45.5, -75.5]]] + coordinates: [ + [ + [45.5, -75.5], + [45.4, -75.6], + [45.4, -75.4], + [45.5, -75.5] + ] + ] }, properties: {} } as GeoJSON.Feature; @@ -189,4 +134,3 @@ describe('getBirdSpeedKph', () => { expect(speed).toBeUndefined(); }); }); - diff --git a/packages/evolution-common/src/utils/__tests__/TypeUtils.test.ts b/packages/evolution-common/src/utils/__tests__/TypeUtils.test.ts index 545c6f181..480b32326 100644 --- a/packages/evolution-common/src/utils/__tests__/TypeUtils.test.ts +++ b/packages/evolution-common/src/utils/__tests__/TypeUtils.test.ts @@ -12,8 +12,12 @@ describe('TypeUtils', () => { class TestClass { prop1: string; prop2: number; - method1() { return 'method1'; } - method2() { return true; } + method1() { + return 'method1'; + } + method2() { + return true; + } constructor(prop1: string, prop2: number) { this.prop1 = prop1; @@ -23,10 +27,7 @@ describe('TypeUtils', () => { test('should exclude function property names from a type', () => { type NonFunctionProps = ExcludeFunctionPropertyNames; - const nonFunctionProps: NonFunctionProps = { - prop1: 'value1', - prop2: 123, - }; + const nonFunctionProps: NonFunctionProps = { prop1: 'value1', prop2: 123 }; expect(Object.keys(nonFunctionProps)).toEqual(['prop1', 'prop2']); }); }); diff --git a/packages/evolution-common/src/utils/__tests__/formatters.test.ts b/packages/evolution-common/src/utils/__tests__/formatters.test.ts index 9fcde1023..13ca41625 100644 --- a/packages/evolution-common/src/utils/__tests__/formatters.test.ts +++ b/packages/evolution-common/src/utils/__tests__/formatters.test.ts @@ -8,60 +8,60 @@ import { canadianPostalCodeFormatter, eightDigitsAccessCodeFormatter } from '../formatters'; describe('helper', () => { - describe('eightDigitsAccessCodeFormatter', () => { - test.each([ - // Test case format: [description, input, expected output] - ['formats 8 digits as XXXX-XXXX', '12345678', '1234-5678'], - ['converts existing dashes correctly', '1234-5678', '1234-5678'], - ['converts underscores to dashes', '1234_5678', '1234-5678'], - ['removes letters and special characters', 'ab12cd34ef56gh78', '1234-5678'], - ['handles mixed special characters', '12_34-ab56!78', '1234-5678'], - ['keeps dashes but removes other special chars', '12-34-56-78', '1234-5678'], - ['smaller code with dashes', '123-456', '123-456'], - ['8 digits with dash at the wrong place', '123-45678', '1234-5678'], - ['does not format if less than 8 digits', '123456', '123456'], - ['truncates to 9 characters max', '1234567890', '1234-5678'], - ['handles input with spaces', '1234 5678', '1234-5678'], - ['handles input with spaces before and after', ' 12 34 5678 ', '1234-5678'], - ['handles empty string', '', ''], - ['9 characters, but less than 8 digits', '123-45-6-', '1234-56'], - ['handles with non-numeric at the end', '1234db', '1234'], - ['partial access code, 1 character', '1', '1'], - ['partial access code, 2 characters', '12', '12'], - ['partial access code, 3 characters', '123', '123'], - ['partial access code, 4 characters', '1234', '1234'], - // FIXME The dash should remain in the formatted code, but it is not the case currently with the delimiterLazyShow - ['partial access code, 4 characters + dash', '1234-', '1234-'], - ['partial access code, 4 characters + dash + 2 characters', '1234-23', '1234-23'], - ['partial access code, 5 characters', '12345', '12345'], - ['partial access code, 6 characters', '123456', '123456'], - ['partial access code, 7 characters', '1234567', '1234567'], - ])('%s: %s => %s', (_, input, expected) => { - expect(eightDigitsAccessCodeFormatter(input)).toBe(expected); + describe('eightDigitsAccessCodeFormatter', () => { + test.each([ + // Test case format: [description, input, expected output] + ['formats 8 digits as XXXX-XXXX', '12345678', '1234-5678'], + ['converts existing dashes correctly', '1234-5678', '1234-5678'], + ['converts underscores to dashes', '1234_5678', '1234-5678'], + ['removes letters and special characters', 'ab12cd34ef56gh78', '1234-5678'], + ['handles mixed special characters', '12_34-ab56!78', '1234-5678'], + ['keeps dashes but removes other special chars', '12-34-56-78', '1234-5678'], + ['smaller code with dashes', '123-456', '123-456'], + ['8 digits with dash at the wrong place', '123-45678', '1234-5678'], + ['does not format if less than 8 digits', '123456', '123456'], + ['truncates to 9 characters max', '1234567890', '1234-5678'], + ['handles input with spaces', '1234 5678', '1234-5678'], + ['handles input with spaces before and after', ' 12 34 5678 ', '1234-5678'], + ['handles empty string', '', ''], + ['9 characters, but less than 8 digits', '123-45-6-', '1234-56'], + ['handles with non-numeric at the end', '1234db', '1234'], + ['partial access code, 1 character', '1', '1'], + ['partial access code, 2 characters', '12', '12'], + ['partial access code, 3 characters', '123', '123'], + ['partial access code, 4 characters', '1234', '1234'], + // FIXME The dash should remain in the formatted code, but it is not the case currently with the delimiterLazyShow + ['partial access code, 4 characters + dash', '1234-', '1234-'], + ['partial access code, 4 characters + dash + 2 characters', '1234-23', '1234-23'], + ['partial access code, 5 characters', '12345', '12345'], + ['partial access code, 6 characters', '123456', '123456'], + ['partial access code, 7 characters', '1234567', '1234567'] + ])('%s: %s => %s', (_, input, expected) => { + expect(eightDigitsAccessCodeFormatter(input)).toBe(expected); + }); }); - }); - describe('canadianPostalCodeFormatter', () => { - test.each([ - // Test case format: [description, input, expected output] - ['lower case no space', 'h2e1r3', 'H2E 1R3'], - ['correctly formatted', 'H2E 1R3', 'H2E 1R3'], - ['With invalid delimiters', 'H2e-1r3', 'H2E 1R3'], - ['Many invalid characters', 'h.2.e.1.r.3', 'H2E 1R3'], - ['all numeric', '123456', '123 456'], - ['all letters', 'abcdef', 'ABC DEF'], - ['longer than 7 characters', 'h2e1y6u7r4', 'H2E 1Y6'], - ['handles input with many spaces', 'h 2 e 1r3', 'H2E 1R3'], - ['handles input with spaces before and after', ' H2E 1R3 ', 'H2E 1R3'], - ['handles empty string', '', ''], - ['partial postal code, 1 character', 'h', 'H'], - ['partial postal code, 2 characters', 'h2', 'H2'], - ['partial postal code, 3 characters', 'h2e', 'H2E'], - ['partial postal code, 4 characters', 'h2e1', 'H2E1'], - ['partial postal code, 5 characters', 'h2e1r', 'H2E1R'], - ['partial postal code, 4 characters + space', 'h2e1 ', 'H2E1 '], - ])('%s: %s => %s', (_, input, expected) => { - expect(canadianPostalCodeFormatter(input)).toBe(expected); + describe('canadianPostalCodeFormatter', () => { + test.each([ + // Test case format: [description, input, expected output] + ['lower case no space', 'h2e1r3', 'H2E 1R3'], + ['correctly formatted', 'H2E 1R3', 'H2E 1R3'], + ['With invalid delimiters', 'H2e-1r3', 'H2E 1R3'], + ['Many invalid characters', 'h.2.e.1.r.3', 'H2E 1R3'], + ['all numeric', '123456', '123 456'], + ['all letters', 'abcdef', 'ABC DEF'], + ['longer than 7 characters', 'h2e1y6u7r4', 'H2E 1Y6'], + ['handles input with many spaces', 'h 2 e 1r3', 'H2E 1R3'], + ['handles input with spaces before and after', ' H2E 1R3 ', 'H2E 1R3'], + ['handles empty string', '', ''], + ['partial postal code, 1 character', 'h', 'H'], + ['partial postal code, 2 characters', 'h2', 'H2'], + ['partial postal code, 3 characters', 'h2e', 'H2E'], + ['partial postal code, 4 characters', 'h2e1', 'H2E1'], + ['partial postal code, 5 characters', 'h2e1r', 'H2E1R'], + ['partial postal code, 4 characters + space', 'h2e1 ', 'H2E1 '] + ])('%s: %s => %s', (_, input, expected) => { + expect(canadianPostalCodeFormatter(input)).toBe(expected); + }); }); - }); -}); \ No newline at end of file +}); diff --git a/packages/evolution-common/src/utils/__tests__/helpers.test.ts b/packages/evolution-common/src/utils/__tests__/helpers.test.ts index 6e45daaea..edb6f848f 100644 --- a/packages/evolution-common/src/utils/__tests__/helpers.test.ts +++ b/packages/evolution-common/src/utils/__tests__/helpers.test.ts @@ -14,10 +14,7 @@ import config from 'chaire-lib-common/lib/config/shared/project.config'; import * as Helpers from '../helpers'; import { UserInterviewAttributes } from '../../services/questionnaire/types'; -jest.mock('i18next', () => ({ - t: jest.fn(), - language: 'en' -})); +jest.mock('i18next', () => ({ t: jest.fn(), language: 'en' })); jest.mock('uuid', () => ({ v4: jest.fn().mockReturnValue('arbitrary uuid') })); const mockedT = i18n.t as jest.MockedFunction; @@ -27,31 +24,15 @@ const interviewAttributes: UserInterviewAttributes = { participant_id: 1, is_completed: false, is_questionable: false, - response: { - section1: { - q1: 'abc', - q2: 3 - }, - section2: { - q1: 'test' - } - } as any, - validations: { - section1: { - q1: true, - q2: false - }, - section2: { - q1: true - } - } as any, + response: { section1: { q1: 'abc', q2: 3 }, section2: { q1: 'test' } } as any, + validations: { section1: { q1: true, q2: false }, section2: { q1: true } } as any, is_valid: true }; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, @@ -59,7 +40,6 @@ const userAttributes = { showUserInfo: true }; - const interviewAttributesWithHh: UserInterviewAttributes = { id: 1, uuid: 'arbitrary uuid', @@ -67,40 +47,17 @@ const interviewAttributesWithHh: UserInterviewAttributes = { is_completed: false, is_questionable: false, response: { - section1: { - q1: 'abc', - q2: 3 - }, - section2: { - q1: 'test' - }, + section1: { q1: 'abc', q2: 3 }, + section2: { q1: 'test' }, household: { size: 1, persons: { - personId1: { - _uuid: 'personId1', - journeys: { - journeyId1: { - _uuid: 'journeyId1', - _sequence: 1 - } - } - }, - personId2: { - _uuid: 'personId2' - } + personId1: { _uuid: 'personId1', journeys: { journeyId1: { _uuid: 'journeyId1', _sequence: 1 } } }, + personId2: { _uuid: 'personId2' } } } } as any, - validations: { - section1: { - q1: true, - q2: false - }, - section2: { - q1: true - } - } as any, + validations: { section1: { q1: true, q2: false }, section2: { q1: true } } as any, is_valid: true }; @@ -150,9 +107,17 @@ each([ [null, 'string', null], [undefined, 'string', undefined], [['str1', 'str2'], 'string', 'str1', ['str1', 'str2']], - [{ type: 'Feature', geometry: { type: 'Point', coordinates: [0,0] }, properties: {} }, 'geojson', { type: 'Feature', geometry: { type: 'Point', coordinates: [0,0] }, properties: {} }], + [ + { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} }, + 'geojson', + { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} } + ], // Should add the properties to the feature - [{ type: 'Feature', geometry: { type: 'Point', coordinates: [0,0] } }, 'geojson', { type: 'Feature', geometry: { type: 'Point', coordinates: [0,0] }, properties: {} }], + [ + { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] } }, + 'geojson', + { type: 'Feature', geometry: { type: 'Point', coordinates: [0, 0] }, properties: {} } + ], [{ type: 'Feature', geometry: 'not a geometry' }, 'geojson', null], [3, 'geojson', null], ['not a feature', 'geojson', null], @@ -190,7 +155,7 @@ each([ ['foo.bar.test', '../../../myField', 'myField'], ['foo.bar.test', '../../../../myField', 'myField'], ['foo.bar.test', '../../myField.abc', 'foo.myField.abc'], - ['foo.bar.test', '../../../', null], + ['foo.bar.test', '../../../', null] ]).test('getPath: %s %s', (path, relativePath, expected) => { expect(Helpers.getPath(path, relativePath)).toEqual(expected); }); @@ -207,7 +172,7 @@ each([ ['section1.q3', undefined, undefined, undefined], ['section1.q1', 'def', '../../section2.q1', ((interviewAttributesWithHh.response as any).section2 as any).q1], ['section1.q1', 'def', 'section2.q1', null], - ['section1', 'def', undefined, ((interviewAttributesWithHh.response as any).section1 as any)], + ['section1', 'def', undefined, (interviewAttributesWithHh.response as any).section1 as any] ]).test('getResponse: %s %s %s', (path, defaultValue, relativePath, expected) => { expect(Helpers.getResponse(interviewAttributesWithHh, path, defaultValue, relativePath)).toEqual(expected); }); @@ -233,7 +198,7 @@ each([ ['section1', undefined, null], ['section1', true, null], ['section1', false, null], - ['section1.q2', undefined, false], + ['section1.q2', undefined, false] ]).test('getValidation: %s %s %s', (path, defaultValue, expected) => { expect(Helpers.getValidation(interviewAttributesWithHh, path, defaultValue)).toEqual(expected); }); @@ -256,7 +221,7 @@ each([ [[{ test: 'foo' }, 'abc', true], 'abc'], [['1234 test street', 'H2R2B8'], '1234 test street, H2R2B8'], [['1234 test street', 'H2R2B8', 543], '1234 test street, H2R2B8, 543'], - [['1234 test street', null], '1234 test street'], + [['1234 test street', null], '1234 test street'] ]).test('formatGeocodingQueryStringFromMultipleFields: %s', (fields, expected) => { expect(Helpers.formatGeocodingQueryStringFromMultipleFields(fields)).toEqual(expected); }); @@ -273,7 +238,7 @@ test('parseString', () => { expect(parseFct).toHaveBeenCalledTimes(1); expect(parseFct).toHaveBeenCalledWith(interviewAttributes, 'some.path', undefined); // With function and user - expect(Helpers.parseString(parseFct, interviewAttributes, 'some.path', userAttributes )).toEqual('test'); + expect(Helpers.parseString(parseFct, interviewAttributes, 'some.path', userAttributes)).toEqual('test'); expect(parseFct).toHaveBeenCalledTimes(2); expect(parseFct).toHaveBeenCalledWith(interviewAttributes, 'some.path', userAttributes); }); @@ -318,7 +283,7 @@ test('parse', () => { expect(parseFct).toHaveBeenCalledTimes(1); expect(parseFct).toHaveBeenCalledWith(interviewAttributes, 'some.path', undefined); // With function and user - expect(Helpers.parse(parseFct, interviewAttributes, 'some.path', userAttributes )).toEqual(returnValue); + expect(Helpers.parse(parseFct, interviewAttributes, 'some.path', userAttributes)).toEqual(returnValue); expect(parseFct).toHaveBeenCalledTimes(2); expect(parseFct).toHaveBeenCalledWith(interviewAttributes, 'some.path', userAttributes); }); @@ -357,8 +322,13 @@ test('parseInteger', () => { each([ ['Simple string', 'test', 'test', undefined], ['Object with simple strings', { en: 'english', fr: 'french' }, 'english', undefined], - ['Object with parsed function', { en: arbitraryFunction.mockReturnValue('english'), fr: jest.fn().mockReturnValue('french') }, 'english', 'parseString'], - ['TFunction', arbitraryFunction.mockReturnValue('english'), 'english', 'parseWithT'], + [ + 'Object with parsed function', + { en: arbitraryFunction.mockReturnValue('english'), fr: jest.fn().mockReturnValue('french') }, + 'english', + 'parseString' + ], + ['TFunction', arbitraryFunction.mockReturnValue('english'), 'english', 'parseWithT'] ]).test('translate string: %s', (_title, toTranslate, expected, testCall?: 'parseString' | 'parseWithT') => { const path = 'some.path'; expect(Helpers.translateString(toTranslate, i18n, interviewAttributes, path, userAttributes)).toEqual(expected); @@ -378,7 +348,7 @@ each([ ['replacement by an object response', 'something.{section1}.other', 'something.unknown.other'], ['many replacements with no dot after second', 'something.{section1.q1}.{section2.q1}other', 'something.abc.testother'], ['undefined replaced value', 'something.{section3.q1}.other', 'something.unknown.other'], - ['bot defined and undefined replaced values', 'something.{section1.q1}.{section3.q1}.other', 'something.abc.unknown.other'], + ['bot defined and undefined replaced values', 'something.{section1.q1}.{section3.q1}.other', 'something.abc.unknown.other'] ]).test('interpolatePath, %s', (_title, path, expectedPath) => { expect(Helpers.interpolatePath(interviewAttributes, path)).toEqual(expectedPath); }); @@ -461,94 +431,137 @@ each([ describe('Group functions', () => { each([ - ['one object at the end', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 1, -1, [], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 3 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { } - }], - ['0 objects', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 0, -1, [], { }], - ['one object at sequence', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 1, 2, [], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2._sequence': 3, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 2 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { } - }], - ['one object at sequence 0, should be 1', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 1, 0, [], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj1._sequence': 2, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2._sequence': 3, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 1 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { } - }], - ['one object at sequence too large', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 1, 5, [], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 3 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { } - }], - ['2 objects, no previous objects', {}, 2, undefined, [], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 1 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { }, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { _uuid: 'newObj1', _sequence: 2 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { } - }], - ['3 objects, at sequence 2', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 3, 2, [], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2._sequence': 5, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 2 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { }, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { _uuid: 'newObj1', _sequence: 3 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { }, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': { _uuid: 'newObj2', _sequence: 4 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': { } - }], - ['with attributes, same count', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 3, 2, [{ myVal: 'first' }, { myVal: 'second', other: 'test' }, { myVal: 'third' }], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2._sequence': 5, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 2, myVal: 'first' }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { }, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { _uuid: 'newObj1', _sequence: 3, myVal: 'second', other: 'test' }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { }, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': { _uuid: 'newObj2', _sequence: 4, myVal: 'third' }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': { } - }], - ['with attributes, unequal count', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 3, 2, [{ myVal: 'first' }, { myVal: 'second', other: 'test' }], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2._sequence': 5, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 2, myVal: 'first' }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { }, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { _uuid: 'newObj1', _sequence: 3, myVal: 'second', other: 'test' }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { }, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': { _uuid: 'newObj2', _sequence: 4 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': { } - }], - ['negative object count', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, -1, -1, [], { }], - ['negative, but not -1, sequence', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 1, -2, [], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 3 }, - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { } - }] + [ + 'one object at the end', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, + 1, + -1, + [], + { + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 3 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': {} + } + ], + ['0 objects', { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, 0, -1, [], {}], + [ + 'one object at sequence', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, + 1, + 2, + [], + { + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2._sequence': 3, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 2 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': {} + } + ], + [ + 'one object at sequence 0, should be 1', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, + 1, + 0, + [], + { + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj1._sequence': 2, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2._sequence': 3, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 1 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': {} + } + ], + [ + 'one object at sequence too large', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, + 1, + 5, + [], + { + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 3 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': {} + } + ], + [ + '2 objects, no previous objects', + {}, + 2, + undefined, + [], + { + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 1 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': {}, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { _uuid: 'newObj1', _sequence: 2 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': {} + } + ], + [ + '3 objects, at sequence 2', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, + 3, + 2, + [], + { + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2._sequence': 5, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 2 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': {}, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { _uuid: 'newObj1', _sequence: 3 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': {}, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': { _uuid: 'newObj2', _sequence: 4 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': {} + } + ], + [ + 'with attributes, same count', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, + 3, + 2, + [{ myVal: 'first' }, { myVal: 'second', other: 'test' }, { myVal: 'third' }], + { + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2._sequence': 5, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 2, myVal: 'first' }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': {}, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { + _uuid: 'newObj1', + _sequence: 3, + myVal: 'second', + other: 'test' + }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': {}, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': { _uuid: 'newObj2', _sequence: 4, myVal: 'third' }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': {} + } + ], + [ + 'with attributes, unequal count', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, + 3, + 2, + [{ myVal: 'first' }, { myVal: 'second', other: 'test' }], + { + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2._sequence': 5, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 2, myVal: 'first' }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': {}, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': { + _uuid: 'newObj1', + _sequence: 3, + myVal: 'second', + other: 'test' + }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj1': {}, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': { _uuid: 'newObj2', _sequence: 4 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj2': {} + } + ], + ['negative object count', { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, -1, -1, [], {}], + [ + 'negative, but not -1, sequence', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, + 1, + -2, + [], + { + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': { _uuid: 'newObj0', _sequence: 3 }, + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.newObj0': {} + } + ] ]).test('add grouped objects: %s', (_title, previousObj, count, seq, attributes, expected) => { // Mock the uuid return value for (let i = 0; i < count; i++) { @@ -561,66 +574,71 @@ describe('Group functions', () => { }); each([ - ['single object to remove at the end', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 'obj2', {}, [ - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2' - ]], - ['multiple objects to remove at the end', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 }, - obj3: { _uuid: 'obj3', _sequence: 3 } - }, ['obj2', 'obj3'], {}, [ - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj3', - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj3' - ]], - ['single object to remove in the middle', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 }, - obj3: { _uuid: 'obj3', _sequence: 3 } - }, 'obj2', { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj3._sequence': 2 - }, [ - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2' - ]], - ['multiple objects to remove at various location', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 }, - obj3: { _uuid: 'obj3', _sequence: 3 }, - obj4: { _uuid: 'obj4', _sequence: 4 }, - obj5: { _uuid: 'obj5', _sequence: 5 } - }, ['obj2', 'obj4'], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj3._sequence': 2, - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj5._sequence': 3 - }, [ - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj4', - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj4' - ]], - ['unexisting objects', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, 'objnone', {}, []], - ['no path to remove', { - obj1: { _uuid: 'obj1', _sequence: 1 }, - obj2: { _uuid: 'obj2', _sequence: 2 } - }, [], {}, []], - ['No sequence in previous object data', { - obj1: { _uuid: 'obj1' }, - obj2: { _uuid: 'obj2' }, - obj3: { _uuid: 'obj3' } - }, ['obj2'], { - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj3._sequence': 2 - }, [ - 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', - 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2' - ]] + [ + 'single object to remove at the end', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, + 'obj2', + {}, + [ + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2' + ] + ], + [ + 'multiple objects to remove at the end', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 }, obj3: { _uuid: 'obj3', _sequence: 3 } }, + ['obj2', 'obj3'], + {}, + [ + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj3', + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj3' + ] + ], + [ + 'single object to remove in the middle', + { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 }, obj3: { _uuid: 'obj3', _sequence: 3 } }, + 'obj2', + { 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj3._sequence': 2 }, + [ + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2' + ] + ], + [ + 'multiple objects to remove at various location', + { + obj1: { _uuid: 'obj1', _sequence: 1 }, + obj2: { _uuid: 'obj2', _sequence: 2 }, + obj3: { _uuid: 'obj3', _sequence: 3 }, + obj4: { _uuid: 'obj4', _sequence: 4 }, + obj5: { _uuid: 'obj5', _sequence: 5 } + }, + ['obj2', 'obj4'], + { + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj3._sequence': 2, + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj5._sequence': 3 + }, + [ + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj4', + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj4' + ] + ], + ['unexisting objects', { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, 'objnone', {}, []], + ['no path to remove', { obj1: { _uuid: 'obj1', _sequence: 1 }, obj2: { _uuid: 'obj2', _sequence: 2 } }, [], {}, []], + [ + 'No sequence in previous object data', + { obj1: { _uuid: 'obj1' }, obj2: { _uuid: 'obj2' }, obj3: { _uuid: 'obj3' } }, + ['obj2'], + { 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj3._sequence': 2 }, + [ + 'response.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2', + 'validations.household.persons.personId1.journeys.journeyId1.visitedPlaces.obj2' + ] + ] ]).test('remove grouped objects: %s', (_title, previousObj, paths, expectedByPath, expectedUnset) => { const basePath = 'household.persons.personId1.journeys.journeyId1.visitedPlaces'; const interview = _cloneDeep(interviewAttributesWithHh); @@ -632,10 +650,14 @@ describe('Group functions', () => { describe('getWidgetChoiceFromValue', () => { const selectChoices = [ - { groupShortname: 'group', groupLabel: 'groupLabel', choices: [ - { value: 'sub_value1', label: 'label1' }, - { value: 'sub_value2', label: 'label2' } - ] }, + { + groupShortname: 'group', + groupLabel: 'groupLabel', + choices: [ + { value: 'sub_value1', label: 'label1' }, + { value: 'sub_value2', label: 'label2' } + ] + }, { value: 'value3', label: 'label3' } ]; const radioChoices = [ @@ -657,15 +679,14 @@ describe('getWidgetChoiceFromValue', () => { ['radio', false, radioChoices, radioChoices[2]], ['radio', 'undefinedValue', radioChoices, undefined], ['checkbox', 'value1', checkboxChoices, checkboxChoices[0]], - ['checkbox', 'undefinedValue', checkboxChoices, undefined], + ['checkbox', 'undefinedValue', checkboxChoices, undefined] ]).test('Widgets with choice array: %s %s', (inputType, value, choices, expected) => { - const widgetChoice = Helpers.getWidgetChoiceFromValue({ widget: { - type: 'question', - inputType: inputType, - path: 'test', - choices, - label: 'test' - }, value, interview: interviewAttributes, path: 'test' }); + const widgetChoice = Helpers.getWidgetChoiceFromValue({ + widget: { type: 'question', inputType: inputType, path: 'test', choices, label: 'test' }, + value, + interview: interviewAttributes, + path: 'test' + }); expect(widgetChoice).toEqual(expected); }); @@ -677,37 +698,36 @@ describe('getWidgetChoiceFromValue', () => { ['radio', false, radioChoices, radioChoices[2]], ['radio', 'undefinedValue', radioChoices, undefined], ['checkbox', 'value1', checkboxChoices, checkboxChoices[0]], - ['checkbox', 'undefinedValue', checkboxChoices, undefined], + ['checkbox', 'undefinedValue', checkboxChoices, undefined] ]).test('Widgets with choice function: %s %s', (inputType, value, choices, expected) => { const choiceFct = jest.fn().mockReturnValue(choices); - const widgetChoice = Helpers.getWidgetChoiceFromValue({ widget: { - type: 'question', - inputType: inputType, - path: 'test', - choices: choiceFct, - label: 'test' - }, value, interview: interviewAttributes, path: 'test' }); + const widgetChoice = Helpers.getWidgetChoiceFromValue({ + widget: { type: 'question', inputType: inputType, path: 'test', choices: choiceFct, label: 'test' }, + value, + interview: interviewAttributes, + path: 'test' + }); expect(widgetChoice).toEqual(expected); expect(choiceFct).toHaveBeenCalledWith(interviewAttributes, 'test'); }); test('Question widgets with no choices', () => { - const widgetChoice = Helpers.getWidgetChoiceFromValue({ widget: { - type: 'question', - inputType: 'string', - path: 'test', - label: 'test' - }, value: 'value', interview: interviewAttributes, path: 'test' }); + const widgetChoice = Helpers.getWidgetChoiceFromValue({ + widget: { type: 'question', inputType: 'string', path: 'test', label: 'test' }, + value: 'value', + interview: interviewAttributes, + path: 'test' + }); expect(widgetChoice).toEqual(undefined); }); test('Other widgets', () => { - const widgetChoice = Helpers.getWidgetChoiceFromValue({ widget: { - type: 'text', - path: 'test', - text: 'test' - }, value: 'value', interview: interviewAttributes, path: 'test' }); + const widgetChoice = Helpers.getWidgetChoiceFromValue({ + widget: { type: 'text', path: 'test', text: 'test' }, + value: 'value', + interview: interviewAttributes, + path: 'test' + }); expect(widgetChoice).toEqual(undefined); }); - }); diff --git a/packages/evolution-frontend/src/actions/__tests__/Survey.test.ts b/packages/evolution-frontend/src/actions/__tests__/Survey.test.ts index 02928c8bf..1ba4e9be7 100644 --- a/packages/evolution-frontend/src/actions/__tests__/Survey.test.ts +++ b/packages/evolution-frontend/src/actions/__tests__/Survey.test.ts @@ -25,43 +25,29 @@ let fetchStatus: number[] = []; //jest.mock('node-fetch', () => jest.fn().mockImplementation(() => Promise.resolve({ status: fetchStatus.pop() || 200, json: jsonFetchResolve }))); //const fetchMock = fetch as jest.MockedFunction; - jest.mock('@zeit/fetch-retry', () => { const fetchMock = jest.fn().mockImplementation(() => Promise.resolve({ status: fetchStatus.pop() || 200, json: jsonFetchResolve })); return () => fetchMock; }); const fetchRetryMock = fetchRetry as jest.MockedFunction; -jest.mock('../../config/i18n.config', () => ({ - language: 'en' -})); +jest.mock('../../config/i18n.config', () => ({ language: 'en' })); jest.mock('evolution-common/lib/utils/helpers', () => { // Require the original module to not be mocked... - const originalModule = - jest.requireActual('evolution-common/lib/utils/helpers'); + const originalModule = jest.requireActual('evolution-common/lib/utils/helpers'); - return { - ...originalModule, - addGroupedObjects: jest.fn(), - removeGroupedObjects: jest.fn() - }; + return { ...originalModule, addGroupedObjects: jest.fn(), removeGroupedObjects: jest.fn() }; }); const mockedAddGroupedObject = addGroupedObjects as jest.MockedFunction; const mockedRemoveGroupedObject = removeGroupedObjects as jest.MockedFunction; // Mock wrong response code handler -jest.mock('../../services/errorManagement/errorHandling', () => ({ - handleHttpOtherResponseCode: jest.fn(), - handleClientError: jest.fn() -})); +jest.mock('../../services/errorManagement/errorHandling', () => ({ handleHttpOtherResponseCode: jest.fn(), handleClientError: jest.fn() })); const mockedHandleHttpOtherResponseCode = handleHttpOtherResponseCode as jest.MockedFunction; const mockedHandleClientError = handleClientError as jest.MockedFunction; // Mock the navigation service jest.mock('evolution-common/lib/services/questionnaire/sections/NavigationService', () => ({ - createNavigationService: jest.fn().mockReturnValue(({ - initNavigationState: jest.fn(), - navigate: jest.fn() - })) + createNavigationService: jest.fn().mockReturnValue({ initNavigationState: jest.fn(), navigate: jest.fn() }) })); const navigationServiceMock = createNavigationService({}); const mockNavigate = navigationServiceMock.navigate as jest.MockedFunction; @@ -73,25 +59,8 @@ const interviewAttributes: UserRuntimeInterviewAttributes = { uuid: 'arbitrary uuid', participant_id: 1, is_completed: false, - response: { - _language: 'en', - section1: { - q1: 'abc', - q2: 3 - }, - section2: { - q1: 'test' - } - } as any, - validations: { - section1: { - q1: true, - q2: false - }, - section2: { - q1: true - } - } as any, + response: { _language: 'en', section1: { q1: 'abc', q2: 3 }, section2: { q1: 'test' } } as any, + validations: { section1: { q1: true, q2: false }, section2: { q1: true } } as any, is_valid: true, widgets: {}, groups: {}, @@ -102,7 +71,7 @@ const interviewAttributes: UserRuntimeInterviewAttributes = { const testUser = { id: 1, username: 'test', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: jest.fn(), is_admin: false, @@ -112,7 +81,13 @@ const testUser = { // Mock functions jest.mock('../utils', () => ({ - prepareSectionWidgets: jest.fn().mockImplementation((_sectionShortname, interview, _affectedPaths, valuesByPath) => ({ updatedInterview: _cloneDeep(interview), updatedValuesByPath: _cloneDeep(valuesByPath), needUpdate: false })) + prepareSectionWidgets: jest + .fn() + .mockImplementation((_sectionShortname, interview, _affectedPaths, valuesByPath) => ({ + updatedInterview: _cloneDeep(interview), + updatedValuesByPath: _cloneDeep(valuesByPath), + needUpdate: false + })) })); const mockDispatch = jest.fn().mockImplementation((action) => { if (action.type === 'UPDATE_INTERVIEW') { @@ -123,17 +98,12 @@ const mockDispatch = jest.fn().mockImplementation((action) => { const mockPrepareSectionWidgets = prepareSectionWidgets as jest.MockedFunction; // Interview to use as state, reset before each test, but can be updated by the update state action let interviewAsState = _cloneDeep(interviewAttributes); -const mockGetState = jest.fn().mockImplementation(() => ({ - survey: { - interview: interviewAsState, - interviewLoaded: true, - errors: undefined, - navigationService: navigationServiceMock - }, - auth: { - user: testUser - } -})); +const mockGetState = jest + .fn() + .mockImplementation(() => ({ + survey: { interview: interviewAsState, interviewLoaded: true, errors: undefined, navigationService: navigationServiceMock }, + auth: { user: testUser } + })); beforeEach(() => { jest.clearAllMocks(); @@ -142,7 +112,6 @@ beforeEach(() => { }); describe('Update interview', () => { - test('Call with an interview, no validation change, no error', async () => { // Prepare mock and test data const updateCallback = jest.fn(); @@ -154,7 +123,10 @@ describe('Update interview', () => { expectedInterviewAsState.sectionLoaded = 'section'; // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes) }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes) }, + updateCallback + ); await callback(mockDispatch, mockGetState); // Verifications @@ -163,22 +135,30 @@ describe('Update interview', () => { const actualInterviewArg = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg, expectedInterviewToPrepare)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepare, { 'response.section1.q1': true }, { ...valuesByPath }, false, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepare, + { 'response.section1.q1': true }, + { ...valuesByPath }, + false, + testUser + ); expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/updateInterview', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ - id: interviewAttributes.id, - interviewId: interviewAttributes.uuid, - participant_id: interviewAttributes.participant_id, - valuesByPath: { ...valuesByPath, sectionLoaded: 'section' }, - unsetPaths: [] + expect(fetchRetryMock).toHaveBeenCalledWith( + '/api/survey/updateInterview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + id: interviewAttributes.id, + interviewId: interviewAttributes.uuid, + participant_id: interviewAttributes.participant_id, + valuesByPath: { ...valuesByPath, sectionLoaded: 'section' }, + unsetPaths: [] + }) }) - })); + ); expect(mockDispatch).toHaveBeenCalledTimes(3); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, @@ -186,9 +166,7 @@ describe('Update interview', () => { errors: {}, submitted: false }); - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsState); }); @@ -197,12 +175,7 @@ describe('Update interview', () => { const updateCallback = jest.fn(); jsonFetchResolve.mockResolvedValue({ status: 'success', interviewId: interviewAttributes.uuid }); const valuesByPath = { 'validations.section1.q1': false }; - const userAction = { - type: 'widgetInteraction' as const, - widgetType: 'string', - path: 'response.section1.q1', - value: 'foo' - }; + const userAction = { type: 'widgetInteraction' as const, widgetType: 'string', path: 'response.section1.q1', value: 'foo' }; // Both path in user action and valuesByPath should have been updated const expectedInterviewToPrepare = _cloneDeep(interviewAttributes); (expectedInterviewToPrepare.response as any).section1.q1 = 'foo'; @@ -211,7 +184,10 @@ describe('Update interview', () => { expectedInterviewAsState.sectionLoaded = 'section'; // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes), userAction }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes), userAction }, + updateCallback + ); await callback(mockDispatch, mockGetState); // Verifications @@ -220,23 +196,31 @@ describe('Update interview', () => { const actualInterviewArg = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg, expectedInterviewToPrepare)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepare, { 'response.section1.q1': true, 'validations.section1.q1': true }, { ...valuesByPath }, false, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepare, + { 'response.section1.q1': true, 'validations.section1.q1': true }, + { ...valuesByPath }, + false, + testUser + ); expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/updateInterview', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ - id: interviewAttributes.id, - interviewId: interviewAttributes.uuid, - participant_id: interviewAttributes.participant_id, - valuesByPath: { ...valuesByPath, sectionLoaded: 'section' }, - unsetPaths: [], - userAction + expect(fetchRetryMock).toHaveBeenCalledWith( + '/api/survey/updateInterview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + id: interviewAttributes.id, + interviewId: interviewAttributes.uuid, + participant_id: interviewAttributes.participant_id, + valuesByPath: { ...valuesByPath, sectionLoaded: 'section' }, + unsetPaths: [], + userAction + }) }) - })); + ); expect(mockDispatch).toHaveBeenCalledTimes(3); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, @@ -244,9 +228,7 @@ describe('Update interview', () => { errors: {}, submitted: false }); - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsState); }); @@ -255,10 +237,7 @@ describe('Update interview', () => { const updateCallback = jest.fn(); jsonFetchResolve.mockResolvedValue({ status: 'success', interviewId: interviewAttributes.uuid }); const valuesByPath = { 'response.section1.q1': 'foo' }; - const buttonClickUserAction = { - type: 'buttonClick' as const, - buttonId: 'test' - }; + const buttonClickUserAction = { type: 'buttonClick' as const, buttonId: 'test' }; const unsetPaths = ['response.section2.q1']; const expectedInterviewToPrepare = _cloneDeep(interviewAttributes); (expectedInterviewToPrepare.response as any).section1.q1 = 'foo'; @@ -266,14 +245,23 @@ describe('Update interview', () => { mockPrepareSectionWidgets.mockImplementationOnce((_sectionShortname, interview, _affectedPaths, valuesByPath) => { const innerInterview = _cloneDeep(interview); (innerInterview.validations as any).section1.q2 = true; - return { updatedInterview: innerInterview, updatedValuesByPath: { ... valuesByPath, 'validations.section1.q2': true }, needUpdate: false }; + return { updatedInterview: innerInterview, updatedValuesByPath: { ...valuesByPath, 'validations.section1.q2': true }, needUpdate: false }; }); const expectedInterviewAsState = _cloneDeep(expectedInterviewToPrepare); expectedInterviewAsState.sectionLoaded = 'section'; (expectedInterviewAsState.validations as any).section1.q2 = true; // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), unsetPaths, interview: _cloneDeep(interviewAttributes), userAction: buttonClickUserAction }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { + sectionShortname: 'section', + valuesByPath: _cloneDeep(valuesByPath), + unsetPaths, + interview: _cloneDeep(interviewAttributes), + userAction: buttonClickUserAction + }, + updateCallback + ); await callback(mockDispatch, mockGetState); // Verifications @@ -282,23 +270,31 @@ describe('Update interview', () => { const actualInterviewArg = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg, expectedInterviewToPrepare)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepare, { 'response.section1.q1': true, 'response.section2.q1': true }, { ...valuesByPath }, false, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepare, + { 'response.section1.q1': true, 'response.section2.q1': true }, + { ...valuesByPath }, + false, + testUser + ); expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/updateInterview', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ - id: interviewAttributes.id, - interviewId: interviewAttributes.uuid, - participant_id: interviewAttributes.participant_id, - valuesByPath: { ...valuesByPath, 'validations.section1.q2': true, sectionLoaded: 'section' }, - unsetPaths: ['response.section2.q1'], - userAction: buttonClickUserAction + expect(fetchRetryMock).toHaveBeenCalledWith( + '/api/survey/updateInterview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + id: interviewAttributes.id, + interviewId: interviewAttributes.uuid, + participant_id: interviewAttributes.participant_id, + valuesByPath: { ...valuesByPath, 'validations.section1.q2': true, sectionLoaded: 'section' }, + unsetPaths: ['response.section2.q1'], + userAction: buttonClickUserAction + }) }) - })); + ); expect(mockDispatch).toHaveBeenCalledTimes(3); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, @@ -306,9 +302,7 @@ describe('Update interview', () => { errors: {}, submitted: false }); - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsState); }); @@ -317,10 +311,7 @@ describe('Update interview', () => { const updateCallback = jest.fn(); jsonFetchResolve.mockResolvedValue({ status: 'success', interviewId: interviewAttributes.uuid }); const valuesByPath = { 'response.section1.q1': 'foo' }; - const buttonClickUserAction = { - type: 'buttonClick' as const, - buttonId: 'test' - }; + const buttonClickUserAction = { type: 'buttonClick' as const, buttonId: 'test' }; const unsetPaths = ['response.section2.q1']; const expectedInterviewToPrepare = _cloneDeep(interviewAttributes); (expectedInterviewToPrepare.response as any).section1.q1 = 'foo'; @@ -349,13 +340,26 @@ describe('Update interview', () => { validatedInterview.widgets = nextWidgetStatuses as any; validatedInterview.groups = nextGroupStatuses; mockPrepareSectionWidgets.mockImplementationOnce((_sectionShortname, interview, _affectedPaths, valuesByPath) => { - return { updatedInterview: validatedInterview, updatedValuesByPath: { ... valuesByPath, 'validations.section1.q2': true }, needUpdate: false }; + return { + updatedInterview: validatedInterview, + updatedValuesByPath: { ...valuesByPath, 'validations.section1.q2': true }, + needUpdate: false + }; }); const expectedInterviewAsState = _cloneDeep(validatedInterview); expectedInterviewAsState.sectionLoaded = 'section'; // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), unsetPaths, interview: _cloneDeep(interviewAttributes), userAction: buttonClickUserAction }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { + sectionShortname: 'section', + valuesByPath: _cloneDeep(valuesByPath), + unsetPaths, + interview: _cloneDeep(interviewAttributes), + userAction: buttonClickUserAction + }, + updateCallback + ); await callback(mockDispatch, mockGetState); // Verifications @@ -364,23 +368,35 @@ describe('Update interview', () => { const actualInterviewArg = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg, expectedInterviewToPrepare)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepare, { 'response.section1.q1': true, 'response.section2.q1': true }, { ...valuesByPath }, false, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepare, + { 'response.section1.q1': true, 'response.section2.q1': true }, + { ...valuesByPath }, + false, + testUser + ); expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/updateInterview', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ - id: interviewAttributes.id, - interviewId: interviewAttributes.uuid, - participant_id: interviewAttributes.participant_id, - valuesByPath: { ...valuesByPath, 'validations.section1.q2': true, sectionLoaded: 'section' }, - unsetPaths: ['response.section2.q1'], - userAction: { ...buttonClickUserAction, hiddenWidgets: [ 'invisibleWidgetPath', 'group.groupIdWithSomeInvisible.invisibleWidget' ], invalidWidgets: [ 'visibleInvalidWidgetPath', 'group.groupIdWithSomeInvisible.visibleInvalidWidget' ]} + expect(fetchRetryMock).toHaveBeenCalledWith( + '/api/survey/updateInterview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + id: interviewAttributes.id, + interviewId: interviewAttributes.uuid, + participant_id: interviewAttributes.participant_id, + valuesByPath: { ...valuesByPath, 'validations.section1.q2': true, sectionLoaded: 'section' }, + unsetPaths: ['response.section2.q1'], + userAction: { + ...buttonClickUserAction, + hiddenWidgets: ['invisibleWidgetPath', 'group.groupIdWithSomeInvisible.invisibleWidget'], + invalidWidgets: ['visibleInvalidWidgetPath', 'group.groupIdWithSomeInvisible.visibleInvalidWidget'] + } + }) }) - })); + ); expect(mockDispatch).toHaveBeenCalledTimes(3); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, @@ -388,19 +404,14 @@ describe('Update interview', () => { errors: {}, submitted: false }); - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsState); }); test('Call with a valuesByPath set to undefined', async () => { // Prepare an interview with object data const testInterview = _cloneDeep(interviewAttributes); - testInterview.response.group = { - obj1: { field1: 'value1', field2: 'value2' }, - obj2: { field1: 'value3', field2: 'value4' } - }; + testInterview.response.group = { obj1: { field1: 'value1', field2: 'value2' }, obj2: { field1: 'value3', field2: 'value4' } }; // Prepare mock and test data, to remove the obj2 object const updateCallback = jest.fn(); @@ -414,13 +425,20 @@ describe('Update interview', () => { validatedInterview.widgets = {}; validatedInterview.groups = {}; mockPrepareSectionWidgets.mockImplementationOnce((_sectionShortname, interview, _affectedPaths, valuesByPath) => { - return { updatedInterview: validatedInterview, updatedValuesByPath: { ... valuesByPath, 'validations.section1.q2': true }, needUpdate: false }; + return { + updatedInterview: validatedInterview, + updatedValuesByPath: { ...valuesByPath, 'validations.section1.q2': true }, + needUpdate: false + }; }); const expectedInterviewAsState = _cloneDeep(validatedInterview); expectedInterviewAsState.sectionLoaded = 'section'; // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(testInterview) }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(testInterview) }, + updateCallback + ); await callback(mockDispatch, mockGetState); // Verifications @@ -442,20 +460,21 @@ describe('Update interview', () => { ); expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/updateInterview', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ - id: interviewAttributes.id, - interviewId: interviewAttributes.uuid, - participant_id: interviewAttributes.participant_id, - valuesByPath: { ...valuesByPath, 'validations.section1.q2': true, sectionLoaded: 'section' }, - unsetPaths: ['response.group.obj2'] + expect(fetchRetryMock).toHaveBeenCalledWith( + '/api/survey/updateInterview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + id: interviewAttributes.id, + interviewId: interviewAttributes.uuid, + participant_id: interviewAttributes.participant_id, + valuesByPath: { ...valuesByPath, 'validations.section1.q2': true, sectionLoaded: 'section' }, + unsetPaths: ['response.group.obj2'] + }) }) - })); + ); expect(mockDispatch).toHaveBeenCalledTimes(3); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, @@ -463,16 +482,16 @@ describe('Update interview', () => { errors: {}, submitted: false }); - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsState); }); test('Test with previous and new server errors', async () => { // Prepare mock and test data const previousServerErrors = { 'section1.q1': { en: 'previous server error' }, 'section2.q1': { en: 'error that should not change' } }; - const mockLocalGetState = jest.fn().mockReturnValue({ survey: { interview: interviewAttributes, interviewLoaded: true, errors: previousServerErrors } }); + const mockLocalGetState = jest + .fn() + .mockReturnValue({ survey: { interview: interviewAttributes, interviewLoaded: true, errors: previousServerErrors } }); const updateCallback = jest.fn(); const newServerMessages = { 'section1.q2': { en: 'New server error on q2' } }; jsonFetchResolve.mockResolvedValue({ status: 'invalid', interviewId: interviewAttributes.uuid, messages: newServerMessages }); @@ -483,7 +502,10 @@ describe('Update interview', () => { expectedInterviewAsState.sectionLoaded = 'section'; // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview:_cloneDeep(interviewAttributes) }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes) }, + updateCallback + ); await callback(mockDispatch, mockLocalGetState); // Verifications @@ -492,22 +514,30 @@ describe('Update interview', () => { const actualInterviewArg = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg, expectedInterviewToPrepare)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepare, { 'response.section1.q1': true }, { ...valuesByPath }, false, undefined); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepare, + { 'response.section1.q1': true }, + { ...valuesByPath }, + false, + undefined + ); expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/updateInterview', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ - id: interviewAttributes.id, - interviewId: interviewAttributes.uuid, - participant_id: interviewAttributes.participant_id, - valuesByPath: { ...valuesByPath, sectionLoaded: 'section' }, - unsetPaths: [] + expect(fetchRetryMock).toHaveBeenCalledWith( + '/api/survey/updateInterview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + id: interviewAttributes.id, + interviewId: interviewAttributes.uuid, + participant_id: interviewAttributes.participant_id, + valuesByPath: { ...valuesByPath, sectionLoaded: 'section' }, + unsetPaths: [] + }) }) - })); + ); expect(mockDispatch).toHaveBeenCalledTimes(3); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, @@ -515,9 +545,7 @@ describe('Update interview', () => { errors: { ...newServerMessages, 'section2.q1': previousServerErrors['section2.q1'] }, submitted: false }); - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsState); }); @@ -537,7 +565,10 @@ describe('Update interview', () => { expectedInterviewAsState.sectionLoaded = 'section'; // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes) }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes) }, + updateCallback + ); await callback(mockDispatch, mockGetState); // Verifications @@ -546,50 +577,68 @@ describe('Update interview', () => { const actualInterviewArg = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg, expectedInterviewToPrepare)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepare, { 'response.section1.q1': true }, { ...valuesByPath }, false, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepare, + { 'response.section1.q1': true }, + { ...valuesByPath }, + false, + testUser + ); // Extract the actual interview argument and verify with a strict comparison approach const actualInterviewArg2 = mockPrepareSectionWidgets.mock.calls[1][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg2, expectedInterviewAsState)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewAsState, { 'response.section1.q2': true }, { ...serverUpdatedValues }, true, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewAsState, + { 'response.section1.q2': true }, + { ...serverUpdatedValues }, + true, + testUser + ); expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/updateInterview', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ - id: interviewAttributes.id, - interviewId: interviewAttributes.uuid, - participant_id: interviewAttributes.participant_id, - valuesByPath: { ...valuesByPath, sectionLoaded: 'section' }, - unsetPaths: [] + expect(fetchRetryMock).toHaveBeenCalledWith( + '/api/survey/updateInterview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + id: interviewAttributes.id, + interviewId: interviewAttributes.uuid, + participant_id: interviewAttributes.participant_id, + valuesByPath: { ...valuesByPath, sectionLoaded: 'section' }, + unsetPaths: [] + }) }) - })); + ); expect(mockDispatch).toHaveBeenCalledTimes(3); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, interview: expectedInterviewAsState, - errors: { }, + errors: {}, submitted: false }); - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsState); }); test('With local exception', async () => { // Prepare mock and test data, the prepareWidget function will throw an exception const updateCallback = jest.fn(); - mockPrepareSectionWidgets.mockImplementationOnce(() => { throw 'error'; }); + mockPrepareSectionWidgets.mockImplementationOnce(() => { + throw 'error'; + }); const valuesByPath = { 'response.section1.q1': 'foo' }; const expectedInterviewToPrepare = _cloneDeep(interviewAttributes); (expectedInterviewToPrepare.response as any).section1.q1 = 'foo'; // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes) }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes) }, + updateCallback + ); await callback(mockDispatch, mockGetState); // Verifications @@ -598,15 +647,18 @@ describe('Update interview', () => { const actualInterviewArg = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg, expectedInterviewToPrepare)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepare, { 'response.section1.q1': true }, { ...valuesByPath }, false, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepare, + { 'response.section1.q1': true }, + { ...valuesByPath }, + false, + testUser + ); expect(fetchRetryMock).not.toHaveBeenCalled(); expect(mockDispatch).toHaveBeenCalledTimes(2); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); - expect(mockDispatch).toHaveBeenNthCalledWith(2, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); + expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).not.toHaveBeenCalled(); expect(mockedHandleClientError).toHaveBeenCalledTimes(1); expect(mockedHandleClientError).toHaveBeenCalledWith(new Error('error'), { history: undefined, interviewId: interviewAttributes.id }); @@ -622,7 +674,10 @@ describe('Update interview', () => { (expectedInterviewToPrepare.response as any).section1.q1 = 'foo'; // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes) }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(interviewAttributes) }, + updateCallback + ); await callback(mockDispatch, mockGetState); // Verifications @@ -631,15 +686,18 @@ describe('Update interview', () => { const actualInterviewArg = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg, expectedInterviewToPrepare)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepare, { 'response.section1.q1': true }, { ...valuesByPath }, false, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepare, + { 'response.section1.q1': true }, + { ...valuesByPath }, + false, + testUser + ); expect(fetchRetryMock).toHaveBeenCalledTimes(1); expect(mockDispatch).toHaveBeenCalledTimes(2); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); - expect(mockDispatch).toHaveBeenNthCalledWith(2, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); + expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).not.toHaveBeenCalled(); expect(mockedHandleHttpOtherResponseCode).toHaveBeenCalledTimes(1); expect(mockedHandleHttpOtherResponseCode).toHaveBeenCalledWith(401, mockDispatch, undefined); @@ -648,13 +706,16 @@ describe('Update interview', () => { test('Test with no change and _all set to true, no request to server required', async () => { // Prepare mock and test data const updateCallback = jest.fn(); - const valuesByPath = { '_all': true }; + const valuesByPath = { _all: true }; const initialInterview = _cloneDeep(interviewAttributes); initialInterview.sectionLoaded = 'section'; const expectedInterviewAsState = _cloneDeep(initialInterview); // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(initialInterview) }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: _cloneDeep(initialInterview) }, + updateCallback + ); await callback(mockDispatch, mockGetState); // Verifications @@ -663,13 +724,11 @@ describe('Update interview', () => { const actualInterviewArg = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg, initialInterview)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', initialInterview, { '_all': true }, { ...valuesByPath }, false, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', initialInterview, { _all: true }, { ...valuesByPath }, false, testUser); expect(fetchRetryMock).not.toHaveBeenCalled(); expect(mockDispatch).toHaveBeenCalledTimes(3); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, @@ -677,9 +736,7 @@ describe('Update interview', () => { errors: {}, submitted: true }); - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsState); }); @@ -688,10 +745,7 @@ describe('Update interview', () => { const updateCallback = jest.fn(); jsonFetchResolve.mockResolvedValue({ status: 'success', interviewId: interviewAttributes.uuid }); const valuesByPath = { _all: true }; - const userAction = { - type: 'languageChange' as const, - language: 'fr' - }; + const userAction = { type: 'languageChange' as const, language: 'fr' }; // No expected change to the interview, we set the section loaded to make sure no changes will be detected in the update call const initialInterview = _cloneDeep(interviewAttributes); initialInterview.sectionLoaded = 'section'; @@ -699,7 +753,10 @@ describe('Update interview', () => { const expectedInterviewAsState = _cloneDeep(expectedInterviewToPrepare); // Do the actual test - const callback = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: initialInterview, userAction }, updateCallback); + const callback = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPath), interview: initialInterview, userAction }, + updateCallback + ); await callback(mockDispatch, mockGetState); // Verifications @@ -708,23 +765,31 @@ describe('Update interview', () => { const actualInterviewArg = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArg, expectedInterviewToPrepare)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepare, { _all: true }, { _all: true }, false, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepare, + { _all: true }, + { _all: true }, + false, + testUser + ); expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/updateInterview', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ - id: interviewAttributes.id, - interviewId: interviewAttributes.uuid, - participant_id: interviewAttributes.participant_id, - valuesByPath: { _all: true }, - unsetPaths: [], - userAction + expect(fetchRetryMock).toHaveBeenCalledWith( + '/api/survey/updateInterview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + id: interviewAttributes.id, + interviewId: interviewAttributes.uuid, + participant_id: interviewAttributes.participant_id, + valuesByPath: { _all: true }, + unsetPaths: [], + userAction + }) }) - })); + ); expect(mockDispatch).toHaveBeenCalledTimes(3); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, @@ -732,9 +797,7 @@ describe('Update interview', () => { errors: {}, submitted: true }); - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsState); }); @@ -743,21 +806,11 @@ describe('Update interview', () => { const updateCallback = jest.fn(); jsonFetchResolve.mockResolvedValue({ status: 'success', interviewId: interviewAttributes.uuid }); const valuesByPathCall1 = { 'validations.section1.q1': false }; - const userActionCall1 = { - type: 'widgetInteraction' as const, - widgetType: 'string', - path: 'response.section1.q1', - value: 'foo' - }; + const userActionCall1 = { type: 'widgetInteraction' as const, widgetType: 'string', path: 'response.section1.q1', value: 'foo' }; // Prepare test data for second call const valuesByPathCall2 = { 'validations.section1.q2': true }; - const userActionCall2 = { - type: 'widgetInteraction' as const, - widgetType: 'number', - path: 'response.section1.q2', - value: 1234 - }; + const userActionCall2 = { type: 'widgetInteraction' as const, widgetType: 'number', path: 'response.section1.q2', value: 1234 }; // Both path in user action and valuesByPath should have been updated for both actions const expectedInterviewToPrepareForCall1 = _cloneDeep(interviewAttributes); @@ -772,8 +825,14 @@ describe('Update interview', () => { expectedInterviewAsStateAfterCall2.sectionLoaded = 'section'; // Do the actual test - const callbackCall1 = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPathCall1), userAction: userActionCall1 }, updateCallback); - const callbackCall2 = SurveyActions.startUpdateInterview({ sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPathCall2), userAction: userActionCall2 }, updateCallback); + const callbackCall1 = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPathCall1), userAction: userActionCall1 }, + updateCallback + ); + const callbackCall2 = SurveyActions.startUpdateInterview( + { sectionShortname: 'section', valuesByPath: _cloneDeep(valuesByPathCall2), userAction: userActionCall2 }, + updateCallback + ); const callbackPromise1 = callbackCall1(mockDispatch, mockGetState); const callbackPromise2 = callbackCall2(mockDispatch, mockGetState); await Promise.all([callbackPromise1, callbackPromise2]); @@ -784,39 +843,57 @@ describe('Update interview', () => { const actualInterviewArgCall1 = mockPrepareSectionWidgets.mock.calls[0][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArgCall1, expectedInterviewToPrepareForCall1)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepareForCall1, { 'response.section1.q1': true, 'validations.section1.q1': true }, { ...valuesByPathCall1 }, false, testUser);// Extract the actual interview argument and verify with a strict comparison approach + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepareForCall1, + { 'response.section1.q1': true, 'validations.section1.q1': true }, + { ...valuesByPathCall1 }, + false, + testUser + ); // Extract the actual interview argument and verify with a strict comparison approach const actualInterviewArgCall2 = mockPrepareSectionWidgets.mock.calls[1][1]; // Use lodash's isEqual as the equal will ignore undefined vs missing property differences expect(_isEqual(actualInterviewArgCall2, expectedInterviewToPrepareForCall2)).toBe(true); - expect(mockPrepareSectionWidgets).toHaveBeenCalledWith('section', expectedInterviewToPrepareForCall2, { 'response.section1.q2': true, 'validations.section1.q2': true }, { ...valuesByPathCall2 }, false, testUser); + expect(mockPrepareSectionWidgets).toHaveBeenCalledWith( + 'section', + expectedInterviewToPrepareForCall2, + { 'response.section1.q2': true, 'validations.section1.q2': true }, + { ...valuesByPathCall2 }, + false, + testUser + ); expect(fetchRetryMock).toHaveBeenCalledTimes(2); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/updateInterview', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ - id: interviewAttributes.id, - interviewId: interviewAttributes.uuid, - participant_id: interviewAttributes.participant_id, - valuesByPath: { ...valuesByPathCall1, sectionLoaded: 'section' }, - unsetPaths: [], - userAction: userActionCall1 + expect(fetchRetryMock).toHaveBeenCalledWith( + '/api/survey/updateInterview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + id: interviewAttributes.id, + interviewId: interviewAttributes.uuid, + participant_id: interviewAttributes.participant_id, + valuesByPath: { ...valuesByPathCall1, sectionLoaded: 'section' }, + unsetPaths: [], + userAction: userActionCall1 + }) }) - })); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/updateInterview', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ - id: interviewAttributes.id, - interviewId: interviewAttributes.uuid, - participant_id: interviewAttributes.participant_id, - // No sectionLoaded in the second call as it is identical to the first one - valuesByPath: { ...valuesByPathCall2 }, - unsetPaths: [], - userAction: userActionCall2 + ); + expect(fetchRetryMock).toHaveBeenCalledWith( + '/api/survey/updateInterview', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ + id: interviewAttributes.id, + interviewId: interviewAttributes.uuid, + participant_id: interviewAttributes.participant_id, + // No sectionLoaded in the second call as it is identical to the first one + valuesByPath: { ...valuesByPathCall2 }, + unsetPaths: [], + userAction: userActionCall2 + }) }) - })); + ); expect(mockDispatch).toHaveBeenCalledTimes(6); - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(2, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, @@ -824,12 +901,8 @@ describe('Update interview', () => { errors: {}, submitted: false }); - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: 'DECREMENT_LOADING_STATE' - }); - expect(mockDispatch).toHaveBeenNthCalledWith(4, { - type: 'INCREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: 'DECREMENT_LOADING_STATE' }); + expect(mockDispatch).toHaveBeenNthCalledWith(4, { type: 'INCREMENT_LOADING_STATE' }); expect(mockDispatch).toHaveBeenNthCalledWith(5, { type: 'UPDATE_INTERVIEW', interviewLoaded: true, @@ -837,17 +910,13 @@ describe('Update interview', () => { errors: {}, submitted: false }); - expect(mockDispatch).toHaveBeenNthCalledWith(6, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(6, { type: 'DECREMENT_LOADING_STATE' }); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsStateAfterCall1); expect(updateCallback).toHaveBeenCalledWith(expectedInterviewAsStateAfterCall2); }); - }); describe('startNavigate', () => { - let startUpdateInterviewSpy; let validateAndPrepareSectionSpy: jest.SpiedFunction; const startUpdateInterviewMock = jest.fn(); @@ -860,19 +929,16 @@ describe('startNavigate', () => { interviewLoaded: true, errors: undefined, navigationService: navigationServiceMock, - navigation: { - currentSection, - navigationHistory: [{ sectionShortname: 'previousSection' }] - } + navigation: { currentSection, navigationHistory: [{ sectionShortname: 'previousSection' }] } }, - auth: { - user: testUser - } + auth: { user: testUser } }); beforeAll(() => { startUpdateInterviewSpy = jest.spyOn(SurveyActions, 'startUpdateInterview').mockReturnValue(startUpdateInterviewMock); - validateAndPrepareSectionSpy = jest.spyOn(SurveyActions, 'validateAndPrepareSection').mockImplementation((_s, interview, _a, valuesByPath, _U, _user) => [ interview, valuesByPath ]); + validateAndPrepareSectionSpy = jest + .spyOn(SurveyActions, 'validateAndPrepareSection') + .mockImplementation((_s, interview, _a, valuesByPath, _U, _user) => [interview, valuesByPath]); jest.spyOn(bowser, 'getParser').mockReturnValue(bowser.getParser('test')); }); @@ -882,12 +948,8 @@ describe('startNavigate', () => { }); const validateIncrementLoadingStateCalls = (dispatch: jest.Mock) => { - expect(mockDispatch).toHaveBeenNthCalledWith(1, { - type: 'INCREMENT_LOADING_STATE' - }); - expect(mockDispatch).toHaveBeenNthCalledWith(4, { - type: 'DECREMENT_LOADING_STATE' - }); + expect(mockDispatch).toHaveBeenNthCalledWith(1, { type: 'INCREMENT_LOADING_STATE' }); + expect(mockDispatch).toHaveBeenNthCalledWith(4, { type: 'DECREMENT_LOADING_STATE' }); }; test('should initialize navigation if called without parameters and without prior navigation', async () => { @@ -904,7 +966,11 @@ describe('startNavigate', () => { // Verify call to navigation service expect(mockInitNavigationState).toHaveBeenCalledTimes(1); - expect(mockInitNavigationState).toHaveBeenCalledWith({ interview: interviewAttributes, requestedSection: undefined, currentSection: undefined }); + expect(mockInitNavigationState).toHaveBeenCalledWith({ + interview: interviewAttributes, + requestedSection: undefined, + currentSection: undefined + }); // Verify dispatch calls expect(mockDispatch).toHaveBeenCalledTimes(4); @@ -919,11 +985,7 @@ describe('startNavigate', () => { }); // Verify navigation update call - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: SurveyActionTypes.NAVIGATE, - targetSection - }); - + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: SurveyActionTypes.NAVIGATE, targetSection }); }); test('should navigation to next section if called without parameters, but with prior navigation', async () => { @@ -938,7 +1000,14 @@ describe('startNavigate', () => { // Verify validation function call expect(validateAndPrepareSectionSpy).toHaveBeenCalledTimes(1); - expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith(currentSection.sectionShortname, interviewAttributes, { _all: true }, { _all: true }, false, testUser); + expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith( + currentSection.sectionShortname, + interviewAttributes, + { _all: true }, + { _all: true }, + false, + testUser + ); // Verify call to navigation service expect(mockInitNavigationState).not.toHaveBeenCalled(); @@ -958,10 +1027,7 @@ describe('startNavigate', () => { }); // Verify navigation update call - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: SurveyActionTypes.NAVIGATE, - targetSection - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: SurveyActionTypes.NAVIGATE, targetSection }); }); test('should initialize navigation if called with requested section, no callback', async () => { @@ -981,7 +1047,11 @@ describe('startNavigate', () => { // Verify call to navigation service expect(mockNavigate).not.toHaveBeenCalled(); expect(mockInitNavigationState).toHaveBeenCalledTimes(1); - expect(mockInitNavigationState).toHaveBeenCalledWith({ interview: interviewAttributes, requestedSection: requestedSection.sectionShortname, currentSection }); + expect(mockInitNavigationState).toHaveBeenCalledWith({ + interview: interviewAttributes, + requestedSection: requestedSection.sectionShortname, + currentSection + }); // Verify dispatch calls expect(mockDispatch).toHaveBeenCalledTimes(4); @@ -996,13 +1066,9 @@ describe('startNavigate', () => { }); // Verify navigation update call - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: SurveyActionTypes.NAVIGATE, - targetSection - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: SurveyActionTypes.NAVIGATE, targetSection }); }); - test('should call callback at the end', async () => { // Prepare mock and test data const navCallback = jest.fn(); @@ -1016,7 +1082,14 @@ describe('startNavigate', () => { // Verify validation function call expect(validateAndPrepareSectionSpy).toHaveBeenCalledTimes(1); - expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith(currentSection.sectionShortname, interviewAttributes, { _all: true }, { _all: true }, false, testUser); + expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith( + currentSection.sectionShortname, + interviewAttributes, + { _all: true }, + { _all: true }, + false, + testUser + ); // Verify call to navigation service expect(mockInitNavigationState).not.toHaveBeenCalled(); @@ -1036,10 +1109,7 @@ describe('startNavigate', () => { }); // Verify navigation update call - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: SurveyActionTypes.NAVIGATE, - targetSection - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: SurveyActionTypes.NAVIGATE, targetSection }); // Verify call to callback expect(navCallback).toHaveBeenCalledTimes(1); @@ -1059,7 +1129,14 @@ describe('startNavigate', () => { // Verify validation function call expect(validateAndPrepareSectionSpy).toHaveBeenCalledTimes(1); - expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith(currentSection.sectionShortname, interviewAttributes, { _all: true }, { _all: true }, false, testUser); + expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith( + currentSection.sectionShortname, + interviewAttributes, + { _all: true }, + { _all: true }, + false, + testUser + ); // Verify call to navigation service, interview should have been updated with values expect(mockInitNavigationState).not.toHaveBeenCalled(); @@ -1080,10 +1157,7 @@ describe('startNavigate', () => { }); // Verify navigation update call - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: SurveyActionTypes.NAVIGATE, - targetSection - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: SurveyActionTypes.NAVIGATE, targetSection }); }); test('should update interview with combined values by path from call, side effect, validate and nav', async () => { @@ -1097,7 +1171,7 @@ describe('startNavigate', () => { const navValuesByPath = { 'response.section1.q1': 'foo', 'response.navField': 3 }; mockNavigate.mockReturnValueOnce({ targetSection, valuesByPath: navValuesByPath }); - validateAndPrepareSectionSpy.mockImplementationOnce((_s, interview, _a, valuesByPath, _U, _user) => [ interview, validationValuesByPath ]); + validateAndPrepareSectionSpy.mockImplementationOnce((_s, interview, _a, valuesByPath, _U, _user) => [interview, validationValuesByPath]); mockGetState.mockImplementationOnce(mockStateWithNav); // Do the actual test @@ -1107,7 +1181,14 @@ describe('startNavigate', () => { // Verify validation function call expect(validateAndPrepareSectionSpy).toHaveBeenCalledTimes(1); // Don't validate the interview in this call. It was tested in other tests and since it was modified in place later in the function, the value changes from the original call - expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith(currentSection.sectionShortname, expect.anything(), { _all: true }, { _all: true }, false, testUser); + expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith( + currentSection.sectionShortname, + expect.anything(), + { _all: true }, + { _all: true }, + false, + testUser + ); // Verify call to navigation service const expectedNavInterview = _cloneDeep(interviewAttributes); @@ -1132,17 +1213,17 @@ describe('startNavigate', () => { }); // Verify navigation update call - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: SurveyActionTypes.NAVIGATE, - targetSection - }); + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: SurveyActionTypes.NAVIGATE, targetSection }); }); test('should stay on current page if current widgets are invalid', async () => { // Prepare mock and test data const targetSection = { sectionShortname: 'nextSection' }; mockNavigate.mockReturnValueOnce({ targetSection }); - validateAndPrepareSectionSpy.mockImplementationOnce((_s, interview, _a, valuesByPath, _U, _user) => [ { ...interview, allWidgetsValid: false }, valuesByPath ]); + validateAndPrepareSectionSpy.mockImplementationOnce((_s, interview, _a, valuesByPath, _U, _user) => [ + { ...interview, allWidgetsValid: false }, + valuesByPath + ]); mockGetState.mockImplementationOnce(mockStateWithNav); // Do the actual test @@ -1151,7 +1232,14 @@ describe('startNavigate', () => { // Verify validation function call expect(validateAndPrepareSectionSpy).toHaveBeenCalledTimes(1); - expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith(currentSection.sectionShortname, interviewAttributes, { _all: true }, { _all: true }, false, testUser); + expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith( + currentSection.sectionShortname, + interviewAttributes, + { _all: true }, + { _all: true }, + false, + testUser + ); // Verify navigation service is not called expect(mockInitNavigationState).not.toHaveBeenCalled(); @@ -1165,17 +1253,17 @@ describe('startNavigate', () => { // Verify interview update dispatch call expect(SurveyActions.startUpdateInterview).toHaveBeenCalledTimes(1); - expect(SurveyActions.startUpdateInterview).toHaveBeenCalledWith({ - sectionShortname: 'currentSection', - valuesByPath: { _all: true } - }); + expect(SurveyActions.startUpdateInterview).toHaveBeenCalledWith({ sectionShortname: 'currentSection', valuesByPath: { _all: true } }); }); test('should stay on current page and send user action, if current widgets are invalid', async () => { // Prepare mock and test data const targetSection = { sectionShortname: 'nextSection' }; mockNavigate.mockReturnValueOnce({ targetSection }); - validateAndPrepareSectionSpy.mockImplementationOnce((_s, interview, _a, valuesByPath, _U, _user) => [ { ...interview, allWidgetsValid: false }, valuesByPath ]); + validateAndPrepareSectionSpy.mockImplementationOnce((_s, interview, _a, valuesByPath, _U, _user) => [ + { ...interview, allWidgetsValid: false }, + valuesByPath + ]); mockGetState.mockImplementationOnce(mockStateWithNav); // User action that should be sent instead of the default sectionChange const userAction = { type: 'buttonClick' as const, buttonId: 'testButton' }; @@ -1186,7 +1274,14 @@ describe('startNavigate', () => { // Verify validation function call expect(validateAndPrepareSectionSpy).toHaveBeenCalledTimes(1); - expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith(currentSection.sectionShortname, interviewAttributes, { _all: true }, { _all: true }, false, testUser); + expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith( + currentSection.sectionShortname, + interviewAttributes, + { _all: true }, + { _all: true }, + false, + testUser + ); // Verify navigation service is not called expect(mockInitNavigationState).not.toHaveBeenCalled(); @@ -1233,7 +1328,7 @@ describe('startNavigate', () => { validatedInterview.widgets = nextWidgetStatuses; validatedInterview.groups = nextGroupStatuses; validateAndPrepareSectionSpy.mockImplementationOnce((_sectionShortname, _interview, _a, valuesByPath, _U, _user) => { - return [ validatedInterview, valuesByPath ]; + return [validatedInterview, valuesByPath]; }); mockGetState.mockImplementationOnce(mockStateWithNav); @@ -1243,7 +1338,14 @@ describe('startNavigate', () => { // Verify validation function call expect(validateAndPrepareSectionSpy).toHaveBeenCalledTimes(1); - expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith(currentSection.sectionShortname, interviewAttributes, { _all: true }, { _all: true }, false, testUser); + expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith( + currentSection.sectionShortname, + interviewAttributes, + { _all: true }, + { _all: true }, + false, + testUser + ); // Verify call to navigation service const expectedNavInterview = _cloneDeep(interviewAttributes); @@ -1260,15 +1362,16 @@ describe('startNavigate', () => { expect(SurveyActions.startUpdateInterview).toHaveBeenCalledTimes(1); expect(SurveyActions.startUpdateInterview).toHaveBeenCalledWith({ sectionShortname: targetSection.sectionShortname, - userAction: { type: 'sectionChange', targetSection, previousSection: currentSection, hiddenWidgets: [ 'invisibleWidgetPath', 'group.groupIdWithSomeInvisible.invisibleWidget' ] } + userAction: { + type: 'sectionChange', + targetSection, + previousSection: currentSection, + hiddenWidgets: ['invisibleWidgetPath', 'group.groupIdWithSomeInvisible.invisibleWidget'] + } }); // Verify navigation update call - expect(mockDispatch).toHaveBeenNthCalledWith(3, { - type: SurveyActionTypes.NAVIGATE, - targetSection - }); - + expect(mockDispatch).toHaveBeenNthCalledWith(3, { type: SurveyActionTypes.NAVIGATE, targetSection }); }); test('should properly handle exceptions', async () => { @@ -1276,7 +1379,9 @@ describe('startNavigate', () => { const targetSection = { sectionShortname: 'nextSection' }; mockNavigate.mockReturnValueOnce({ targetSection }); const error = new Error('Validation error'); - validateAndPrepareSectionSpy.mockImplementationOnce(() => { throw error; }); + validateAndPrepareSectionSpy.mockImplementationOnce(() => { + throw error; + }); mockGetState.mockImplementationOnce(mockStateWithNav); // Do the actual test @@ -1285,7 +1390,14 @@ describe('startNavigate', () => { // Verify validation function call expect(validateAndPrepareSectionSpy).toHaveBeenCalledTimes(1); - expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith(currentSection.sectionShortname, interviewAttributes, { _all: true }, { _all: true }, false, testUser); + expect(validateAndPrepareSectionSpy).toHaveBeenCalledWith( + currentSection.sectionShortname, + interviewAttributes, + { _all: true }, + { _all: true }, + false, + testUser + ); // Verify navigation service is not called expect(mockInitNavigationState).not.toHaveBeenCalled(); @@ -1295,7 +1407,6 @@ describe('startNavigate', () => { expect(handleClientError).toHaveBeenCalledTimes(1); expect(handleClientError).toHaveBeenCalledWith(error, { interviewId: interviewAttributes.id }); }); - }); describe('startAddGroupedObjects', () => { @@ -1372,7 +1483,6 @@ describe('startAddGroupedObjects', () => { expect(mockDispatch).toHaveBeenCalledTimes(1); expect(mockDispatch).toHaveBeenCalledWith(startUpdateInterviewMock); }); - }); describe('startRemoveGroupedObjects', () => { @@ -1402,7 +1512,10 @@ describe('startRemoveGroupedObjects', () => { expect(mockedRemoveGroupedObject).toHaveBeenCalledTimes(1); expect(mockedRemoveGroupedObject).toHaveBeenCalledWith(interviewAttributes, paths); expect(startUpdateInterviewSpy).toHaveBeenCalledTimes(1); - expect(startUpdateInterviewSpy).toHaveBeenCalledWith({ valuesByPath: defaultRemoveGroupResponse[0], unsetPaths: defaultRemoveGroupResponse[1] }, undefined); + expect(startUpdateInterviewSpy).toHaveBeenCalledWith( + { valuesByPath: defaultRemoveGroupResponse[0], unsetPaths: defaultRemoveGroupResponse[1] }, + undefined + ); expect(mockDispatch).toHaveBeenCalledTimes(1); expect(mockDispatch).toHaveBeenCalledWith(startUpdateInterviewMock); }); @@ -1437,18 +1550,19 @@ describe('startRemoveGroupedObjects', () => { expect(mockedRemoveGroupedObject).toHaveBeenCalledTimes(1); expect(mockedRemoveGroupedObject).toHaveBeenCalledWith(interviewAttributes, paths); expect(startUpdateInterviewSpy).toHaveBeenCalledTimes(1); - expect(startUpdateInterviewSpy).toHaveBeenCalledWith({ valuesByPath: defaultRemoveGroupResponse[0], unsetPaths: defaultRemoveGroupResponse[1] }, callback); + expect(startUpdateInterviewSpy).toHaveBeenCalledWith( + { valuesByPath: defaultRemoveGroupResponse[0], unsetPaths: defaultRemoveGroupResponse[1] }, + callback + ); expect(mockDispatch).toHaveBeenCalledTimes(1); expect(mockDispatch).toHaveBeenCalledWith(startUpdateInterviewMock); }); - }); describe('startSetInterview', () => { - // Prepare minimal questionnaire section config const applicationSections = { - sectionLast: { + sectionLast: { type: 'section' as const, widgets: [], previousSection: 'sectionFirst', @@ -1456,7 +1570,8 @@ describe('startSetInterview', () => { enableConditional: true, completionConditional: true, navMenu: { type: 'inNav' as const, menuName: 'sectionLast' } - }, sectionFirst: { + }, + sectionFirst: { type: 'section' as const, widgets: [], previousSection: null, @@ -1483,7 +1598,6 @@ describe('startSetInterview', () => { }); test('No prefilled response', async () => { - // Prepare mock and test data const returnedInterview = _cloneDeep(interviewAttributes); jsonFetchResolve.mockResolvedValue({ status: 'success', interview: returnedInterview }); @@ -1494,26 +1608,17 @@ describe('startSetInterview', () => { // Verifications expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/activeInterview', expect.objectContaining({ - credentials: 'include' - })); + expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/activeInterview', expect.objectContaining({ credentials: 'include' })); expect(mockDispatch).toHaveBeenCalledTimes(2); - expect(mockDispatch).toHaveBeenCalledWith({ - type: SurveyActionTypes.SET_INTERVIEW, - interview: returnedInterview, - interviewLoaded: true - }); + expect(mockDispatch).toHaveBeenCalledWith({ type: SurveyActionTypes.SET_INTERVIEW, interview: returnedInterview, interviewLoaded: true }); expect(mockDispatch).toHaveBeenCalledWith(startNavigateMock); expect(SurveyActions.startNavigate).toHaveBeenCalledWith({ requestedSection: undefined, - valuesByPath: { - 'response._browser': expect.anything() - } + valuesByPath: { 'response._browser': expect.anything() } }); }); test('With prefilled response and section', async () => { - // Prepare mock and test data const prefilledResponse = { fieldA: 'valueA', fieldB: 'valueB' }; const returnedInterview = _cloneDeep(interviewAttributes); @@ -1525,25 +1630,14 @@ describe('startSetInterview', () => { // Verifications expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/activeInterview', expect.objectContaining({ - credentials: 'include' - })); + expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/activeInterview', expect.objectContaining({ credentials: 'include' })); expect(mockDispatch).toHaveBeenCalledTimes(2); - expect(mockDispatch).toHaveBeenCalledWith({ - type: SurveyActionTypes.SET_INTERVIEW, - interview: returnedInterview, - interviewLoaded: true - }); + expect(mockDispatch).toHaveBeenCalledWith({ type: SurveyActionTypes.SET_INTERVIEW, interview: returnedInterview, interviewLoaded: true }); expect(mockDispatch).toHaveBeenCalledWith(startNavigateMock); expect(SurveyActions.startNavigate).toHaveBeenCalledWith({ requestedSection: { sectionShortname: 'sectionFirst' }, - valuesByPath: { - 'response._browser': expect.anything(), - 'response.fieldA': 'valueA', - 'response.fieldB': 'valueB' - } + valuesByPath: { 'response._browser': expect.anything(), 'response.fieldA': 'valueA', 'response.fieldB': 'valueB' } }); - }); test('No interview returned, should give an error message', async () => { @@ -1558,16 +1652,13 @@ describe('startSetInterview', () => { // Verifications expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/activeInterview', expect.objectContaining({ - credentials: 'include' - })); + expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/activeInterview', expect.objectContaining({ credentials: 'include' })); expect(mockDispatch).not.toHaveBeenCalled(); expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Get active interview: no interview was returned, it\'s not supposed to happen'); expect(SurveyActions.startNavigate).not.toHaveBeenCalled(); }); test('with interview UUID', async () => { - const uuid = uuidV4(); // Prepare mock and test data @@ -1580,27 +1671,17 @@ describe('startSetInterview', () => { // Verifications expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith(`/api/survey/activeInterview/${uuid}`, expect.objectContaining({ - credentials: 'include' - })); + expect(fetchRetryMock).toHaveBeenCalledWith(`/api/survey/activeInterview/${uuid}`, expect.objectContaining({ credentials: 'include' })); expect(mockDispatch).toHaveBeenCalledTimes(2); - expect(mockDispatch).toHaveBeenCalledWith({ - type: SurveyActionTypes.SET_INTERVIEW, - interview: returnedInterview, - interviewLoaded: true - }); + expect(mockDispatch).toHaveBeenCalledWith({ type: SurveyActionTypes.SET_INTERVIEW, interview: returnedInterview, interviewLoaded: true }); expect(mockDispatch).toHaveBeenCalledWith(startNavigateMock); expect(SurveyActions.startNavigate).toHaveBeenCalledWith({ requestedSection: { sectionShortname: 'sectionFirst' }, - valuesByPath: { - 'response._browser': expect.anything() - } + valuesByPath: { 'response._browser': expect.anything() } }); - }); test('Invalid response from server', async () => { - // Prepare mock and test data fetchStatus.push(401); jsonFetchResolve.mockResolvedValue({ status: 'unauthorized' }); @@ -1611,16 +1692,12 @@ describe('startSetInterview', () => { // Verifications expect(fetchRetryMock).toHaveBeenCalledTimes(1); - expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/activeInterview', expect.objectContaining({ - credentials: 'include' - })); + expect(fetchRetryMock).toHaveBeenCalledWith('/api/survey/activeInterview', expect.objectContaining({ credentials: 'include' })); expect(mockDispatch).not.toHaveBeenCalled(); expect(SurveyActions.startNavigate).not.toHaveBeenCalled(); - }); test('Exception while fetching', async () => { - // Prepare mock and test data const error = new Error('error fetching'); fetchRetryMock.mockRejectedValueOnce(error); @@ -1632,7 +1709,5 @@ describe('startSetInterview', () => { // Verifications expect(handleClientError).toHaveBeenCalledTimes(1); expect(handleClientError).toHaveBeenCalledWith(error, { history: undefined, interviewId: undefined }); - }); - }); diff --git a/packages/evolution-frontend/src/actions/utils/__tests__/Conditional.test.ts b/packages/evolution-frontend/src/actions/utils/__tests__/Conditional.test.ts index 03cf6b312..f08de9f10 100644 --- a/packages/evolution-frontend/src/actions/utils/__tests__/Conditional.test.ts +++ b/packages/evolution-frontend/src/actions/utils/__tests__/Conditional.test.ts @@ -14,7 +14,7 @@ import { checkConditional, checkChoicesConditional } from '../Conditional'; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, @@ -23,40 +23,18 @@ const userAttributes = { }; type CustomSurvey = { - section1?: { - q1?: string; - q2?: number; - q4?: string; - }; - section2?: { - q1?: string; - }; + section1?: { q1?: string; q2?: number; q4?: string }; + section2?: { q1?: string }; choicePath?: undefined | null | string | string[]; -} +}; const interviewAttributes: UserInterviewAttributes = { id: 1, uuid: 'arbitrary uuid', participant_id: 1, is_completed: false, - response: { - section1: { - q1: 'abc', - q2: 3 - }, - section2: { - q1: 'test' - } - } as any, - validations: { - section1: { - q1: true, - q2: false - }, - section2: { - q1: true - } - } as any, + response: { section1: { q1: 'abc', q2: 3 }, section2: { q1: 'test' } } as any, + validations: { section1: { q1: true, q2: false }, section2: { q1: true } } as any, is_valid: true }; @@ -70,18 +48,40 @@ each([ ['function which returns a single boolean array: false', jest.fn().mockReturnValue([false]), [false, undefined, undefined]], ['function which returns a full array with null: true', jest.fn().mockReturnValue([true, null, null]), [true, null, null]], ['function which returns a full array with one values: true', jest.fn().mockReturnValue([true, 'string', null]), [true, 'string', null]], - ['function which returns a full array with values: true', jest.fn().mockReturnValue([true, 'string', 'customString']), [true, 'string', 'customString']], - ['function which returns a full array with custom value: true', jest.fn().mockReturnValue([true, null, 'customString']), [true, null, 'customString']], + [ + 'function which returns a full array with values: true', + jest.fn().mockReturnValue([true, 'string', 'customString']), + [true, 'string', 'customString'] + ], + [ + 'function which returns a full array with custom value: true', + jest.fn().mockReturnValue([true, null, 'customString']), + [true, null, 'customString'] + ], ['function which returns a full array with null: false', jest.fn().mockReturnValue([false, null, null]), [false, null, null]], ['function which returns a full array with one values: false', jest.fn().mockReturnValue([false, 'string', null]), [false, 'string', null]], - ['function which returns a full array with values: false', jest.fn().mockReturnValue([false, 'string', 'customString']), [false, 'string', 'customString']], - ['function which returns a full array with custom value: false', jest.fn().mockReturnValue([false, null, 'customString']), [false, null, 'customString']], + [ + 'function which returns a full array with values: false', + jest.fn().mockReturnValue([false, 'string', 'customString']), + [false, 'string', 'customString'] + ], + [ + 'function which returns a full array with custom value: false', + jest.fn().mockReturnValue([false, null, 'customString']), + [false, null, 'customString'] + ], ['function which returns 2-elements array with null: true', jest.fn().mockReturnValue([true, null]), [true, null, null]], ['function which returns 2-elements array with value: true', jest.fn().mockReturnValue([true, 'string']), [true, 'string', null]], ['function which returns 2-elements array with null: false', jest.fn().mockReturnValue([false, null]), [false, null, null]], ['function which returns 2-elements array with value: false', jest.fn().mockReturnValue([false, 'string']), [false, 'string', null]], ['legacy return value', jest.fn().mockReturnValue(null), [false, undefined, undefined]], - ['function with error', jest.fn().mockImplementation(() => { throw 'error'; }), [false, undefined, undefined]], + [ + 'function with error', + jest.fn().mockImplementation(() => { + throw 'error'; + }), + [false, undefined, undefined] + ] ]).test('Test check conditional %s', (_title, conditional, expectedResult) => { expect(checkConditional(conditional, interviewAttributes, 'path', userAttributes)).toEqual(expectedResult); if (typeof conditional === 'function') { @@ -91,18 +91,9 @@ each([ }); describe('Test check choice conditional', () => { - const basicChoices = [ - { value: 'a' }, - { value: 'b' } - ]; - const withGrouped = [ - { value: 'c' }, - { groupShortname: 'gsn', groupLabel: 'label', choices: basicChoices } - ]; - const withTrueFalseValues = [ - { value: false }, - { value: true } - ]; + const basicChoices = [{ value: 'a' }, { value: 'b' }]; + const withGrouped = [{ value: 'c' }, { groupShortname: 'gsn', groupLabel: 'label', choices: basicChoices }]; + const withTrueFalseValues = [{ value: false }, { value: true }]; const variousConditionalAllTrue = [ { value: 'a', conditional: true }, { value: 'b', conditional: jest.fn().mockReturnValue(true) }, @@ -112,10 +103,14 @@ describe('Test check choice conditional', () => { const variousConditional = [ { value: 'a', conditional: true }, { value: 'b', conditional: jest.fn().mockReturnValue(false) }, - { groupShortname: 'gsn', groupLabel: 'label', choices: [ - { value: 'c', conditional: jest.fn().mockReturnValue([false]) }, - { value: 'd', conditional: jest.fn().mockReturnValue([false, 'a']) } - ] }, + { + groupShortname: 'gsn', + groupLabel: 'label', + choices: [ + { value: 'c', conditional: jest.fn().mockReturnValue([false]) }, + { value: 'd', conditional: jest.fn().mockReturnValue([false, 'a']) } + ] + }, { value: 'e', condition: true }, { value: 'f', conditional: [false, 'a'] } ]; @@ -133,18 +128,48 @@ describe('Test check choice conditional', () => { ['Parsed choices with undefined conditional, single response', jest.fn().mockReturnValue(basicChoices), 'a', [true, 'a']], ['Parsed with some grouped choices, undefined conditional, single response', jest.fn().mockReturnValue(basicChoices), 'a', [true, 'a']], ['Parsed choices with undefined conditional, no response', jest.fn().mockReturnValue(basicChoices), undefined, [true, undefined]], - ['Parsed choices with various conditional value, all true, single response', jest.fn().mockReturnValue(variousConditionalAllTrue), 'a', [true, 'a']], - ['Parsed choices with various conditional values, some false, single response stays', jest.fn().mockReturnValue(variousConditional), 'a', [true, 'a']], - ['Parsed choices with various conditional values, some false, single response removed', jest.fn().mockReturnValue(variousConditional), 'b', [false, undefined]], - ['Parsed choices with various conditional values, some false, single response changed', jest.fn().mockReturnValue(variousConditional), 'd', [false, 'a']], + [ + 'Parsed choices with various conditional value, all true, single response', + jest.fn().mockReturnValue(variousConditionalAllTrue), + 'a', + [true, 'a'] + ], + [ + 'Parsed choices with various conditional values, some false, single response stays', + jest.fn().mockReturnValue(variousConditional), + 'a', + [true, 'a'] + ], + [ + 'Parsed choices with various conditional values, some false, single response removed', + jest.fn().mockReturnValue(variousConditional), + 'b', + [false, undefined] + ], + [ + 'Parsed choices with various conditional values, some false, single response changed', + jest.fn().mockReturnValue(variousConditional), + 'd', + [false, 'a'] + ], ['Choices with undefined conditional, multi-response', basicChoices, ['a', 'b'], [true, ['a', 'b']]], ['Some grouped choices, undefined conditional, multi-response', withGrouped, ['a', 'c'], [true, ['a', 'c']]], ['Choices with various conditional value, all true, multi-response', variousConditionalAllTrue, ['a', 'b', 'c'], [true, ['a', 'b', 'c']]], ['Choices with various conditional values, some false, multi-response, all stay', variousConditional, ['a', 'e'], [true, ['a', 'e']]], ['Choices with various conditional values, some false, multi-response, all gone', variousConditional, ['b', 'c'], [false, []]], ['Choices with various conditional values, some false, multi-response, some gone', variousConditional, ['a', 'c'], [false, ['a']]], - ['Choices with various conditional values, some false, multi-response, some changes', variousConditional, ['b', 'd', 'e'], [false, ['a', 'e']]], - ['Choices with various conditional values, some false, multi-response, some changes with duplicates', variousConditional, ['c', 'd', 'f'], [false, ['a']]], + [ + 'Choices with various conditional values, some false, multi-response, some changes', + variousConditional, + ['b', 'd', 'e'], + [false, ['a', 'e']] + ], + [ + 'Choices with various conditional values, some false, multi-response, some changes with duplicates', + variousConditional, + ['c', 'd', 'f'], + [false, ['a']] + ] ]).test('%s', (_title, choices, currentValue: undefined | null | string | string[], expectedResult) => { const interview = _cloneDeep(interviewAttributes); (interview.response as any).choicePath = currentValue; diff --git a/packages/evolution-frontend/src/actions/utils/__tests__/Validation.test.ts b/packages/evolution-frontend/src/actions/utils/__tests__/Validation.test.ts index 43e0d001d..07ef56106 100644 --- a/packages/evolution-frontend/src/actions/utils/__tests__/Validation.test.ts +++ b/packages/evolution-frontend/src/actions/utils/__tests__/Validation.test.ts @@ -10,39 +10,15 @@ import each from 'jest-each'; import { checkValidations } from '../Validation'; -type CustomSurvey = { - section1?: { - q1?: string; - q2?: number; - }; - section2?: { - q1?: string; - } -} +type CustomSurvey = { section1?: { q1?: string; q2?: number }; section2?: { q1?: string } }; const interviewAttributes: UserInterviewAttributes = { id: 1, uuid: 'arbitrary uuid', participant_id: 1, is_completed: false, - response: { - section1: { - q1: 'abc', - q2: 3 - }, - section2: { - q1: 'test' - } - } as any, - validations: { - section1: { - q1: true, - q2: false - }, - section2: { - q1: true - } - } as any, + response: { section1: { q1: 'abc', q2: 3 }, section2: { q1: 'test' } } as any, + validations: { section1: { q1: true, q2: false }, section2: { q1: true } } as any, is_valid: true }; @@ -50,10 +26,37 @@ each([ ['undefined validation', undefined, [true, undefined]], ['one group: invalid', jest.fn().mockReturnValue([{ validation: true, errorMessage: 'error' }]), [false, 'error']], ['one group: valid', jest.fn().mockReturnValue([{ validation: false, errorMessage: 'error' }]), [true, undefined]], - ['multiple groups: all valid', jest.fn().mockReturnValue([{ validation: false, errorMessage: 'error' }, { validation: false, errorMessage: 'error2' }]), [true, undefined]], - ['multiple groups: second is invalid', jest.fn().mockReturnValue([{ validation: false, errorMessage: 'error' }, { validation: true, errorMessage: 'error2' }]), [false, 'error2']], - ['multiple groups: all invalid', jest.fn().mockReturnValue([{ validation: true, errorMessage: 'error' }, { validation: true, errorMessage: 'error2' }]), [false, 'error']], - ['function with error, should be valid', jest.fn().mockImplementation(() => { throw 'error'; }), [true, undefined]], + [ + 'multiple groups: all valid', + jest.fn().mockReturnValue([ + { validation: false, errorMessage: 'error' }, + { validation: false, errorMessage: 'error2' } + ]), + [true, undefined] + ], + [ + 'multiple groups: second is invalid', + jest.fn().mockReturnValue([ + { validation: false, errorMessage: 'error' }, + { validation: true, errorMessage: 'error2' } + ]), + [false, 'error2'] + ], + [ + 'multiple groups: all invalid', + jest.fn().mockReturnValue([ + { validation: true, errorMessage: 'error' }, + { validation: true, errorMessage: 'error2' } + ]), + [false, 'error'] + ], + [ + 'function with error, should be valid', + jest.fn().mockImplementation(() => { + throw 'error'; + }), + [true, undefined] + ] ]).test('Test check validation %s', (_title, validations, expectedResult) => { expect(checkValidations(validations, 'dummy value', 'custom', interviewAttributes, 'path', 'customPath')).toEqual(expectedResult); if (typeof validations === 'function') { diff --git a/packages/evolution-frontend/src/actions/utils/__tests__/WidgetOperation.test.ts b/packages/evolution-frontend/src/actions/utils/__tests__/WidgetOperation.test.ts index 532b26a3b..715b1fc26 100644 --- a/packages/evolution-frontend/src/actions/utils/__tests__/WidgetOperation.test.ts +++ b/packages/evolution-frontend/src/actions/utils/__tests__/WidgetOperation.test.ts @@ -16,7 +16,7 @@ import { checkValidations } from '../Validation'; const testUser = { id: 1, username: 'test', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: jest.fn(), is_admin: false, @@ -30,9 +30,7 @@ jest.mock('../Conditional', () => ({ })); const mockedCheckConditional = checkConditional as jest.MockedFunction; const mockedCheckChoicesConditional = checkChoicesConditional as jest.MockedFunction; -jest.mock('../Validation', () => ({ - checkValidations: jest.fn().mockReturnValue([true, undefined]) -})); +jest.mock('../Validation', () => ({ checkValidations: jest.fn().mockReturnValue([true, undefined]) })); const mockedCheckValidations = checkValidations as jest.MockedFunction; // Prepare sections and widget configurations @@ -41,38 +39,15 @@ const choiceSection = 'choiceSection'; const groupSection = 'groupSection'; const group1Name = 'group1Name'; const sections = { - [mainSection]: { - widgets: ['widget1', 'widget2'] - }, - [groupSection]: { - widgets: [group1Name] - }, - [choiceSection]: { - widgets: ['widget4'] - }, + [mainSection]: { widgets: ['widget1', 'widget2'] }, + [groupSection]: { widgets: [group1Name] }, + [choiceSection]: { widgets: ['widget4'] } }; const widgets = { - widget1: { - type: 'question', - inputType: 'string', - label: { en: 'q1 label' }, - path: 'section1.q1', - }, - widget2: { - type: 'question', - inputType: 'string', - path: 'section1.q2', - label: { en: 'q2 label' }, - validations: jest.fn() - }, - widget3: { - type: 'question', - inputType: 'string', - path: 'gq1', - label: { en: 'q3 label' }, - defaultValue: jest.fn().mockReturnValue('default') - }, + widget1: { type: 'question', inputType: 'string', label: { en: 'q1 label' }, path: 'section1.q1' }, + widget2: { type: 'question', inputType: 'string', path: 'section1.q2', label: { en: 'q2 label' }, validations: jest.fn() }, + widget3: { type: 'question', inputType: 'string', path: 'gq1', label: { en: 'q3 label' }, defaultValue: jest.fn().mockReturnValue('default') }, widget4: { type: 'question', inputType: 'select', @@ -84,11 +59,7 @@ const widgets = { { value: 'b', label: {}, conditional: () => false } ] }, - [group1Name]: { - type: 'group', - path: 'groupResponse', - widgets: ['widget3'] - } + [group1Name]: { type: 'group', path: 'groupResponse', widgets: ['widget3'] } }; const mockedDefaultValue = widgets.widget3.defaultValue as jest.MockedFunction; @@ -102,35 +73,11 @@ const interviewAttributes: UserInterviewAttributes = { participant_id: 1, is_completed: false, response: { - section1: { - q1: 'abc', - q2: 3 - }, - groupResponse: { - [group1Id]: { - _uuid: group1Id, - gq1: 'test' - }, - [group2Id]: { - _uuid: group2Id - } - } + section1: { q1: 'abc', q2: 3 }, + groupResponse: { [group1Id]: { _uuid: group1Id, gq1: 'test' }, [group2Id]: { _uuid: group2Id } } } as any, - validations: { - section1: { - q1: true, - q2: true - }, - groupResponse: { - [group1Id]: { - gq1: true - }, - [group2Id]: { - gq1: true - } - } - } as any, - is_valid: true, + validations: { section1: { q1: true, q2: true }, groupResponse: { [group1Id]: { gq1: true }, [group2Id]: { gq1: true } } } as any, + is_valid: true }; const runtimeInterviewAttributes: UserRuntimeInterviewAttributes = { ...interviewAttributes, @@ -181,7 +128,12 @@ test('Test widget preparation, without prior status', () => { expectedInterview.response.section1.q1 = newValue; // Test, 2 widgets should be valid and visible - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(mainSection, testInterviewAttributes, { 'response.section1.q1': true }, _cloneDeep(valuesByPath)); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + mainSection, + testInterviewAttributes, + { 'response.section1.q1': true }, + _cloneDeep(valuesByPath) + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); @@ -191,7 +143,14 @@ test('Test widget preparation, without prior status', () => { // Check calls of the validation function, should have been called only for the widget 2, as widget 1 has previous status and is unchanged expect(mockedCheckValidations).toHaveBeenCalledTimes(2); expect(mockedCheckValidations).toHaveBeenCalledWith(undefined, newValue, undefined, testInterviewAttributes, 'section1.q1', undefined); - expect(mockedCheckValidations).toHaveBeenCalledWith(widgets.widget2.validations, runtimeInterviewAttributes.response.section1.q2, undefined, testInterviewAttributes, 'section1.q2', undefined); + expect(mockedCheckValidations).toHaveBeenCalledWith( + widgets.widget2.validations, + runtimeInterviewAttributes.response.section1.q2, + undefined, + testInterviewAttributes, + 'section1.q2', + undefined + ); // Check calls to the conditional function, called twice, once for each widget expect(mockedCheckConditional).toHaveBeenCalledTimes(2); @@ -200,11 +159,7 @@ test('Test widget preparation, without prior status', () => { expect(mockedCheckChoicesConditional).not.toHaveBeenCalled(); // Check widget statuses for the 2 widgets - expect(updatedInterview.widgets.widget1).toEqual({ - ...commonDefaultWidgetStatus, - path: 'section1.q1', - value: newValue - }); + expect(updatedInterview.widgets.widget1).toEqual({ ...commonDefaultWidgetStatus, path: 'section1.q1', value: newValue }); expect(updatedInterview.widgets.widget2).toEqual({ ...commonDefaultWidgetStatus, path: 'section1.q2', @@ -218,19 +173,10 @@ test('Test widget preparation, without prior status', () => { }); describe('Test with previous status', () => { - // Prepare previous widget statuses const previousWidgetStatuses = { - widget1: { - ...commonDefaultWidgetStatus, - path: 'section1.q1', - value: runtimeInterviewAttributes.response.section1.q1 - }, - widget2: { - ...commonDefaultWidgetStatus, - path: 'section1.q2', - value: runtimeInterviewAttributes.response.section1.q2 - } + widget1: { ...commonDefaultWidgetStatus, path: 'section1.q1', value: runtimeInterviewAttributes.response.section1.q1 }, + widget2: { ...commonDefaultWidgetStatus, path: 'section1.q2', value: runtimeInterviewAttributes.response.section1.q2 } }; test('Only affected widgets should be checked, no change in validity or visibility', () => { @@ -247,7 +193,12 @@ describe('Test with previous status', () => { interviewCopy.response.section1.q2 = newValue; // Test, the 2 widgets should still be valid and visible - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(mainSection, testInterviewAttributes, { 'response.section1.q2': true }, _cloneDeep(valuesByPath)); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + mainSection, + testInterviewAttributes, + { 'response.section1.q2': true }, + _cloneDeep(valuesByPath) + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(interviewCopy)); @@ -256,7 +207,14 @@ describe('Test with previous status', () => { // Check calls of the validation function, should have been called only for the widget 2, as widget 1 has previous status and is unchanged expect(mockedCheckValidations).toHaveBeenCalledTimes(1); - expect(mockedCheckValidations).toHaveBeenCalledWith(widgets.widget2.validations, newValue, undefined, testInterviewAttributes, 'section1.q2', undefined); + expect(mockedCheckValidations).toHaveBeenCalledWith( + widgets.widget2.validations, + newValue, + undefined, + testInterviewAttributes, + 'section1.q2', + undefined + ); // Check calls to the conditional function, called twice, once for each widget expect(mockedCheckConditional).toHaveBeenCalledTimes(2); @@ -266,10 +224,7 @@ describe('Test with previous status', () => { // Widget statuses should match previous ones (no change) expect(updatedInterview.widgets).toEqual({ widget1: previousWidgetStatuses.widget1, - widget2: { - ...previousWidgetStatuses.widget2, - value: newValue - } + widget2: { ...previousWidgetStatuses.widget2, value: newValue } }); // Check the visible widgets and the allWidgetsValid flag @@ -296,7 +251,12 @@ describe('Test with previous status', () => { mockedCheckValidations.mockReturnValueOnce([false, errorMessage]); // Test, both widgets should be visible, only widget 1 is valid - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(mainSection, testInterviewAttributes, { 'response.section1.q2': true }, _cloneDeep(valuesByPath)); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + mainSection, + testInterviewAttributes, + { 'response.section1.q2': true }, + _cloneDeep(valuesByPath) + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); @@ -305,7 +265,14 @@ describe('Test with previous status', () => { // Check calls of the validation function, should have been called only for the widget 2, as widget 1 has previous status and is unchanged expect(mockedCheckValidations).toHaveBeenCalledTimes(1); - expect(mockedCheckValidations).toHaveBeenCalledWith(widgets.widget2.validations, newValue, undefined, testInterviewAttributes, 'section1.q2', undefined); + expect(mockedCheckValidations).toHaveBeenCalledWith( + widgets.widget2.validations, + newValue, + undefined, + testInterviewAttributes, + 'section1.q2', + undefined + ); // Check calls to the conditional function, called twice, once for each widget expect(mockedCheckConditional).toHaveBeenCalledTimes(2); @@ -315,24 +282,18 @@ describe('Test with previous status', () => { // Widget statuses should match previous ones (no change) expect(updatedInterview.widgets).toEqual({ widget1: previousWidgetStatuses.widget1, - widget2: { - ...previousWidgetStatuses.widget2, - isValid: false, - errorMessage, - value: newValue - } + widget2: { ...previousWidgetStatuses.widget2, isValid: false, errorMessage, value: newValue } }); // Check the visible widgets and the allWidgetsValid flag expect(updatedInterview.visibleWidgets).toEqual(['section1.q1', 'section1.q2']); expect(updatedInterview.allWidgetsValid).toEqual(false); - }); test('Check all widgets if "_all" is affected', () => { // Prepare test data, no changes const testInterviewAttributes = _cloneDeep(runtimeInterviewAttributes); - const valuesByPath = { }; + const valuesByPath = {}; // Set statuses before update testInterviewAttributes.widgets = _cloneDeep(previousWidgetStatuses); @@ -345,7 +306,12 @@ describe('Test with previous status', () => { mockedCheckValidations.mockReturnValueOnce([false, errorMessage]); // Test, the 2 widgets should be visible, widget1 is invalid - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(mainSection, testInterviewAttributes, { '_all': true }, _cloneDeep(valuesByPath)); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + mainSection, + testInterviewAttributes, + { _all: true }, + _cloneDeep(valuesByPath) + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); @@ -354,8 +320,22 @@ describe('Test with previous status', () => { // Check calls of the validation function, should have been called for all widgets expect(mockedCheckValidations).toHaveBeenCalledTimes(2); - expect(mockedCheckValidations).toHaveBeenCalledWith(undefined, interviewAttributes.response.section1.q1, undefined, testInterviewAttributes, 'section1.q1', undefined); - expect(mockedCheckValidations).toHaveBeenCalledWith(widgets.widget2.validations, interviewAttributes.response.section1.q2, undefined, testInterviewAttributes, 'section1.q2', undefined); + expect(mockedCheckValidations).toHaveBeenCalledWith( + undefined, + interviewAttributes.response.section1.q1, + undefined, + testInterviewAttributes, + 'section1.q1', + undefined + ); + expect(mockedCheckValidations).toHaveBeenCalledWith( + widgets.widget2.validations, + interviewAttributes.response.section1.q2, + undefined, + testInterviewAttributes, + 'section1.q2', + undefined + ); // Check calls to the conditional function, called twice, once for each widget expect(mockedCheckConditional).toHaveBeenCalledTimes(2); @@ -364,22 +344,18 @@ describe('Test with previous status', () => { // Widget statuses should match previous ones (no change) expect(updatedInterview.widgets).toEqual({ - widget1: { + widget1: { ...previousWidgetStatuses.widget1, isValid: false, errorMessage, isCustomResponded: true // FIXME Really? What does it mean? There is no custom value, but the code sets it to true }, - widget2: { - ...previousWidgetStatuses.widget2, - isCustomResponded: true - } + widget2: { ...previousWidgetStatuses.widget2, isCustomResponded: true } }); // Check the visible widgets and the allWidgetsValid flag expect(updatedInterview.visibleWidgets).toEqual(['section1.q1', 'section1.q2']); expect(updatedInterview.allWidgetsValid).toEqual(false); - }); test('Should set widget status if previous path does not match current one', () => { @@ -391,7 +367,12 @@ describe('Test with previous status', () => { testInterviewAttributes.widgets = previousStatuses; // Test, the 2 widgets should still be valid and visible - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(mainSection, testInterviewAttributes, { 'response.section1.q2': true }, {}); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + mainSection, + testInterviewAttributes, + { 'response.section1.q2': true }, + {} + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(interviewAttributes)); @@ -400,8 +381,22 @@ describe('Test with previous status', () => { // Check calls of the validation function, should have been called for both widget: widget 2 because it is affected and widget 1 because it does not have a proper previous status. expect(mockedCheckValidations).toHaveBeenCalledTimes(2); - expect(mockedCheckValidations).toHaveBeenCalledWith(undefined, interviewAttributes.response.section1.q1, undefined, testInterviewAttributes, 'section1.q1', undefined); - expect(mockedCheckValidations).toHaveBeenCalledWith(widgets.widget2.validations, interviewAttributes.response.section1.q2, undefined, testInterviewAttributes, 'section1.q2', undefined); + expect(mockedCheckValidations).toHaveBeenCalledWith( + undefined, + interviewAttributes.response.section1.q1, + undefined, + testInterviewAttributes, + 'section1.q1', + undefined + ); + expect(mockedCheckValidations).toHaveBeenCalledWith( + widgets.widget2.validations, + interviewAttributes.response.section1.q2, + undefined, + testInterviewAttributes, + 'section1.q2', + undefined + ); // Check calls to the conditional function, called twice, once for each widget expect(mockedCheckConditional).toHaveBeenCalledTimes(2); @@ -409,21 +404,16 @@ describe('Test with previous status', () => { expect(mockedCheckConditional).toHaveBeenCalledWith(undefined, testInterviewAttributes, 'section1.q2', undefined); // Widget statuses should have been updated and match the one specified in the test, not the previous ones from the interview - expect(updatedInterview.widgets).toEqual({ - widget1: previousWidgetStatuses.widget1, - widget2: previousWidgetStatuses.widget2 - }); + expect(updatedInterview.widgets).toEqual({ widget1: previousWidgetStatuses.widget1, widget2: previousWidgetStatuses.widget2 }); // Check the visible widgets and the allWidgetsValid flag expect(updatedInterview.visibleWidgets).toEqual(['section1.q1', 'section1.q2']); expect(updatedInterview.allWidgetsValid).toEqual(true); }); - }); describe('Test with conditional', () => { test('Change in widget1, make widget2 invisible and empty', () => { - // Prepare test data const newValue = 2; const testInterviewAttributes = _cloneDeep(runtimeInterviewAttributes); @@ -432,16 +422,8 @@ describe('Test with conditional', () => { // Prepare previous widget statuses const previousWidgetStatuses = { - widget1: { - ...commonDefaultWidgetStatus, - path: 'section1.q1', - value: testInterviewAttributes.response.section1.q1 - }, - widget2: { - ...commonDefaultWidgetStatus, - path: 'section1.q2', - value: runtimeInterviewAttributes.response.section1.q2 - } + widget1: { ...commonDefaultWidgetStatus, path: 'section1.q1', value: testInterviewAttributes.response.section1.q1 }, + widget2: { ...commonDefaultWidgetStatus, path: 'section1.q2', value: runtimeInterviewAttributes.response.section1.q2 } }; testInterviewAttributes.widgets = _cloneDeep(previousWidgetStatuses); @@ -457,7 +439,12 @@ describe('Test with conditional', () => { mockedCheckConditional.mockReturnValueOnce([false, undefined, undefined]); // Test, widget 2 should be invisible and empty, widget 1 should be visible and valid - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(mainSection, testInterviewAttributes, { 'response.section1.q1': true }, _cloneDeep(valuesByPath)); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + mainSection, + testInterviewAttributes, + { 'response.section1.q1': true }, + _cloneDeep(valuesByPath) + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); @@ -467,7 +454,14 @@ describe('Test with conditional', () => { // Check calls of the validation function, should have been called for both widgets and their new values expect(mockedCheckValidations).toHaveBeenCalledTimes(2); expect(mockedCheckValidations).toHaveBeenCalledWith(undefined, newValue, undefined, testInterviewAttributes, 'section1.q1', undefined); - expect(mockedCheckValidations).toHaveBeenCalledWith(widgets.widget2.validations, undefined, undefined, testInterviewAttributes, 'section1.q2', undefined); + expect(mockedCheckValidations).toHaveBeenCalledWith( + widgets.widget2.validations, + undefined, + undefined, + testInterviewAttributes, + 'section1.q2', + undefined + ); // Check calls to the conditional function, called twice, once for each widget expect(mockedCheckConditional).toHaveBeenCalledTimes(2); @@ -477,23 +471,15 @@ describe('Test with conditional', () => { // Widget statuses should match previous ones (no change) expect(updatedInterview.widgets).toEqual({ widget1: previousWidgetStatuses.widget1, - widget2: { - ...previousWidgetStatuses.widget2, - isValid: true, - isVisible: false, - isEmpty: true, - value: undefined - } + widget2: { ...previousWidgetStatuses.widget2, isValid: true, isVisible: false, isEmpty: true, value: undefined } }); // Check the visible widgets and the allWidgetsValid flag expect(updatedInterview.visibleWidgets).toEqual(['section1.q1']); expect(updatedInterview.allWidgetsValid).toEqual(true); - }); test('Change in widget1, make widget2 invisible with default value', () => { - // Prepare test data const newValue = 2; const widget2DefaultValue = 'defaultValue'; @@ -503,16 +489,8 @@ describe('Test with conditional', () => { // Prepare previous widget statuses const previousWidgetStatuses = { - widget1: { - ...commonDefaultWidgetStatus, - path: 'section1.q1', - value: testInterviewAttributes.response.section1.q1 - }, - widget2: { - ...commonDefaultWidgetStatus, - path: 'section1.q2', - value: runtimeInterviewAttributes.response.section1.q2 - } + widget1: { ...commonDefaultWidgetStatus, path: 'section1.q1', value: testInterviewAttributes.response.section1.q1 }, + widget2: { ...commonDefaultWidgetStatus, path: 'section1.q2', value: runtimeInterviewAttributes.response.section1.q2 } }; testInterviewAttributes.widgets = _cloneDeep(previousWidgetStatuses); @@ -529,7 +507,12 @@ describe('Test with conditional', () => { mockedCheckConditional.mockReturnValueOnce([false, widget2DefaultValue, undefined]); // Test, widget 2 should be invisible and empty, widget 1 should be visible and valid - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(mainSection, testInterviewAttributes, { 'response.section1.q1': true }, _cloneDeep(valuesByPath)); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + mainSection, + testInterviewAttributes, + { 'response.section1.q1': true }, + _cloneDeep(valuesByPath) + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); @@ -539,7 +522,14 @@ describe('Test with conditional', () => { // Check calls of the validation function, should have been called for both widgets and their new values expect(mockedCheckValidations).toHaveBeenCalledTimes(2); expect(mockedCheckValidations).toHaveBeenCalledWith(undefined, newValue, undefined, testInterviewAttributes, 'section1.q1', undefined); - expect(mockedCheckValidations).toHaveBeenCalledWith(widgets.widget2.validations, widget2DefaultValue, undefined, testInterviewAttributes, 'section1.q2', undefined); + expect(mockedCheckValidations).toHaveBeenCalledWith( + widgets.widget2.validations, + widget2DefaultValue, + undefined, + testInterviewAttributes, + 'section1.q2', + undefined + ); // Check calls to the conditional function, called twice, once for each widget expect(mockedCheckConditional).toHaveBeenCalledTimes(2); @@ -549,30 +539,22 @@ describe('Test with conditional', () => { // Widget statuses should match previous ones (no change) expect(updatedInterview.widgets).toEqual({ widget1: previousWidgetStatuses.widget1, - widget2: { - ...previousWidgetStatuses.widget2, - isValid: true, - isVisible: false, - isEmpty: false, - value: widget2DefaultValue - } + widget2: { ...previousWidgetStatuses.widget2, isValid: true, isVisible: false, isEmpty: false, value: widget2DefaultValue } }); // Check the visible widgets and the allWidgetsValid flag expect(updatedInterview.visibleWidgets).toEqual(['section1.q1']); expect(updatedInterview.allWidgetsValid).toEqual(true); - }); }); describe('Test a group widget', () => { - test('Initial group widget preparation, no previous status', () => { const g2DefaultValue = 'default'; // Prepare test data, without widget status at first, make the value of gq1 in group 2 undefined, so it will set the default value const testInterviewAttributes = _cloneDeep(runtimeInterviewAttributes); testInterviewAttributes.response.groupResponse[group2Id].gq1 = undefined; - const valuesByPath = { }; + const valuesByPath = {}; // Prepare expected interview, with the new value for gq1 of group2 const expectedInterview = _cloneDeep(interviewAttributes) as any; @@ -582,7 +564,12 @@ describe('Test a group widget', () => { mockedDefaultValue.mockReturnValueOnce(g2DefaultValue); // Test, 2 widgets should be valid and visible - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(groupSection, testInterviewAttributes, { }, _cloneDeep(valuesByPath)); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + groupSection, + testInterviewAttributes, + {}, + _cloneDeep(valuesByPath) + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); @@ -591,8 +578,22 @@ describe('Test a group widget', () => { // Check calls of the validation function, should have been called for the widget of each group expect(mockedCheckValidations).toHaveBeenCalledTimes(2); - expect(mockedCheckValidations).toHaveBeenCalledWith(undefined, interviewAttributes.response.groupResponse[group1Id].gq1, undefined, testInterviewAttributes, `groupResponse.${group1Id}.gq1`, undefined); - expect(mockedCheckValidations).toHaveBeenCalledWith(undefined, g2DefaultValue, undefined, testInterviewAttributes, `groupResponse.${group2Id}.gq1`, undefined); + expect(mockedCheckValidations).toHaveBeenCalledWith( + undefined, + interviewAttributes.response.groupResponse[group1Id].gq1, + undefined, + testInterviewAttributes, + `groupResponse.${group1Id}.gq1`, + undefined + ); + expect(mockedCheckValidations).toHaveBeenCalledWith( + undefined, + g2DefaultValue, + undefined, + testInterviewAttributes, + `groupResponse.${group2Id}.gq1`, + undefined + ); // Check calls to the conditional function, called twice, once for each widget expect(mockedCheckConditional).toHaveBeenCalledTimes(2); @@ -621,7 +622,6 @@ describe('Test a group widget', () => { // Check the visible widgets and the allWidgetsValid flag expect(updatedInterview.visibleWidgets).toEqual([`groupResponse.${group1Id}.gq1`, `groupResponse.${group2Id}.gq1`]); expect(updatedInterview.allWidgetsValid).toEqual(true); - }); test('With previous status and change in group 1', () => { @@ -636,20 +636,10 @@ describe('Test a group widget', () => { // Prepare previous widget statuses const previousWidgetStatuses = { [group1Id]: { - widget3: { - ...commonDefaultWidgetStatus, - path: 'groupResponse.group1Id.gq1', - value: 'any previous value', - groupedObjectId: group1Id - }, + widget3: { ...commonDefaultWidgetStatus, path: 'groupResponse.group1Id.gq1', value: 'any previous value', groupedObjectId: group1Id } }, [group2Id]: { - widget3: { - ...commonDefaultWidgetStatus, - path: 'groupResponse.group2Id.gq1', - value: valueInGroup2, - groupedObjectId: group2Id - } + widget3: { ...commonDefaultWidgetStatus, path: 'groupResponse.group2Id.gq1', value: valueInGroup2, groupedObjectId: group2Id } } }; testInterviewAttributes.groups = { [group1Name]: _cloneDeep(previousWidgetStatuses) }; @@ -660,7 +650,12 @@ describe('Test a group widget', () => { expectedInterview.response.groupResponse[group1Id].gq1 = newValue; // Test, 2 widgets should be valid and visible - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(groupSection, testInterviewAttributes, { 'response.groupResponse.group1Id.gq1': true }, _cloneDeep(valuesByPath)); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + groupSection, + testInterviewAttributes, + { 'response.groupResponse.group1Id.gq1': true }, + _cloneDeep(valuesByPath) + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); @@ -669,7 +664,14 @@ describe('Test a group widget', () => { // Check calls of the validation function, should have been called only for the widget in group 1 expect(mockedCheckValidations).toHaveBeenCalledTimes(1); - expect(mockedCheckValidations).toHaveBeenCalledWith(undefined, newValue, undefined, testInterviewAttributes, `groupResponse.${group1Id}.gq1`, undefined); + expect(mockedCheckValidations).toHaveBeenCalledWith( + undefined, + newValue, + undefined, + testInterviewAttributes, + `groupResponse.${group1Id}.gq1`, + undefined + ); // Check calls to the conditional function, called twice, once for each widget expect(mockedCheckConditional).toHaveBeenCalledTimes(2); @@ -697,11 +699,9 @@ describe('Test a group widget', () => { expect(updatedInterview.visibleWidgets).toEqual([`groupResponse.${group1Id}.gq1`, `groupResponse.${group2Id}.gq1`]); expect(updatedInterview.allWidgetsValid).toEqual(true); }); - }); test('Test simple widget data with update key', () => { - // Prepare test data, without widget status at first const newValue = 'newValue'; const testInterviewAttributes = _cloneDeep(runtimeInterviewAttributes); @@ -713,7 +713,13 @@ test('Test simple widget data with update key', () => { expectedInterview.response.section1.q1 = newValue; // Test, 2 widgets should be valid and visible, with updateKey set - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(mainSection, testInterviewAttributes, { 'response.section1.q1': true }, _cloneDeep(valuesByPath), true); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + mainSection, + testInterviewAttributes, + { 'response.section1.q1': true }, + _cloneDeep(valuesByPath), + true + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); @@ -737,14 +743,12 @@ test('Test simple widget data with update key', () => { // Check the visible widgets and the allWidgetsValid flag expect(updatedInterview.visibleWidgets).toEqual(['section1.q1', 'section1.q2']); expect(updatedInterview.allWidgetsValid).toEqual(true); - }); describe('Test with choice conditional', () => { // These tests use the choiceSection, with widget4 with choice conditional test('No change in value if all choice conditionals are true', () => { - // Prepare test data, without widget status const newValue = 'a'; const testInterviewAttributes = _cloneDeep(runtimeInterviewAttributes); @@ -760,7 +764,13 @@ describe('Test with choice conditional', () => { mockedCheckChoicesConditional.mockReturnValueOnce([true, undefined]); // Test, the widget should be valid and visible, with value as set - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(choiceSection, testInterviewAttributes, { 'response.section1.q4': true }, _cloneDeep(valuesByPath), false); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + choiceSection, + testInterviewAttributes, + { 'response.section1.q4': true }, + _cloneDeep(valuesByPath), + false + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); @@ -772,21 +782,15 @@ describe('Test with choice conditional', () => { expect(mockedCheckChoicesConditional).toHaveBeenCalledWith(newValue, widgets.widget4.choices, testInterviewAttributes, 'section1.q4'); // Check widget status for widget 4 - expect(updatedInterview.widgets.widget4).toEqual({ - ...commonDefaultWidgetStatus, - path: 'section1.q4', - value: newValue - }); + expect(updatedInterview.widgets.widget4).toEqual({ ...commonDefaultWidgetStatus, path: 'section1.q4', value: newValue }); expect(Object.keys(updatedInterview.widgets)).toEqual(['widget4']); // Check the visible widgets and the allWidgetsValid flag expect(updatedInterview.visibleWidgets).toEqual(['section1.q4']); expect(updatedInterview.allWidgetsValid).toEqual(true); - }); test('Choice conditional changes, set to undefined, as side-effect of other change', () => { - // Prepare test data, changing a widget that is not widget4 const valueForW4 = 'a'; const newValue = 3; @@ -807,7 +811,13 @@ describe('Test with choice conditional', () => { mockedCheckChoicesConditional.mockReturnValueOnce([false, undefined]); // Test, widget4 should have been updated - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(choiceSection, testInterviewAttributes, { 'response.section1.q1': true }, _cloneDeep(valuesByPath), false); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + choiceSection, + testInterviewAttributes, + { 'response.section1.q1': true }, + _cloneDeep(valuesByPath), + false + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); @@ -832,11 +842,9 @@ describe('Test with choice conditional', () => { // Check the visible widgets and the allWidgetsValid flag expect(updatedInterview.visibleWidgets).toEqual(['section1.q4']); expect(updatedInterview.allWidgetsValid).toEqual(true); - }); test('Choice visibility changes, change to new values, with previous statuses', () => { - // Prepare test data, changing a widget that is not widget4 const initialValueForWidget4 = 'a'; const updatedValueForWidget4 = 'b'; @@ -848,13 +856,7 @@ describe('Test with choice conditional', () => { const valuesByPath = { 'response.section1.q1': newValue }; // Prepare previous widget statuses - const previousWidgetStatuses = { - widget4: { - ...commonDefaultWidgetStatus, - path: 'section1.q4', - value: initialValueForWidget4 - } - }; + const previousWidgetStatuses = { widget4: { ...commonDefaultWidgetStatus, path: 'section1.q4', value: initialValueForWidget4 } }; testInterviewAttributes.widgets = _cloneDeep(previousWidgetStatuses); // Prepare expected interview, with the new value for q4 @@ -868,16 +870,29 @@ describe('Test with choice conditional', () => { mockedCheckChoicesConditional.mockReturnValueOnce([false, updatedValueForWidget4]); // Test, widget4 should have been updated - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(choiceSection, testInterviewAttributes, { 'response.section1.q1': true }, _cloneDeep(valuesByPath), false); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + choiceSection, + testInterviewAttributes, + { 'response.section1.q1': true }, + _cloneDeep(valuesByPath), + false + ); // Interview data should correspond to expected expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); - expect(updatedValuesByPath).toEqual(Object.assign({ 'validations.section1.q4': true, 'response.section1.q4': updatedValueForWidget4 }, valuesByPath)); + expect(updatedValuesByPath).toEqual( + Object.assign({ 'validations.section1.q4': true, 'response.section1.q4': updatedValueForWidget4 }, valuesByPath) + ); expect(needUpdate).toEqual(true); // Check the mockecCheckChoicesConditional call expect(mockedCheckChoicesConditional).toHaveBeenCalledTimes(1); - expect(mockedCheckChoicesConditional).toHaveBeenCalledWith(initialValueForWidget4, widgets.widget4.choices, testInterviewAttributes, 'section1.q4'); + expect(mockedCheckChoicesConditional).toHaveBeenCalledWith( + initialValueForWidget4, + widgets.widget4.choices, + testInterviewAttributes, + 'section1.q4' + ); // Check widget statuses for widget 4, should have an update key and updated value expect(updatedInterview.widgets.widget4).toEqual({ @@ -891,23 +906,15 @@ describe('Test with choice conditional', () => { // Check the visible widgets and the allWidgetsValid flag expect(updatedInterview.visibleWidgets).toEqual(['section1.q4']); expect(updatedInterview.allWidgetsValid).toEqual(true); - }); }); describe('Test text widget', () => { - test('Test with path and conditional', () => { // Test data const mockConditional = jest.fn(); const path = 'somePath'; - const widget = { - type: 'text', - align: 'center', - path, - text: 'Test text', - conditional: mockConditional - }; + const widget = { type: 'text', align: 'center', path, text: 'Test text', conditional: mockConditional }; mockedCheckConditional.mockReturnValueOnce([true, undefined, undefined]); setApplicationConfiguration({ sections: { [mainSection]: { widgets: ['widget'] } }, widgets: { widget } }); @@ -917,7 +924,6 @@ describe('Test text widget', () => { expect(mockedCheckConditional).toHaveBeenLastCalledWith(mockConditional, testInterviewAttributes, path, undefined); }); - }); describe('Test with custom path', () => { @@ -954,7 +960,7 @@ describe('Test with custom path', () => { }; beforeEach(() => { - setApplicationConfiguration({ sections: { choiceSection: { widgets: [ 'widgetWithCustom'] } }, widgets: { widgetWithCustom } }); + setApplicationConfiguration({ sections: { choiceSection: { widgets: ['widgetWithCustom'] } }, widgets: { widgetWithCustom } }); }); test('Test with custom path, custom choice not selected', () => { @@ -972,12 +978,26 @@ describe('Test with custom path', () => { expectedInterview.validations.section1.q4custom = true; // Test, custom statuses should be set to empty - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(choiceSection, testInterviewAttributes, { 'response.section1.q4': true }, _cloneDeep(valuesByPath)); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + choiceSection, + testInterviewAttributes, + { 'response.section1.q4': true }, + _cloneDeep(valuesByPath) + ); expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); - expect(updatedValuesByPath).toEqual(Object.assign({}, valuesByPath, { 'validations.section1.q4': true, 'validations.section1.q4custom': true })); + expect(updatedValuesByPath).toEqual( + Object.assign({}, valuesByPath, { 'validations.section1.q4': true, 'validations.section1.q4custom': true }) + ); expect(needUpdate).toEqual(false); expect(mockedCheckValidations).toHaveBeenCalledTimes(1); - expect(mockedCheckValidations).toHaveBeenCalledWith(widgetWithCustom.validations, (testInterviewAttributes as any).response.section1.q4, undefined, testInterviewAttributes, 'section1.q4', 'section1.q4custom'); + expect(mockedCheckValidations).toHaveBeenCalledWith( + widgetWithCustom.validations, + (testInterviewAttributes as any).response.section1.q4, + undefined, + testInterviewAttributes, + 'section1.q4', + 'section1.q4custom' + ); expect(updatedInterview.widgets.widgetWithCustom).toEqual({ ...defaultExpectedWidgetStatus, isValid: true, @@ -1007,12 +1027,26 @@ describe('Test with custom path', () => { expectedInterview.validations.section1.q4custom = true; // Test, the custom value should be responded - const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets(choiceSection, testInterviewAttributes, { 'response.section1.q4': true }, _cloneDeep(valuesByPath)); + const { updatedInterview, updatedValuesByPath, needUpdate } = prepareSectionWidgets( + choiceSection, + testInterviewAttributes, + { 'response.section1.q4': true }, + _cloneDeep(valuesByPath) + ); expect(updatedInterview).toEqual(expect.objectContaining(expectedInterview)); - expect(updatedValuesByPath).toEqual(Object.assign({}, valuesByPath, { 'validations.section1.q4': true, 'validations.section1.q4custom': true })); + expect(updatedValuesByPath).toEqual( + Object.assign({}, valuesByPath, { 'validations.section1.q4': true, 'validations.section1.q4custom': true }) + ); expect(needUpdate).toEqual(false); expect(mockedCheckValidations).toHaveBeenCalledTimes(1); - expect(mockedCheckValidations).toHaveBeenCalledWith(widgetWithCustom.validations, currentResponse, currentCustomResponse, testInterviewAttributes, 'section1.q4', 'section1.q4custom'); + expect(mockedCheckValidations).toHaveBeenCalledWith( + widgetWithCustom.validations, + currentResponse, + currentCustomResponse, + testInterviewAttributes, + 'section1.q4', + 'section1.q4custom' + ); expect(updatedInterview.widgets.widgetWithCustom).toEqual({ ...defaultExpectedWidgetStatus, isValid: true, @@ -1024,5 +1058,4 @@ describe('Test with custom path', () => { expect(updatedInterview.visibleWidgets).toEqual(['section1.q4', 'section1.q4custom']); expect(updatedInterview.allWidgetsValid).toEqual(true); }); - }); diff --git a/packages/evolution-frontend/src/components/admin/monitoring/__tests__/HorizontalBarMonitoringChart.test.tsx b/packages/evolution-frontend/src/components/admin/monitoring/__tests__/HorizontalBarMonitoringChart.test.tsx index c0a3cd734..907186e09 100644 --- a/packages/evolution-frontend/src/components/admin/monitoring/__tests__/HorizontalBarMonitoringChart.test.tsx +++ b/packages/evolution-frontend/src/components/admin/monitoring/__tests__/HorizontalBarMonitoringChart.test.tsx @@ -38,12 +38,7 @@ describe('HorizontalBarMonitoringChart', () => { mockFetch.mockClear(); }); - const defaultProps = { - apiUrl: 'http://example.com/api/data', - chartTitle: 'Test Chart Title', - xAxisTitle: 'X Axis', - yAxisTitle: 'Y Axis' - }; + const defaultProps = { apiUrl: 'http://example.com/api/data', chartTitle: 'Test Chart Title', xAxisTitle: 'X Axis', yAxisTitle: 'Y Axis' }; // const mockData = [ // { label: 'A', percentage: 60, count: 6 }, @@ -92,9 +87,7 @@ describe('HorizontalBarMonitoringChart', () => { }); it('shows error when server returns 500', async () => { - mockFetch.mockResolvedValueOnce({ - status: 500 - } as Response); + mockFetch.mockResolvedValueOnce({ status: 500 } as Response); render(); await waitFor(() => { @@ -117,10 +110,7 @@ describe('HorizontalBarMonitoringChart', () => { }); it('shows error when JSON parsing fails', async () => { - mockFetch.mockResolvedValueOnce({ - status: 200, - json: jest.fn().mockRejectedValueOnce(new Error('Invalid JSON')) - } as unknown as Response); + mockFetch.mockResolvedValueOnce({ status: 200, json: jest.fn().mockRejectedValueOnce(new Error('Invalid JSON')) } as unknown as Response); render(); await waitFor(() => { @@ -129,10 +119,7 @@ describe('HorizontalBarMonitoringChart', () => { }); it('POSTs with expected payload and credentials', async () => { - (fetch as jest.Mock).mockResolvedValueOnce({ - status: 200, - json: async () => ({ status: 'ok', result: { distribution: [] } }) - } as Response); + (fetch as jest.Mock).mockResolvedValueOnce({ status: 200, json: async () => ({ status: 'ok', result: { distribution: [] } }) } as Response); render(); await waitFor(() => expect(fetch).toHaveBeenCalled()); const [url, init] = (fetch as jest.Mock).mock.calls[0]; @@ -143,10 +130,7 @@ describe('HorizontalBarMonitoringChart', () => { }); it('renders nothing if data is empty', async () => { - mockFetch.mockResolvedValueOnce({ - status: 200, - json: async () => ({ status: 'ok', result: { distribution: [] } }) - } as Response); + mockFetch.mockResolvedValueOnce({ status: 200, json: async () => ({ status: 'ok', result: { distribution: [] } }) } as Response); const { container } = render(); await waitFor(() => { diff --git a/packages/evolution-frontend/src/components/admin/monitoring/__tests__/SingleValueMonitoringChart.test.tsx b/packages/evolution-frontend/src/components/admin/monitoring/__tests__/SingleValueMonitoringChart.test.tsx index 7dd34236c..d0b063171 100644 --- a/packages/evolution-frontend/src/components/admin/monitoring/__tests__/SingleValueMonitoringChart.test.tsx +++ b/packages/evolution-frontend/src/components/admin/monitoring/__tests__/SingleValueMonitoringChart.test.tsx @@ -34,18 +34,10 @@ describe('SingleValueMonitoringChart', () => { mockFetch.mockClear(); }); - const defaultProps = { - apiUrl: 'http://example.com/api/data', - valueName: 'testValue', - valueTitle: 'Test Value Title', - valueUnit: 'km' - }; + const defaultProps = { apiUrl: 'http://example.com/api/data', valueName: 'testValue', valueTitle: 'Test Value Title', valueUnit: 'km' }; it('fetches and displays value correctly when status is 200', async () => { - mockFetch.mockResolvedValueOnce({ - status: 200, - json: async () => ({ status: 'ok', result: { testValue: 42 } }) - } as Response); + mockFetch.mockResolvedValueOnce({ status: 200, json: async () => ({ status: 'ok', result: { testValue: 42 } }) } as Response); render(); @@ -57,10 +49,7 @@ describe('SingleValueMonitoringChart', () => { }); it('displays error when value is not a number', async () => { - mockFetch.mockResolvedValueOnce({ - status: 200, - json: async () => ({ status: 'ok', result: { testValue: 'not a number' } }) - } as Response); + mockFetch.mockResolvedValueOnce({ status: 200, json: async () => ({ status: 'ok', result: { testValue: 'not a number' } }) } as Response); render(); @@ -80,10 +69,7 @@ describe('SingleValueMonitoringChart', () => { }); it('displays decimal values correctly', async () => { - mockFetch.mockResolvedValueOnce({ - status: 200, - json: async () => ({ status: 'ok', result: { testValue: 42.756 } }) - } as Response); + mockFetch.mockResolvedValueOnce({ status: 200, json: async () => ({ status: 'ok', result: { testValue: 42.756 } }) } as Response); render(); @@ -95,10 +81,7 @@ describe('SingleValueMonitoringChart', () => { it('displays value without unit when not provided', async () => { const propsWithoutUnit = { ...defaultProps, valueUnit: undefined }; - mockFetch.mockResolvedValueOnce({ - status: 200, - json: async () => ({ status: 'ok', result: { testValue: 100 } }) - } as Response); + mockFetch.mockResolvedValueOnce({ status: 200, json: async () => ({ status: 'ok', result: { testValue: 100 } }) } as Response); render(); @@ -109,10 +92,7 @@ describe('SingleValueMonitoringChart', () => { }); it('displays error when JSON parsing fails', async () => { - mockFetch.mockResolvedValueOnce({ - status: 200, - json: jest.fn().mockRejectedValueOnce(new Error('Invalid JSON')) - } as unknown as Response); + mockFetch.mockResolvedValueOnce({ status: 200, json: jest.fn().mockRejectedValueOnce(new Error('Invalid JSON')) } as unknown as Response); render(); @@ -123,9 +103,7 @@ describe('SingleValueMonitoringChart', () => { }); it('displays error when server returns 404 status', async () => { - mockFetch.mockResolvedValueOnce({ - status: 404 - } as Response); + mockFetch.mockResolvedValueOnce({ status: 404 } as Response); render(); @@ -136,9 +114,7 @@ describe('SingleValueMonitoringChart', () => { }); it('displays error when server returns 500 status', async () => { - mockFetch.mockResolvedValueOnce({ - status: 500 - } as Response); + mockFetch.mockResolvedValueOnce({ status: 500 } as Response); render(); diff --git a/packages/evolution-frontend/src/components/admin/routers/__tests__/AdminSurveyRouter.test.ts b/packages/evolution-frontend/src/components/admin/routers/__tests__/AdminSurveyRouter.test.ts index 2d6a7c464..073c64181 100644 --- a/packages/evolution-frontend/src/components/admin/routers/__tests__/AdminSurveyRouter.test.ts +++ b/packages/evolution-frontend/src/components/admin/routers/__tests__/AdminSurveyRouter.test.ts @@ -10,10 +10,7 @@ import { render } from '@testing-library/react'; import getAdminSurveyRoutes from '../AdminSurveyRouter'; // Mock the components since we're testing route configuration, not component rendering -jest.mock('../AdminRootLayout', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'AdminRootLayout') -})); +jest.mock('../AdminRootLayout', () => ({ __esModule: true, default: () => React.createElement('div', null, 'AdminRootLayout') })); jest.mock('chaire-lib-frontend/lib/components/routers/PrivateRoute', () => ({ __esModule: true, @@ -31,20 +28,11 @@ jest.mock('chaire-lib-frontend/lib/components/routers/AdminRoute', () => ({ })); // Mock page components -jest.mock('../../pages/AdminMonitoringPage', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'AdminMonitoringPage') -})); +jest.mock('../../pages/AdminMonitoringPage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'AdminMonitoringPage') })); -jest.mock('../../pages/ReviewPage', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'AdminReviewPage') -})); +jest.mock('../../pages/ReviewPage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'AdminReviewPage') })); -jest.mock('../../pages/SurveyCorrection', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'AdminSurveyCorrectionPage') -})); +jest.mock('../../pages/SurveyCorrection', () => ({ __esModule: true, default: () => React.createElement('div', null, 'AdminSurveyCorrectionPage') })); jest.mock('../../pages/RespondentBehaviorPage', () => ({ __esModule: true, @@ -56,10 +44,7 @@ jest.mock('../../../pages/SurveyUnavailablePage', () => ({ default: () => React.createElement('div', null, 'SurveyUnavailablePage') })); -jest.mock('../../../pages/NotFoundPage', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'NotFoundPage') -})); +jest.mock('../../../pages/NotFoundPage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'NotFoundPage') })); jest.mock('chaire-lib-frontend/lib/components/pages/UnauthorizedPage', () => ({ __esModule: true, @@ -71,9 +56,7 @@ jest.mock('chaire-lib-frontend/lib/components/pages/MaintenancePage', () => ({ default: () => React.createElement('div', null, 'MaintenancePage') })); -jest.mock('chaire-lib-frontend/lib/components/pages', () => ({ - LoginPage: () => React.createElement('div', null, 'AdminLoginPage') -})); +jest.mock('chaire-lib-frontend/lib/components/pages', () => ({ LoginPage: () => React.createElement('div', null, 'AdminLoginPage') })); jest.mock('chaire-lib-frontend/lib/components/pages/RegisterPage', () => ({ __esModule: true, @@ -100,10 +83,7 @@ jest.mock('chaire-lib-frontend/lib/components/pages/UnconfirmedPage', () => ({ default: () => React.createElement('div', null, 'UnconfirmedPage') })); -jest.mock('../../../hoc/SurveyWithErrorBoundary', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'Survey') -})); +jest.mock('../../../hoc/SurveyWithErrorBoundary', () => ({ __esModule: true, default: () => React.createElement('div', null, 'Survey') })); jest.mock('chaire-lib-frontend/lib/components/pages/admin/UsersPage', () => ({ __esModule: true, @@ -115,23 +95,14 @@ jest.mock('../../interviews/InterviewsByAccessCode', () => ({ default: () => React.createElement('div', null, 'InterviewsByAccessCode') })); -jest.mock('../../pages/InterviewsPage', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'InterviewsPage') -})); +jest.mock('../../pages/InterviewsPage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'InterviewsPage') })); -jest.mock('../../pages/AdminHomePage', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'AdminHomePage') -})); +jest.mock('../../pages/AdminHomePage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'AdminHomePage') })); // Mock config jest.mock('chaire-lib-common/lib/config/shared/project.config', () => ({ __esModule: true, - default: { - auth: {}, - mapDefaultCenter: { lon: -73.6131, lat: 45.5041 } - } + default: { auth: {}, mapDefaultCenter: { lon: -73.6131, lat: 45.5041 } } })); describe('getAdminSurveyRoutes', () => { @@ -197,32 +168,29 @@ describe('getAdminSurveyRoutes', () => { expect(children.length).toBe(expectedRoutes.length); }); - it.each(expectedRoutes)( - 'Should have route $path with correct path and $wrapperType wrapper', - ({ path, wrapperType }) => { - const rootRoute = routes[0]; - const children = rootRoute.children || []; - - const getWrapperType = (pathToCheck: string): string => { - const route = children.find((child) => child.path === pathToCheck); - if (!route || !route.element) return 'Unknown'; - - // Render the element and check the output - const { container, unmount } = render(route.element as React.ReactElement); - const textContent = container.textContent || ''; - - // Clean up the render before returning to avoid memory leaks - unmount(); - - if (textContent.includes('AdminRoute')) return 'AdminRoute'; - if (textContent.includes('PrivateRoute')) return 'PrivateRoute'; - if (textContent.includes('PublicRoute')) return 'PublicRoute'; - return 'Unknown'; - }; - - const paths = children.map((child) => child.path); - expect(paths).toContain(path); - expect(getWrapperType(path)).toBe(wrapperType); - } - ); + it.each(expectedRoutes)('Should have route $path with correct path and $wrapperType wrapper', ({ path, wrapperType }) => { + const rootRoute = routes[0]; + const children = rootRoute.children || []; + + const getWrapperType = (pathToCheck: string): string => { + const route = children.find((child) => child.path === pathToCheck); + if (!route || !route.element) return 'Unknown'; + + // Render the element and check the output + const { container, unmount } = render(route.element as React.ReactElement); + const textContent = container.textContent || ''; + + // Clean up the render before returning to avoid memory leaks + unmount(); + + if (textContent.includes('AdminRoute')) return 'AdminRoute'; + if (textContent.includes('PrivateRoute')) return 'PrivateRoute'; + if (textContent.includes('PublicRoute')) return 'PublicRoute'; + return 'Unknown'; + }; + + const paths = children.map((child) => child.path); + expect(paths).toContain(path); + expect(getWrapperType(path)).toBe(wrapperType); + }); }); diff --git a/packages/evolution-frontend/src/components/admin/validations/InterviewStats.tsx b/packages/evolution-frontend/src/components/admin/validations/InterviewStats.tsx index e42513c96..f0cb0acd0 100644 --- a/packages/evolution-frontend/src/components/admin/validations/InterviewStats.tsx +++ b/packages/evolution-frontend/src/components/admin/validations/InterviewStats.tsx @@ -117,7 +117,7 @@ const InterviewStats = (props: InterviewStatsProps) => { Object.keys(surveyObjects.auditsByObject).some( (key) => surveyObjects.auditsByObject![key] && surveyObjects.auditsByObject![key].length > 0 )) || - (surveyObjects?.audits && surveyObjects.audits.length > 0) + (surveyObjects?.audits && surveyObjects.audits.length > 0) ); return ( diff --git a/packages/evolution-frontend/src/components/admin/validations/react-table-config.d.ts b/packages/evolution-frontend/src/components/admin/validations/react-table-config.d.ts index 9381a0ea8..c27604206 100644 --- a/packages/evolution-frontend/src/components/admin/validations/react-table-config.d.ts +++ b/packages/evolution-frontend/src/components/admin/validations/react-table-config.d.ts @@ -66,7 +66,8 @@ declare module 'react-table' { // take this file as-is, or comment out the sections that don't apply to your plugin configuration export interface TableOptions> - extends UseExpandedOptions, + extends + UseExpandedOptions, UseFiltersOptions, UseGlobalFiltersOptions, UseGroupByOptions, @@ -81,13 +82,11 @@ declare module 'react-table' { Record {} export interface Hooks = Record> - extends UseExpandedHooks, - UseGroupByHooks, - UseRowSelectHooks, - UseSortByHooks {} + extends UseExpandedHooks, UseGroupByHooks, UseRowSelectHooks, UseSortByHooks {} export interface TableInstance = Record> - extends UseColumnOrderInstanceProps, + extends + UseColumnOrderInstanceProps, UseExpandedInstanceProps, UseFiltersInstanceProps, UseGlobalFiltersInstanceProps, @@ -98,7 +97,8 @@ declare module 'react-table' { UseSortByInstanceProps {} export interface TableState = Record> - extends UseColumnOrderState, + extends + UseColumnOrderState, UseExpandedState, UseFiltersState, UseGlobalFiltersState, @@ -110,14 +110,16 @@ declare module 'react-table' { UseSortByState {} export interface ColumnInterface = Record> - extends UseFiltersColumnOptions, + extends + UseFiltersColumnOptions, UseGlobalFiltersColumnOptions, UseGroupByColumnOptions, UseResizeColumnsColumnOptions, UseSortByColumnOptions {} export interface ColumnInstance = Record> - extends UseFiltersColumnProps, + extends + UseFiltersColumnProps, UseGroupByColumnProps, UseResizeColumnsColumnProps, UseSortByColumnProps {} @@ -125,12 +127,8 @@ declare module 'react-table' { // TODO: Is the V really necessary? Why is it the only interface here that has it? /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ export interface Cell = Record, V = any> - extends UseGroupByCellProps, - UseRowStateCellProps {} + extends UseGroupByCellProps, UseRowStateCellProps {} export interface Row = Record> - extends UseExpandedRowProps, - UseGroupByRowProps, - UseRowSelectRowProps, - UseRowStateRowProps {} + extends UseExpandedRowProps, UseGroupByRowProps, UseRowSelectRowProps, UseRowStateRowProps {} } diff --git a/packages/evolution-frontend/src/components/hoc/__tests__/WithSurveyContextHoc.test.tsx b/packages/evolution-frontend/src/components/hoc/__tests__/WithSurveyContextHoc.test.tsx index 38d42af07..a6b9a8018 100644 --- a/packages/evolution-frontend/src/components/hoc/__tests__/WithSurveyContextHoc.test.tsx +++ b/packages/evolution-frontend/src/components/hoc/__tests__/WithSurveyContextHoc.test.tsx @@ -41,22 +41,24 @@ export const BaseTestComponent: React.FunctionComponent<{}> = (props: {}) => { const [devMode, dispatchSurvey] = React.useReducer(surveyReducer, { devMode: false }); return ( - + ); }; - + class TestComponent extends React.Component { public render() { - return
{Object.keys(this.props.surveyContext.sections)} {this.props.surveyContext.devMode === false ? 'normal' : 'dev' } {this.props.foo}
+ return ( +
+ {Object.keys(this.props.surveyContext.sections)} {this.props.surveyContext.devMode === false ? 'normal' : 'dev'} {this.props.foo} +
+ ); } } const TestComponentWithContext = withSurveyContext(TestComponent); test('Test HOC', () => { - const { container } = render( - - ); + const { container } = render(); expect(container).toMatchSnapshot(); -}) \ No newline at end of file +}); diff --git a/packages/evolution-frontend/src/components/hooks/__tests__/useSectionTemplate.test.ts b/packages/evolution-frontend/src/components/hooks/__tests__/useSectionTemplate.test.ts index fc3e1fdfa..28429c660 100644 --- a/packages/evolution-frontend/src/components/hooks/__tests__/useSectionTemplate.test.ts +++ b/packages/evolution-frontend/src/components/hooks/__tests__/useSectionTemplate.test.ts @@ -6,11 +6,9 @@ */ import { renderHook } from '@testing-library/react'; import { useSectionTemplate, SectionProps } from '../useSectionTemplate'; -import { MemoryRouter } from 'react-router' +import { MemoryRouter } from 'react-router'; -jest.mock('../../../services/url', () => ({ - getPathForSection: jest.fn(() => '/new-path') -})); +jest.mock('../../../services/url', () => ({ getPathForSection: jest.fn(() => '/new-path') })); describe('useSectionTemplate', () => { let props: SectionProps; @@ -18,13 +16,8 @@ describe('useSectionTemplate', () => { beforeEach(() => { props = { shortname: 'testSection', - sectionConfig: { - previousSection: null, - nextSection: null, - preload: jest.fn(), - widgets: [] - }, - interview: { response: { field1: 'test' }} as any, + sectionConfig: { previousSection: null, nextSection: null, preload: jest.fn(), widgets: [] }, + interview: { response: { field1: 'test' } } as any, errors: {}, user: {} as any, startUpdateInterview: jest.fn(), @@ -39,16 +32,14 @@ describe('useSectionTemplate', () => { it('should call preload function on mount', () => { renderHook(() => useSectionTemplate(props), { wrapper: MemoryRouter }); - expect(props.sectionConfig.preload).toHaveBeenCalledWith( - props.interview, { - startUpdateInterview: props.startUpdateInterview, - startAddGroupedObjects: props.startAddGroupedObjects, - startRemoveGroupedObjects: props.startRemoveGroupedObjects, - startNavigate: props.startNavigate, - callback: expect.any(Function), - user: props.user - } - ); + expect(props.sectionConfig.preload).toHaveBeenCalledWith(props.interview, { + startUpdateInterview: props.startUpdateInterview, + startAddGroupedObjects: props.startAddGroupedObjects, + startRemoveGroupedObjects: props.startRemoveGroupedObjects, + startNavigate: props.startNavigate, + callback: expect.any(Function), + user: props.user + }); }); it('should set preloaded to true if preload is not a function', () => { @@ -70,10 +61,10 @@ describe('useSectionTemplate', () => { props.allWidgetsValid = false; props.submitted = true; renderHook(() => useSectionTemplate(props), { wrapper: MemoryRouter }); - + // Verify scrollIntoView was called expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth', block: 'nearest' }); - + // Verify focus was set on text input const inputElement = document.getElementById('invalid-input'); expect(document.activeElement).toBe(inputElement); @@ -83,19 +74,18 @@ describe('useSectionTemplate', () => { // Setup scrollIntoView mock const scrollIntoViewMock = jest.fn(); Element.prototype.scrollIntoView = scrollIntoViewMock; - + document.body.innerHTML = `
`; - + props.allWidgetsValid = false; props.submitted = true; renderHook(() => useSectionTemplate(props), { wrapper: MemoryRouter }); - + // Verify scrollIntoView was called with correct parameters expect(scrollIntoViewMock).toHaveBeenCalledWith({ behavior: 'smooth', block: 'nearest' }); }); - }); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputButton.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputButton.test.tsx index 213c19b66..69acd0569 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputButton.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputButton.test.tsx @@ -14,47 +14,23 @@ import i18next from 'i18next'; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, pages: [], showUserInfo: true -} +}; describe('InputButton with normal options', () => { const conditionalFct = jest.fn().mockReturnValue(true); const translationFct = jest.fn().mockReturnValue('Translated string'); const choices = [ - { - value: 'val1', - label: { en: 'english value', fr: 'valeur française' }, - hidden: false, - color: 'green' - }, - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png', - color: 'red', - size: 'small' as const - }, - { - value: 'hiddenVal', - label: { en: 'english hidden', fr: 'cachée' }, - hidden: true - }, - { - value: 'conditionalVal', - label: { en: 'english conditional', fr: 'conditionnelle' }, - conditional: conditionalFct - }, - { - value: 'val3', - label: translationFct, - hidden: false, - color: 'green' - } + { value: 'val1', label: { en: 'english value', fr: 'valeur française' }, hidden: false, color: 'green' }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png', color: 'red', size: 'small' as const }, + { value: 'hiddenVal', label: { en: 'english hidden', fr: 'cachée' }, hidden: true }, + { value: 'conditionalVal', label: { en: 'english conditional', fr: 'conditionnelle' }, conditional: conditionalFct }, + { value: 'val3', label: translationFct, hidden: false, color: 'green' } ]; const widgetConfig = { @@ -65,32 +41,29 @@ describe('InputButton with normal options', () => { choices, containsHtml: true, size: 'medium' as const, - label: { - fr: `Texte en français`, - en: `English text` - }, + label: { fr: 'Texte en français', en: 'English text' }, isModal: true }; test('Render InputButton with normal option type', () => { - const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); expect(conditionalFct).toHaveBeenCalledTimes(1); expect(conditionalFct).toHaveBeenCalledWith(interviewAttributes, 'foo.test', userAttributes); expect(translationFct).toHaveBeenCalledWith(i18next.t, interviewAttributes, 'foo.test', userAttributes); - }); test('Test Button click event', () => { @@ -100,11 +73,11 @@ describe('InputButton with normal options', () => { id={'test'} onValueChange={mockOnValueChange} widgetConfig={widgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); // Html was added, so the complete text is not there, it is actually composed of many texts @@ -116,54 +89,33 @@ describe('InputButton with normal options', () => { const buttonOption3 = queryByText(choices[3].label['en']); expect(buttonOption3).toBeTruthy(); - + // Click on the close button fireEvent.click(buttonOption1 as any); expect(mockOnValueChange).toHaveBeenCalledTimes(1); - expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: choices[0].value }}); + expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: choices[0].value } }); // Click on the close button fireEvent.click(buttonOption2 as any); expect(mockOnValueChange).toHaveBeenCalledTimes(2); - expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: choices[1].value }}); + expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: choices[1].value } }); // Click on the close button fireEvent.click(buttonOption3 as any); expect(mockOnValueChange).toHaveBeenCalledTimes(3); - expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: choices[3].value }}); + expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: choices[3].value } }); }); - }); test('Render InputButton with choice function', () => { - const choices = [ - { - value: 'val1', - label: { en: 'english value', fr: 'valeur française' }, - hidden: false, - color: 'green' - }, - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png', - color: 'red', - size: 'small' as const - }, - { - value: 'hiddenVal', - label: { en: 'english hidden', fr: 'cachée' }, - hidden: true - }, - { - value: 'conditionalVal', - label: { en: 'english conditional', fr: 'conditionnelle' }, - conditional: () => false - } + { value: 'val1', label: { en: 'english value', fr: 'valeur française' }, hidden: false, color: 'green' }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png', color: 'red', size: 'small' as const }, + { value: 'hiddenVal', label: { en: 'english hidden', fr: 'cachée' }, hidden: true }, + { value: 'conditionalVal', label: { en: 'english conditional', fr: 'conditionnelle' }, conditional: () => false } ]; const choiceFct = jest.fn().mockReturnValue(choices); - + const widgetConfig = { type: 'question' as const, twoColumns: true, @@ -174,26 +126,24 @@ test('Render InputButton with choice function', () => { sameLine: false, containsHtml: true, size: 'medium' as const, - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; - + const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); expect(choiceFct).toHaveBeenCalledTimes(1); expect(choiceFct).toHaveBeenCalledWith(interviewAttributes, 'foo.test', userAttributes); - }); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputCheckbox.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputCheckbox.test.tsx index 5395b31c7..76b275cdf 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputCheckbox.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputCheckbox.test.tsx @@ -14,55 +14,30 @@ import { interviewAttributes } from './interviewData'; import InputCheckbox from '../InputCheckbox'; import i18next from 'i18next'; -jest.mock('chaire-lib-common/lib/utils/RandomUtils', () => ({ - shuffle: jest.fn() -})); +jest.mock('chaire-lib-common/lib/utils/RandomUtils', () => ({ shuffle: jest.fn() })); const shuffleMock = shuffle as jest.MockedFunction; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, pages: [], showUserInfo: true -} +}; describe('Render InputCheckbox with various parameter combinations, all parameters', () => { - const conditionalFct = jest.fn().mockReturnValue(true); const translationFct = jest.fn().mockReturnValue('Translated string'); const choices = [ - { - value: 'val1', - label: { en: 'english value', fr: 'valeur française' }, - hidden: false, - icon: faCrow - }, - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png' - }, - { - value: 'hiddenVal', - label: { en: 'english hidden', fr: 'cachée' }, - hidden: true - }, - { - value: 'conditionalVal', - label: { en: 'english conditional', fr: 'conditionnelle' }, - conditional: conditionalFct - }, - { - value: 'val3', - label: translationFct, - hidden: false, - color: 'green' - } + { value: 'val1', label: { en: 'english value', fr: 'valeur française' }, hidden: false, icon: faCrow }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png' }, + { value: 'hiddenVal', label: { en: 'english hidden', fr: 'cachée' }, hidden: true }, + { value: 'conditionalVal', label: { en: 'english conditional', fr: 'conditionnelle' }, conditional: conditionalFct }, + { value: 'val3', label: translationFct, hidden: false, color: 'green' } ]; const widgetConfig = { @@ -80,28 +55,26 @@ describe('Render InputCheckbox with various parameter combinations, all paramete customLabel: 'custom label', size: 'medium' as const, containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; beforeEach(() => { conditionalFct.mockClear(); - }) + }); test('Includes hidden values, conditional displayed, no custom, 2 columns, 2 selected values', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={['val1', 'val2']} inputRef={React.createRef()} - interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -116,13 +89,15 @@ describe('Render InputCheckbox with various parameter combinations, all paramete const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={testWidgetConfig} value={[]} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -134,13 +109,15 @@ describe('Render InputCheckbox with various parameter combinations, all paramete const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={testWidgetConfig} value={['val2']} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -153,14 +130,16 @@ describe('Render InputCheckbox with various parameter combinations, all paramete const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={testWidgetConfig} value={['value']} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' - customId='foo.test.custom' + path="foo.test" + customId="foo.test.custom" /> ); expect(container).toMatchSnapshot(); @@ -171,32 +150,26 @@ describe('Render InputCheckbox with various parameter combinations, all paramete const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={testWidgetConfig} value={['custom']} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' - customId='foo.test.custom' + path="foo.test" + customId="foo.test.custom" /> ); expect(container).toMatchSnapshot(); }); - }); describe('Render InputCheckbox with minimum parameters', () => { - const choices = [ - { - value: 'val1', - label: { en: 'english value', fr: 'valeur française' } - }, - { - value: 'val2', - label: 'Unilingual label' - } + { value: 'val1', label: { en: 'english value', fr: 'valeur française' } }, + { value: 'val2', label: 'Unilingual label' } ]; const widgetConfig = { @@ -206,44 +179,32 @@ describe('Render InputCheckbox with minimum parameters', () => { inputType: 'checkbox' as const, choices, containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; test('Minimum parameters, one value selected', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={['val1']} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); describe('Render InputCheckbox with HTML label', () => { - const choices = [ - { - value: 'first value', - label: { - fr: '
premiere valeur
', - en: '
first value
' - } - }, - { - value: 'second value', - label: '
second value
' - } + { value: 'first value', label: { fr: '
premiere valeur
', en: '
first value
' } }, + { value: 'second value', label: '
second value
' } ]; const widgetConfig = { @@ -254,26 +215,24 @@ describe('Render InputCheckbox with HTML label', () => { choices, size: 'medium', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; test('HTML label', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={['second value']} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputChoiceSorting.test.ts b/packages/evolution-frontend/src/components/inputs/__tests__/InputChoiceSorting.test.ts index b39c75821..650bff306 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputChoiceSorting.test.ts +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputChoiceSorting.test.ts @@ -4,11 +4,11 @@ * This file is licensed under the MIT License. * License text available at https://opensource.org/licenses/MIT */ -import {sortByParameters} from '../InputChoiceSorting'; +import { sortByParameters } from '../InputChoiceSorting'; -const array = [0,1,2,3,4,5,6,7,8,9]; +const array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; -describe('Sort array in vertical fashion ', () =>{ +describe('Sort array in vertical fashion ', () => { const columns = 3; const rows = 3; const alignment = 'vertical'; @@ -16,26 +16,37 @@ describe('Sort array in vertical fashion ', () =>{ test('Vertical By columns', () => { const result = sortByParameters(array, alignment, columns); - expect(result).toEqual([[0,1,2,3],[4,5,6],[7,8,9]]); + expect(result).toEqual([ + [0, 1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ]); }); test('Vertical By rows', () => { const result = sortByParameters(array, alignment, undefined, rows); - expect(result).toEqual([[0,1,2],[3,4,5],[6,7,8],[9]]); + expect(result).toEqual([[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]); }); test('Vertical By columns & rows', () => { const result = sortByParameters(array, alignment, columns, rows); - expect(result).toEqual([[0,1,2,3],[4,5,6],[7,8,9]]); + expect(result).toEqual([ + [0, 1, 2, 3], + [4, 5, 6], + [7, 8, 9] + ]); }); test('Vertical By no columns & no rows', () => { const result = sortByParameters(array, alignment); - expect(result).toEqual([[0,1,2,3,4],[5,6,7,8,9]]); + expect(result).toEqual([ + [0, 1, 2, 3, 4], + [5, 6, 7, 8, 9] + ]); }); }); -describe('Sort array in horizontal fashion', () =>{ +describe('Sort array in horizontal fashion', () => { const columns = 3; const rows = 3; const alignment = 'horizontal'; @@ -43,22 +54,41 @@ describe('Sort array in horizontal fashion', () =>{ test('horizontal By columns', () => { const result = sortByParameters(array, alignment, columns); - expect(result).toEqual([[0,3,6,9],[1,4,7],[2,5,8]]); + expect(result).toEqual([ + [0, 3, 6, 9], + [1, 4, 7], + [2, 5, 8] + ]); }); test('horizontal By rows', () => { const result = sortByParameters(array, alignment, undefined, rows); - expect(result).toEqual([[0,4,8],[1,5,9],[2,6],[3,7]]); + expect(result).toEqual([ + [0, 4, 8], + [1, 5, 9], + [2, 6], + [3, 7] + ]); }); test('horizontal By columns & rows', () => { const result = sortByParameters(array, alignment, columns, rows); - expect(result).toEqual([[0,3,6,9],[1,4,7],[2,5,8]]); + expect(result).toEqual([ + [0, 3, 6, 9], + [1, 4, 7], + [2, 5, 8] + ]); }); test('horizontal By no columns & no rows', () => { const result = sortByParameters(array, alignment); - expect(result).toEqual([[0,5],[1,6],[2,7],[3,8],[4,9]]); + expect(result).toEqual([ + [0, 5], + [1, 6], + [2, 7], + [3, 8], + [4, 9] + ]); }); }); @@ -69,22 +99,37 @@ describe('Sort array with no alignement', () => { test('with columns', () => { const result = sortByParameters(array, undefined, columns); - expect(result).toEqual([[0,1,2,3,4],[5,6,7,8,9]]); + expect(result).toEqual([ + [0, 1, 2, 3, 4], + [5, 6, 7, 8, 9] + ]); }); test('with rows', () => { const result = sortByParameters(array, undefined, undefined, rows); - expect(result).toEqual([[0,1],[2,3],[4,5],[6,7],[8,9]]); + expect(result).toEqual([ + [0, 1], + [2, 3], + [4, 5], + [6, 7], + [8, 9] + ]); }); test('with columns & rows', () => { const result = sortByParameters(array, undefined, columns, rows); - expect(result).toEqual([[0,1,2,3,4],[5,6,7,8,9]]); + expect(result).toEqual([ + [0, 1, 2, 3, 4], + [5, 6, 7, 8, 9] + ]); }); test('with no columns & no rows', () => { const result = sortByParameters(array); - expect(result).toEqual([[0,1,2,3,4],[5,6,7,8,9]]); + expect(result).toEqual([ + [0, 1, 2, 3, 4], + [5, 6, 7, 8, 9] + ]); }); }); @@ -96,62 +141,94 @@ describe('Sort array by parameters', () => { test('with valid parameters.', () => { const result = sortByParameters(array, alignment, columns, undefined, undefined); - expect(result).toEqual([[0,1,2,3,4],[5,6,7,8,9]]); + expect(result).toEqual([ + [0, 1, 2, 3, 4], + [5, 6, 7, 8, 9] + ]); }); test('with invalid parameters.', () => { const result = sortByParameters(array, alignment, undefined, rows, undefined); - expect(result).toEqual([[0,1,2,3,4],[5,6,7,8,9]]); + expect(result).toEqual([ + [0, 1, 2, 3, 4], + [5, 6, 7, 8, 9] + ]); }); test('with valid columns but invalid rows', () => { const result = sortByParameters(array, alignment, columns, rows, undefined); - expect(result).toEqual([[0,1,2,3,4],[5,6,7,8,9]]); - }) + expect(result).toEqual([ + [0, 1, 2, 3, 4], + [5, 6, 7, 8, 9] + ]); + }); }); describe('Sort array by custom parameters', () => { - const sameAmount = [4,4,2]; - const moreParameters = [6,6,4]; - const lessParameters = [2,2]; - const invalidHorizontalParameters = [3,5,2]; + const sameAmount = [4, 4, 2]; + const moreParameters = [6, 6, 4]; + const lessParameters = [2, 2]; + const invalidHorizontalParameters = [3, 5, 2]; const vertical = 'vertical'; const horizontal = 'horizontal'; test('Vertical with same amount of choices and parameters.', () => { - const result = sortByParameters(array, vertical, undefined, undefined, sameAmount) + const result = sortByParameters(array, vertical, undefined, undefined, sameAmount); - expect(result).toEqual([[0,1,2,3],[4,5,6,7],[8,9]]); + expect(result).toEqual([ + [0, 1, 2, 3], + [4, 5, 6, 7], + [8, 9] + ]); }); test('Vertical with less parameters than choices.', () => { - const result = sortByParameters(array, vertical, undefined, undefined, lessParameters) + const result = sortByParameters(array, vertical, undefined, undefined, lessParameters); - expect(result).toEqual([[0,1],[2,3],[4,5,6,7,8,9]]); + expect(result).toEqual([ + [0, 1], + [2, 3], + [4, 5, 6, 7, 8, 9] + ]); }); test('Vertical with more parameters than choices.', () => { - const result = sortByParameters(array, vertical, undefined, undefined, moreParameters) + const result = sortByParameters(array, vertical, undefined, undefined, moreParameters); - expect(result).toEqual([[0,1,2,3,4,5],[6,7,8,9]]); + expect(result).toEqual([ + [0, 1, 2, 3, 4, 5], + [6, 7, 8, 9] + ]); }); test('Horizontal with same amount of choices and parameters.', () => { - const result = sortByParameters(array, horizontal, undefined, undefined, sameAmount) + const result = sortByParameters(array, horizontal, undefined, undefined, sameAmount); - expect(result).toEqual([[0,4,8],[1,5,9],[2,6],[3,7]]); + expect(result).toEqual([ + [0, 4, 8], + [1, 5, 9], + [2, 6], + [3, 7] + ]); }); test('Horizontal with less parameters than choices.', () => { - const result = sortByParameters(array, horizontal, undefined, undefined, lessParameters) + const result = sortByParameters(array, horizontal, undefined, undefined, lessParameters); - expect(result).toEqual([[0,2],[1,3]]); + expect(result).toEqual([ + [0, 2], + [1, 3] + ]); }); test('Horizontal with more parameters than choices.', () => { - const result = sortByParameters(array, horizontal, undefined, undefined, moreParameters) + const result = sortByParameters(array, horizontal, undefined, undefined, moreParameters); - expect(result).toEqual([[0,6],[1,7],[2,8],[3,9],[4],[5]]); + expect(result).toEqual([[0, 6], [1, 7], [2, 8], [3, 9], [4], [5]]); }); test('Horizontal with invalid parameters', () => { const result = sortByParameters(array, horizontal, undefined, undefined, invalidHorizontalParameters); - expect(result).toEqual([[0,3,8],[1,4,9],[2,5]]); + expect(result).toEqual([ + [0, 3, 8], + [1, 4, 9], + [2, 5] + ]); }); -}); \ No newline at end of file +}); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputDatePicker.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputDatePicker.test.tsx index 9197516ea..8f3b8b6f3 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputDatePicker.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputDatePicker.test.tsx @@ -19,41 +19,39 @@ jest.mock('react-datepicker/dist/react-datepicker.css', () => {}); const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, pages: [], showUserInfo: true -} +}; // TODO These do not test the datepicker data itself, it's just the input describe('Should correctly render InputDatePicker with minimal parameters', () => { - const widgetConfig = { type: 'question' as const, twoColumns: true, path: 'test.foo', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - }, + label: { fr: 'Texte en français', en: 'English text' }, inputType: 'datePicker' as const - } + }; test('Test without value', () => { // Should have a blank style const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={undefined} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -63,50 +61,46 @@ describe('Should correctly render InputDatePicker with minimal parameters', () = const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={'2022-08-10T12:00:00.000Z'} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); describe('Should correctly render InputDatePicker with various parameters', () => { - const baseWidgetConfig = { type: 'question' as const, twoColumns: true, path: 'test.foo', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - }, + label: { fr: 'Texte en français', en: 'English text' }, inputType: 'datePicker' as const - } + }; test('Test with min max date values', () => { const mockMinDate = jest.fn().mockReturnValue(new Date('2022-12-10')); - const widgetConfig = Object.assign({ - maxDate: new Date('2022-08-10'), - minDate: mockMinDate - }, baseWidgetConfig); + const widgetConfig = Object.assign({ maxDate: new Date('2022-08-10'), minDate: mockMinDate }, baseWidgetConfig); const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={'2022-11-11T12:00:00.000Z'} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -114,24 +108,21 @@ describe('Should correctly render InputDatePicker with various parameters', () = }); test('Test time, placeholder labels and locale', () => { - const widgetConfig = Object.assign({ - showTimeSelect: true, - placeholderText: 'click', - locale: { fr: 'fr', en: 'en-CA' }, - }, baseWidgetConfig); + const widgetConfig = Object.assign({ showTimeSelect: true, placeholderText: 'click', locale: { fr: 'fr', en: 'en-CA' } }, baseWidgetConfig); const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={undefined} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputLoading.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputLoading.test.tsx index 7799a30eb..4ade55cb9 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputLoading.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputLoading.test.tsx @@ -10,7 +10,7 @@ import '@testing-library/jest-dom'; import InputLoading from '../InputLoading'; -test('Should correctly render Input Loading', () =>{ +test('Should correctly render Input Loading', () => { const { container } = render(); expect(container).toMatchSnapshot(); }); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputMapFindPlace.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputMapFindPlace.test.tsx index 49d42730a..a90ef80b1 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputMapFindPlace.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputMapFindPlace.test.tsx @@ -18,15 +18,13 @@ jest.mock('react-markdown', () => 'Markdown'); jest.mock('remark-gfm', () => 'remark-gfm'); // Mock db queries -jest.mock('../maps/google/GoogleGeocoder', () => ({ - geocodeMultiplePlaces: jest.fn() -})); +jest.mock('../maps/google/GoogleGeocoder', () => ({ geocodeMultiplePlaces: jest.fn() })); const mockedGeocode = geocodeMultiplePlaces as jest.MockedFunction; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, @@ -39,10 +37,7 @@ const baseWidgetConfig = { twoColumns: true, path: 'test.foo', containsHtml: true, - label: { - fr: 'Texte en français', - en: 'English text' - }, + label: { fr: 'Texte en français', en: 'English text' }, size: 'medium' as const, inputType: 'mapFindPlace' as const, defaultCenter: { lat: 45, lon: -73 }, @@ -50,70 +45,62 @@ const baseWidgetConfig = { }; describe('Render InputMapPoint with various parameters', () => { - test('Test with minimal parameters', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={baseWidgetConfig} value={undefined} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); test('Test with all parameters', () => { - - const testWidgetConfig = Object.assign({ - geocodingQueryString: jest.fn(), - refreshGeocodingLabel: { - fr: 'Rafraîchir la carte', - en: 'Refresh map' - }, - icon: { - url: 'path/to/icon', - size: [80, 80] as [number, number] - }, - maxZoom: 18, - defaultZoom: 15, - coordinatesPrecision: 6, - placesIcon: { - url: 'path/to/places-icon', - size: [85, 85] as [number, number] - }, - selectedIcon: { - url: 'path/to/selected-icon', - size: [90, 90] as [number, number] - }, - maxGeocodingResultsBounds: function (interview, path) { - return [{ lat: 45.2229, lng: -74.3230 }, { lat: 46.1181, lng: -72.9215 }] as [{ lat: number; lng: number; }, { lat: number; lng: number; }]; + const testWidgetConfig = Object.assign( + { + geocodingQueryString: jest.fn(), + refreshGeocodingLabel: { fr: 'Rafraîchir la carte', en: 'Refresh map' }, + icon: { url: 'path/to/icon', size: [80, 80] as [number, number] }, + maxZoom: 18, + defaultZoom: 15, + coordinatesPrecision: 6, + placesIcon: { url: 'path/to/places-icon', size: [85, 85] as [number, number] }, + selectedIcon: { url: 'path/to/selected-icon', size: [90, 90] as [number, number] }, + maxGeocodingResultsBounds: function (interview, path) { + return [ + { lat: 45.2229, lng: -74.323 }, + { lat: 46.1181, lng: -72.9215 } + ] as [{ lat: number; lng: number }, { lat: number; lng: number }]; + }, + invalidGeocodingResultTypes: ['political', 'country'], + updateDefaultValueWhenResponded: true }, - invalidGeocodingResultTypes: [ - 'political', - 'country', - ], - updateDefaultValueWhenResponded: true - }, baseWidgetConfig); + baseWidgetConfig + ); const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={testWidgetConfig} value={{ type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.1, 45.02] } }} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); describe('Test geocoding requests', () => { @@ -121,23 +108,19 @@ describe('Test geocoding requests', () => { const geocodingString = 'foo restaurant'; const mockOnValueChange = jest.fn(); - const testWidgetConfig = Object.assign({ - geocodingQueryString: jest.fn().mockReturnValue(geocodingString), - refreshGeocodingLabel: { - fr: 'Geocode', - en: 'Geocode' - }, - icon: { - url: 'path/to/icon', - }, - maxZoom: 18, - defaultZoom: 15, - placesIcon: { - url: 'path/to/icon', + const testWidgetConfig = Object.assign( + { + geocodingQueryString: jest.fn().mockReturnValue(geocodingString), + refreshGeocodingLabel: { fr: 'Geocode', en: 'Geocode' }, + icon: { url: 'path/to/icon' }, + maxZoom: 18, + defaultZoom: 15, + placesIcon: { url: 'path/to/icon' }, + size: 'medium', + updateDefaultValueWhenResponded: true }, - size: 'medium', - updateDefaultValueWhenResponded: true - }, baseWidgetConfig); + baseWidgetConfig + ); const placeFeature1 = { type: 'Feature' as const, @@ -151,7 +134,7 @@ describe('Test geocoding requests', () => { }; const placeFeature3 = { type: 'Feature' as const, - geometry: { type: 'Point' as const, coordinates: [ -73.5673919, 45.5018869] }, + geometry: { type: 'Point' as const, coordinates: [-73.5673919, 45.5018869] }, properties: { placeData: { place_id: '3', formatted_address: 'Montreal, QC, Canada', types: ['locality', 'political'] } } }; @@ -161,18 +144,19 @@ describe('Test geocoding requests', () => { }); test('Geocode with multiple results', async () => { - - const { container } = render(); + const { container } = render( + + ); const user = userEvent.setup(); // Find and click on the Geocode button, to return multiple values @@ -193,22 +177,22 @@ describe('Test geocoding requests', () => { // Make sure the value has not been changed expect(mockOnValueChange).not.toHaveBeenCalled(); - }); test('Geocode with single results, and confirm result', async () => { - - const { container } = render(); + const { container } = render( + + ); const user = userEvent.setup(); @@ -227,19 +211,23 @@ describe('Test geocoding requests', () => { // Click on the confirm button and make sure the update function has been called await user.click(screen.getByText('ConfirmLocation')); expect(mockOnValueChange).toHaveBeenCalledTimes(1); - expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: { - type: 'Feature' as const, - geometry: placeFeature1.geometry, - properties: { - lastAction: 'findPlace', - geocodingQueryString: geocodingString, - geocodingResultsData: { - formatted_address: placeFeature1.properties.placeData.formatted_address, - place_id: placeFeature1.properties.placeData.place_id, - types: undefined, + expect(mockOnValueChange).toHaveBeenCalledWith({ + target: { + value: { + type: 'Feature' as const, + geometry: placeFeature1.geometry, + properties: { + lastAction: 'findPlace', + geocodingQueryString: geocodingString, + geocodingResultsData: { + formatted_address: placeFeature1.properties.placeData.formatted_address, + place_id: placeFeature1.properties.placeData.place_id, + types: undefined + } + } } } - } } }); + }); // There should not be any selection or confirm widgets anymore expect(container.querySelector('select')).not.toBeInTheDocument(); @@ -247,18 +235,19 @@ describe('Test geocoding requests', () => { }); test('Geocode with single result, then re-query with undefined results', async () => { - - const { container } = render(); + const { container } = render( + + ); const user = userEvent.setup(); @@ -287,22 +276,22 @@ describe('Test geocoding requests', () => { expect(selectionList2).not.toBeInTheDocument(); expect(screen.queryByText('ConfirmLocation')).not.toBeInTheDocument(); - }); test('Geocode with single result, then re-query with rejection', async () => { - - const { container } = render(); + const { container } = render( + + ); const user = userEvent.setup(); // Select and confirm button should not be present @@ -330,11 +319,9 @@ describe('Test geocoding requests', () => { expect(selectionList2).not.toBeInTheDocument(); expect(screen.queryByText('ConfirmLocation')).not.toBeInTheDocument(); - }); test('Click the geocode button, that triggers an update', async () => { - const props = { id: testId, onValueChange: mockOnValueChange, @@ -345,7 +332,7 @@ describe('Test geocoding requests', () => { interview: interviewAttributes, user: userAttributes, path: 'foo.test', - loadingState: 0, + loadingState: 0 }; const { container } = render(); @@ -364,44 +351,46 @@ describe('Test geocoding requests', () => { expect(selectionList).toBeInTheDocument(); expect(screen.getByText('ConfirmLocation')).toBeInTheDocument(); - }); test('Geocode with single imprecise result', async () => { - const widgetConfig = Object.assign({ - invalidGeocodingResultTypes: [ - 'political', - 'country', - 'administrative_area_level_1', - 'administrative_area_level_2', - 'administrative_area_level_3', - 'administrative_area_level_4', - 'administrative_area_level_5', - 'administrative_area_level_6', - 'administrative_area_level_7', - 'colloquial_area', - 'locality', - 'sublocality', - 'sublocality_level_1', - 'neighborhood', - 'route' - ] - }, testWidgetConfig); - - const { container } = render(); - const user = userEvent.setup(); - + const widgetConfig = Object.assign( + { + invalidGeocodingResultTypes: [ + 'political', + 'country', + 'administrative_area_level_1', + 'administrative_area_level_2', + 'administrative_area_level_3', + 'administrative_area_level_4', + 'administrative_area_level_5', + 'administrative_area_level_6', + 'administrative_area_level_7', + 'colloquial_area', + 'locality', + 'sublocality', + 'sublocality_level_1', + 'neighborhood', + 'route' + ] + }, + testWidgetConfig + ); + const { container } = render( + + ); + const user = userEvent.setup(); // Select and confirm button should not be present expect(container.querySelector('select')).not.toBeInTheDocument(); @@ -413,20 +402,24 @@ describe('Test geocoding requests', () => { expect(mockedGeocode).toHaveBeenCalledTimes(1); expect(mockedGeocode).toHaveBeenCalledWith(geocodingString, expect.anything()); - expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: { - type: 'Feature' as const, - geometry: placeFeature3.geometry, - properties: { - lastAction: 'findPlace', - geocodingQueryString: geocodingString, - geocodingResultsData: { - formatted_address: placeFeature3.properties.placeData.formatted_address, - place_id: placeFeature3.properties.placeData.place_id, - types: placeFeature3.properties.placeData.types, - }, - isGeocodingImprecise: true, // key part! + expect(mockOnValueChange).toHaveBeenCalledWith({ + target: { + value: { + type: 'Feature' as const, + geometry: placeFeature3.geometry, + properties: { + lastAction: 'findPlace', + geocodingQueryString: geocodingString, + geocodingResultsData: { + formatted_address: placeFeature3.properties.placeData.formatted_address, + place_id: placeFeature3.properties.placeData.place_id, + types: placeFeature3.properties.placeData.types + }, + isGeocodingImprecise: true // key part! + } + } } - } } }); + }); // Select list and confirm button should not be present expect(container.querySelector('select')).not.toBeInTheDocument(); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputMapPoint.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputMapPoint.test.tsx index 50d0f4f47..102fa4f83 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputMapPoint.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputMapPoint.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event' +import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; import { interviewAttributes } from './interviewData'; @@ -14,84 +14,77 @@ import InputMapPoint from '../InputMapPoint'; import { geocodeSinglePoint } from '../maps/google/GoogleGeocoder'; // Mock db queries -jest.mock('../maps/google/GoogleGeocoder', () => ({ - geocodeSinglePoint: jest.fn() -})); +jest.mock('../maps/google/GoogleGeocoder', () => ({ geocodeSinglePoint: jest.fn() })); const mockedGeocode = geocodeSinglePoint as jest.MockedFunction; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, pages: [], showUserInfo: true -} +}; const baseWidgetConfig = { type: 'question' as const, twoColumns: true, path: 'test.foo', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - }, + label: { fr: 'Texte en français', en: 'English text' }, inputType: 'mapPoint' as const, size: 'medium' as const, defaultCenter: { lat: 45, lon: -73 } }; describe('Render InputMapPoint with various parameters', () => { - test('Test with minimal parameters', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={baseWidgetConfig} value={undefined} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); test('Test with all parameters', () => { - - const testWidgetConfig = Object.assign({ - geocodingQueryString: jest.fn(), - refreshGeocodingLabel: { - fr: `Rafraîchir la carte`, - en: `Refresh map` - }, - icon: { - url: 'path/to/icon', - size: [20, 20] as [number, number] + const testWidgetConfig = Object.assign( + { + geocodingQueryString: jest.fn(), + refreshGeocodingLabel: { fr: 'Rafraîchir la carte', en: 'Refresh map' }, + icon: { url: 'path/to/icon', size: [20, 20] as [number, number] }, + maxZoom: 18, + defaultZoom: 15 }, - maxZoom: 18, - defaultZoom: 15 - }, baseWidgetConfig); + baseWidgetConfig + ); const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={testWidgetConfig} - value={{ type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.1, 45.02]}}} + value={{ type: 'Feature' as const, properties: {}, geometry: { type: 'Point' as const, coordinates: [-73.1, 45.02] } }} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); describe('Test geocoding requests', () => { @@ -99,31 +92,28 @@ describe('Test geocoding requests', () => { const geocodingString = '123 foo street'; const mockOnValueChange = jest.fn(); - const testWidgetConfig = Object.assign({ - geocodingQueryString: jest.fn().mockReturnValue(geocodingString), - refreshGeocodingLabel: { - fr: `Geocode`, - en: `Geocode` - }, - icon: { - url: 'path/to/icon', - size: [20, 20] as [number, number] - }, - maxZoom: 18, - defaultZoom: 15, - placesIcon: { - url: 'path/to/icon', - size: [60, 60] as [number, number] + const testWidgetConfig = Object.assign( + { + geocodingQueryString: jest.fn().mockReturnValue(geocodingString), + refreshGeocodingLabel: { fr: 'Geocode', en: 'Geocode' }, + icon: { url: 'path/to/icon', size: [20, 20] as [number, number] }, + maxZoom: 18, + defaultZoom: 15, + placesIcon: { url: 'path/to/icon', size: [60, 60] as [number, number] }, + updateDefaultValueWhenResponded: true }, - updateDefaultValueWhenResponded: true - }, baseWidgetConfig); + baseWidgetConfig + ); - const geocodedFeature = { - type: 'Feature' as const, + const geocodedFeature = { + type: 'Feature' as const, geometry: { type: 'Point' as const, coordinates: [-73.2, 45.1] }, - properties: { geocodingResultMetadata: { formattedAddress: '123 foo street, Montreal, QC' }, lastAction: 'geocoding', geocodingQueryString: geocodingString } + properties: { + geocodingResultMetadata: { formattedAddress: '123 foo street, Montreal, QC' }, + lastAction: 'geocoding', + geocodingQueryString: geocodingString + } }; - beforeEach(() => { mockedGeocode.mockClear(); @@ -131,17 +121,18 @@ describe('Test geocoding requests', () => { }); test('Geocode single result', async () => { - - render(); + render( + + ); const user = userEvent.setup(); // Find and click on the Geocode button, to return a single result @@ -152,22 +143,22 @@ describe('Test geocoding requests', () => { // Make sure the value was changed to the geocoded feature expect(mockOnValueChange).toHaveBeenCalledTimes(1); - expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: geocodedFeature }}); - + expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: geocodedFeature } }); }); test('Geocode with undefined', async () => { - - render(); + render( + + ); const user = userEvent.setup(); // Find and click on the Geocode button, to return undefined values @@ -178,22 +169,22 @@ describe('Test geocoding requests', () => { // Make sure the value was changed to undefined expect(mockOnValueChange).toHaveBeenCalledTimes(1); - expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: undefined }}); - + expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: undefined } }); }); test('Geocode with rejection', async () => { - - render(); + render( + + ); const user = userEvent.setup(); // Find and click on the Geocode button, with a rejected promise @@ -204,7 +195,6 @@ describe('Test geocoding requests', () => { // Make sure the value was changed to undefined expect(mockOnValueChange).toHaveBeenCalledTimes(1); - expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: undefined }}); + expect(mockOnValueChange).toHaveBeenCalledWith({ target: { value: undefined } }); }); - }); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputMultiselect.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputMultiselect.test.tsx index eb6197a84..7ad8e7f87 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputMultiselect.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputMultiselect.test.tsx @@ -14,46 +14,23 @@ import i18next from 'i18next'; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, pages: [], showUserInfo: true -} +}; describe('Render InputMultiselect with various options', () => { - const conditionalFct = jest.fn().mockReturnValue(true); const translationFct = jest.fn().mockReturnValue('Translated string'); const choices = [ - { - value: 'val1', - label: { en: 'english value', fr: 'valeur française' }, - hidden: false, - icon: 'creative-commons' as const - }, - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png' - }, - { - value: 'hiddenVal', - label: { en: 'english hidden', fr: 'cachée' }, - hidden: true - }, - { - value: 'conditionalVal', - label: { en: 'english conditional', fr: 'conditionnelle' }, - conditional: conditionalFct - }, - { - value: 'val3', - label: translationFct, - hidden: false, - color: 'green' - } + { value: 'val1', label: { en: 'english value', fr: 'valeur française' }, hidden: false, icon: 'creative-commons' as const }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png' }, + { value: 'hiddenVal', label: { en: 'english hidden', fr: 'cachée' }, hidden: true }, + { value: 'conditionalVal', label: { en: 'english conditional', fr: 'conditionnelle' }, conditional: conditionalFct }, + { value: 'val3', label: translationFct, hidden: false, color: 'green' } ]; const widgetConfig = { @@ -64,23 +41,22 @@ describe('Render InputMultiselect with various options', () => { choices, size: 'medium', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; test('Basic options', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} - value={['val1','val2']} + value={['val1', 'val2']} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -88,38 +64,28 @@ describe('Render InputMultiselect with various options', () => { expect(conditionalFct).toHaveBeenCalledWith(interviewAttributes, 'foo.test', userAttributes); expect(translationFct).toHaveBeenCalledWith(i18next.t, interviewAttributes, 'foo.test', userAttributes); }); - + test('With shortcuts', () => { const shortcutTranslateFct = jest.fn().mockReturnValue('Translated shortcut'); - const configWithShortcuts = Object.assign({}, widgetConfig, { + const configWithShortcuts = Object.assign({}, widgetConfig, { shortcuts: [ - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png' - }, - { - value: 'conditionalVal', - label: { en: 'english conditional', fr: 'conditionnelle' }, - color: 'red' - }, - { - value: 'val3', - label: shortcutTranslateFct, - color: 'red' - }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png' }, + { value: 'conditionalVal', label: { en: 'english conditional', fr: 'conditionnelle' }, color: 'red' }, + { value: 'val3', label: shortcutTranslateFct, color: 'red' } ] }); const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={configWithShortcuts} value={['val1']} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -129,18 +95,10 @@ describe('Render InputMultiselect with various options', () => { test('With all parameters', () => { // One of the shortcuts is not a present choice conditionalFct.mockReturnValueOnce(false); - const configWithShortcuts = Object.assign({}, widgetConfig, { + const configWithShortcuts = Object.assign({}, widgetConfig, { shortcuts: [ - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png' - }, - { - value: 'conditionalVal', - label: { en: 'english conditional', fr: 'conditionnelle' }, - color: 'red' - }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png' }, + { value: 'conditionalVal', label: { en: 'english conditional', fr: 'conditionnelle' }, color: 'red' } ], multiple: true, // Should have only one value onlyLabelSearch: true, @@ -150,16 +108,17 @@ describe('Render InputMultiselect with various options', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={configWithShortcuts} value={['val1', 'val2']} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputRadio.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputRadio.test.tsx index 06291d1c6..a1491eebb 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputRadio.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputRadio.test.tsx @@ -16,55 +16,30 @@ import { shuffle } from 'chaire-lib-common/lib/utils/RandomUtils'; import InputRadio from '../InputRadio'; import i18next from 'i18next'; -jest.mock('chaire-lib-common/lib/utils/RandomUtils', () => ({ - shuffle: jest.fn() -})); +jest.mock('chaire-lib-common/lib/utils/RandomUtils', () => ({ shuffle: jest.fn() })); const shuffleMock = shuffle as jest.MockedFunction; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, pages: [], showUserInfo: true -} +}; describe('Render InputRadio with various parameter combinations, all parameters', () => { - const conditionalFct = jest.fn().mockReturnValue(true); const translationFct = jest.fn().mockReturnValue('Translated string'); const choices = [ - { - value: 'val1', - label: { en: 'english value', fr: 'valeur française' }, - hidden: false, - icon: faCrow - }, - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png' - }, - { - value: 'hiddenVal', - label: { en: 'english hidden', fr: 'cachée' }, - hidden: true - }, - { - value: 'conditionalVal', - label: { en: 'english conditional', fr: 'conditionnelle' }, - conditional: conditionalFct - }, - { - value: 'val3', - label: translationFct, - hidden: false, - color: 'green' - } + { value: 'val1', label: { en: 'english value', fr: 'valeur française' }, hidden: false, icon: faCrow }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png' }, + { value: 'hiddenVal', label: { en: 'english hidden', fr: 'cachée' }, hidden: true }, + { value: 'conditionalVal', label: { en: 'english conditional', fr: 'conditionnelle' }, conditional: conditionalFct }, + { value: 'val3', label: translationFct, hidden: false, color: 'green' } ]; const widgetConfig = { @@ -82,27 +57,26 @@ describe('Render InputRadio with various parameter combinations, all parameters' sameLine: false, customLabel: 'custom label', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; beforeEach(() => { conditionalFct.mockClear(); - }) + }); test('Includes hidden values, conditional displayed, no custom, 2 columns', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -117,13 +91,15 @@ describe('Render InputRadio with various parameter combinations, all parameters' const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={testWidgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -135,13 +111,15 @@ describe('Render InputRadio with various parameter combinations, all parameters' const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={testWidgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -154,33 +132,26 @@ describe('Render InputRadio with various parameter combinations, all parameters' const { container } = render( { /* nothing to do */ }} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={testWidgetConfig} - value='val2' + value="val2" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' - customId='foo.test.custom' - + path="foo.test" + customId="foo.test.custom" /> ); expect(container).toMatchSnapshot(); }); - }); describe('Render InputRadio with minimum parameters', () => { - const choices = [ - { - value: 'val1', - label: { en: 'english value', fr: 'valeur française' } - }, - { - value: 'val2', - label: 'Unilingual label' - } + { value: 'val1', label: { en: 'english value', fr: 'valeur française' } }, + { value: 'val2', label: 'Unilingual label' } ]; const widgetConfig = { @@ -190,23 +161,22 @@ describe('Render InputRadio with minimum parameters', () => { inputType: 'radio' as const, choices, containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; test('Minimum parameters', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -214,19 +184,9 @@ describe('Render InputRadio with minimum parameters', () => { }); describe('Render InputRadio with HTML label', () => { - const choices = [ - { - value: 'first value', - label: { - fr: '
premiere valeur
', - en: '
first value
' - } - }, - { - value: 'second value', - label: '
second value
' - } + { value: 'first value', label: { fr: '
premiere valeur
', en: '
first value
' } }, + { value: 'second value', label: '
second value
' } ]; const widgetConfig = { @@ -237,23 +197,22 @@ describe('Render InputRadio with HTML label', () => { choices, size: 'medium', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; test('HTML label', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputRadioNumber.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputRadioNumber.test.tsx index 5825df3d6..f1a39bbf5 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputRadioNumber.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputRadioNumber.test.tsx @@ -5,19 +5,11 @@ * License text available at https://opensource.org/licenses/MIT */ import React from 'react'; -import InputRadioNumber from "../InputRadioNumber"; -import {UserPermissions} from "chaire-lib-common/lib/services/user/userType"; +import InputRadioNumber from '../InputRadioNumber'; +import { UserPermissions } from 'chaire-lib-common/lib/services/user/userType'; import { render, fireEvent } from '@testing-library/react'; -const interview = { - id: 1, - uuid: "", - participant_id: 1, - is_completed: false, - response: {}, - validations: {}, - is_valid: true -}; +const interview = { id: 1, uuid: '', participant_id: 1, is_completed: false, response: {}, validations: {}, is_valid: true }; const user = { id: 1, @@ -27,39 +19,19 @@ const user = { isAuthorized: (permissions: UserPermissions) => false, is_admin: false, pages: [], - showUserInfo: false, + showUserInfo: false }; -const baseWidgetConfig = { - type: 'question' as const, - label: 'test', - path: 'test.radioNumber' -}; +const baseWidgetConfig = { type: 'question' as const, label: 'test', path: 'test.radioNumber' }; describe('Render InputRadioNumber', () => { + const widgetConfig = { ...baseWidgetConfig, inputType: 'radioNumber' as const, valueRange: { min: 1, max: 3 }, overMaxAllowed: false }; - const widgetConfig = { - ...baseWidgetConfig, - inputType: 'radioNumber' as const, - valueRange: { min: 1, max: 3 }, - overMaxAllowed: false - }; - - const widgetConfigOverMax = { - ...baseWidgetConfig, - inputType: 'radioNumber' as const, - valueRange: { min: 1, max: 3 }, - overMaxAllowed: true - }; + const widgetConfigOverMax = { ...baseWidgetConfig, inputType: 'radioNumber' as const, valueRange: { min: 1, max: 3 }, overMaxAllowed: true }; test('InputRadioNumber without "over max" option', () => { const { container } = render( - null} - interview={interview} path={''} user={user} - /> + null} interview={interview} path={''} user={user} /> ); expect(container).toMatchSnapshot(); }); @@ -70,7 +42,10 @@ describe('Render InputRadioNumber', () => { id={'test'} widgetConfig={widgetConfigOverMax} onValueChange={(e) => null} - interview={interview} path={''} user={user}/> + interview={interview} + path={''} + user={user} + /> ); expect(container).toMatchSnapshot(); }); @@ -82,7 +57,9 @@ describe('Render InputRadioNumber', () => { value={4} widgetConfig={widgetConfigOverMax} onValueChange={(e) => null} - interview={interview} path={''} user={user} + interview={interview} + path={''} + user={user} /> ); expect(container).toMatchSnapshot(); @@ -90,12 +67,7 @@ describe('Render InputRadioNumber', () => { }); describe('InputRadioNumber onChange', () => { - const widgetConfig = { - ...baseWidgetConfig, - inputType: 'radioNumber' as const, - valueRange: { min: 0, max: 3 }, - overMaxAllowed: true - }; + const widgetConfig = { ...baseWidgetConfig, inputType: 'radioNumber' as const, valueRange: { min: 0, max: 3 }, overMaxAllowed: true }; test('Test with a radio option', () => { const mockOnValueChange = jest.fn(); @@ -105,18 +77,20 @@ describe('InputRadioNumber onChange', () => { onValueChange={mockOnValueChange} widgetConfig={widgetConfig} value={3} - interview={interview} path={''} user={user} + interview={interview} + path={''} + user={user} /> ); // Find the "1" option - const option1 = queryByText("1"); + const option1 = queryByText('1'); expect(option1).toBeTruthy(); - + // Click on the option 1 fireEvent.click(option1 as any); expect(mockOnValueChange).toHaveBeenCalledTimes(1); - expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 1 }})); + expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 1 } })); }); test('Test entering the max option', () => { @@ -127,39 +101,40 @@ describe('InputRadioNumber onChange', () => { onValueChange={mockOnValueChange} widgetConfig={widgetConfig} value={3} - interview={interview} path={''} user={user} + interview={interview} + path={''} + user={user} /> ); // Find the "4+" option - const optionMax = queryByText("4+"); + const optionMax = queryByText('4+'); expect(optionMax).toBeTruthy(); - + // Click on the option button fireEvent.click(optionMax as any); expect(mockOnValueChange).toHaveBeenCalledTimes(1); - expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 4 }})); + expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 4 } })); // Find the text input - const input = queryByLabelText("SpecifyAboveLimit:"); + const input = queryByLabelText('SpecifyAboveLimit:'); expect(input).toBeTruthy(); // Enter a value in the input fireEvent.change(input as any, { target: { value: '5' } }); fireEvent.blur(input as any); expect(mockOnValueChange).toHaveBeenCalledTimes(2); - expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 5 }})); + expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 5 } })); // Reset to empty string so it appears unanswered fireEvent.change(input as any, { target: { value: '' } }); fireEvent.blur(input as any); expect(mockOnValueChange).toHaveBeenCalledTimes(3); - expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: undefined }})); + expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: undefined } })); // Find the text input - const inputAfterReset = queryByLabelText("SpecifyAboveLimit:"); + const inputAfterReset = queryByLabelText('SpecifyAboveLimit:'); expect(inputAfterReset).toBeFalsy(); - }); test('Test entering the 0 option', () => { @@ -170,23 +145,23 @@ describe('InputRadioNumber onChange', () => { onValueChange={mockOnValueChange} widgetConfig={widgetConfig} value={3} - interview={interview} path={''} user={user} + interview={interview} + path={''} + user={user} /> ); // Find the "0" option - const option0 = queryByText("0"); + const option0 = queryByText('0'); expect(option0).toBeTruthy(); - + // Click on the option 0 fireEvent.click(option0 as any); expect(mockOnValueChange).toHaveBeenCalledTimes(1); - expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 0 }})); + expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ target: { value: 0 } })); // Find the text input - const input = queryByLabelText("SpecifyAboveLimit:"); + const input = queryByLabelText('SpecifyAboveLimit:'); expect(input).toBeFalsy(); - }); - -}); \ No newline at end of file +}); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputRange.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputRange.test.tsx index 61bb0ba54..28b947f54 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputRange.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputRange.test.tsx @@ -21,7 +21,7 @@ jest.mock('react-input-range/src/js/input-range/default-class-names', () => ({ slider: 'input-range__slider', sliderContainer: 'input-range__slider-container', track: 'input-range__track input-range__track--background', - valueLabel: 'input-range__label input-range__label--value', + valueLabel: 'input-range__label input-range__label--value' })); jest.mock('react-input-range/lib/css/index.css', () => {}); @@ -29,40 +29,38 @@ jest.mock('react-input-range/lib/css/index.css', () => {}); const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, pages: [], showUserInfo: true -} +}; describe('Should correctly render InputRange with minimal parameters', () => { - const widgetConfig = { type: 'question' as const, twoColumns: true, path: 'test.foo', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - }, + label: { fr: 'Texte en français', en: 'English text' }, inputType: 'slider' as const - } + }; test('Test without value', () => { // Should have a blank style const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={undefined} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -72,49 +70,45 @@ describe('Should correctly render InputRange with minimal parameters', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={10} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); describe('Should correctly render InputRange with various parameters', () => { - const baseWidgetConfig = { type: 'question' as const, twoColumns: true, path: 'test.foo', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - }, + label: { fr: 'Texte en français', en: 'English text' }, inputType: 'slider' as const - } + }; test('Test with min max range values', () => { - const widgetConfig = Object.assign({ - maxValue: 40, - minValue: 10 - }, baseWidgetConfig); + const widgetConfig = Object.assign({ maxValue: 40, minValue: 10 }, baseWidgetConfig); const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={5} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -122,55 +116,53 @@ describe('Should correctly render InputRange with various parameters', () => { test('Test trackclass and labels', () => { const formatLabel = jest.fn().mockImplementation((val) => `l${val}`); - const widgetConfig = Object.assign({ - formatLabel, - labels: ['unilingual text', { fr: '2e label français', en: '2nd english label' }], - trackClassName: 'myTrackClass', - }, baseWidgetConfig); + const widgetConfig = Object.assign( + { formatLabel, labels: ['unilingual text', { fr: '2e label français', en: '2nd english label' }], trackClassName: 'myTrackClass' }, + baseWidgetConfig + ); const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={10} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); describe('Should correctly render InputRange with not applicable', () => { - const widgetConfig = { type: 'question' as const, twoColumns: true, path: 'test.foo', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - }, + label: { fr: 'Texte en français', en: 'English text' }, inputType: 'slider' as const, includeNotApplicable: true - } + }; test('Test without value', () => { // Should have a blank style const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={undefined} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -180,13 +172,15 @@ describe('Should correctly render InputRange with not applicable', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={10} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -196,37 +190,36 @@ describe('Should correctly render InputRange with not applicable', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={'na'} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); test('Test with custom not applicable label', () => { - const widgetConfigWithLabel = { - ...widgetConfig, - notApplicableLabel: 'Custom non applicable' - } + const widgetConfigWithLabel = { ...widgetConfig, notApplicableLabel: 'Custom non applicable' }; const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfigWithLabel} value={10} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); - diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputSelect.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputSelect.test.tsx index 40371d6a9..f2ba7c723 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputSelect.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputSelect.test.tsx @@ -14,46 +14,23 @@ import i18next from 'i18next'; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, pages: [], showUserInfo: true -} +}; test('Render InputSelect with normal option type', () => { - const conditionalFct = jest.fn().mockReturnValue(true); const translationFct = jest.fn().mockReturnValue('Translated string'); const choices = [ - { - value: 'val1', - label: { en: 'english value', fr: 'valeur française' }, - hidden: false, - icon: 'creative-commons' as const - }, - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png' - }, - { - value: 'hiddenVal', - label: { en: 'english hidden', fr: 'cachée' }, - hidden: true - }, - { - value: 'conditionalVal', - label: { en: 'english conditional', fr: 'conditionnelle' }, - conditional: conditionalFct - }, - { - value: 'val3', - label: translationFct, - hidden: false, - color: 'green' - } + { value: 'val1', label: { en: 'english value', fr: 'valeur française' }, hidden: false, icon: 'creative-commons' as const }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png' }, + { value: 'hiddenVal', label: { en: 'english hidden', fr: 'cachée' }, hidden: true }, + { value: 'conditionalVal', label: { en: 'english conditional', fr: 'conditionnelle' }, conditional: conditionalFct }, + { value: 'val3', label: translationFct, hidden: false, color: 'green' } ]; const widgetConfig = { @@ -64,55 +41,39 @@ test('Render InputSelect with normal option type', () => { choices, size: 'medium', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; - + const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); expect(conditionalFct).toHaveBeenCalledTimes(1); expect(conditionalFct).toHaveBeenCalledWith(interviewAttributes, 'foo.test', userAttributes); expect(translationFct).toHaveBeenCalledWith(i18next.t, interviewAttributes, 'foo.test', userAttributes); - }); test('Render InputSelect with choice function and grouped choice type', () => { - const choices = [ - { - value: 'val1', - label: { en: 'english value', fr: 'valeur française' }, - hidden: false, - icon: 'creative-commons' as const - }, + { value: 'val1', label: { en: 'english value', fr: 'valeur française' }, hidden: false, icon: 'creative-commons' as const }, { groupName: 'select group', groupShortname: 'sel', groupLabel: { en: 'english select label', fr: 'étiquette française' }, choices: [ - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png' - }, - { - value: 'hiddenVal', - label: { en: 'english hidden', fr: 'cachée' }, - hidden: true - }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png' }, + { value: 'hiddenVal', label: { en: 'english hidden', fr: 'cachée' }, hidden: true } ] }, { @@ -120,20 +81,13 @@ test('Render InputSelect with choice function and grouped choice type', () => { groupShortname: 'sel', groupLabel: 'Unilingual group label', choices: [ - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png' - }, - { - value: 'val3', - label: { en: 'english val3', fr: 'val3 français' } - }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png' }, + { value: 'val3', label: { en: 'english val3', fr: 'val3 français' } } ] } ]; const choiceFct = jest.fn().mockReturnValue(choices); - + const widgetConfig = { type: 'question' as const, twoColumns: true, @@ -142,54 +96,38 @@ test('Render InputSelect with choice function and grouped choice type', () => { choices: choiceFct, containsHtml: true, size: 'medium', - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; - + const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); expect(choiceFct).toHaveBeenCalledTimes(1); expect(choiceFct).toHaveBeenCalledWith(interviewAttributes, 'foo.test', userAttributes); - }); test('Render InputSelect with grouped choice type without labels', () => { - const choices = [ - { - value: 'val1', - label: { en: 'english value', fr: 'valeur française' }, - hidden: false, - icon: 'creative-commons' as const - }, + { value: 'val1', label: { en: 'english value', fr: 'valeur française' }, hidden: false, icon: 'creative-commons' as const }, { groupName: 'select group', groupShortname: 'sel', groupLabel: '', choices: [ - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png' - }, - { - value: 'hiddenVal', - label: { en: 'english hidden', fr: 'cachée' }, - hidden: true - }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png' }, + { value: 'hiddenVal', label: { en: 'english hidden', fr: 'cachée' }, hidden: true } ] }, { @@ -197,15 +135,8 @@ test('Render InputSelect with grouped choice type without labels', () => { groupShortname: 'sel', groupLabel: '', choices: [ - { - value: 'val2', - label: 'Unilingual label', - iconPath: 'img/test.png' - }, - { - value: 'val3', - label: { en: 'english val3', fr: 'val3 français' } - }, + { value: 'val2', label: 'Unilingual label', iconPath: 'img/test.png' }, + { value: 'val3', label: { en: 'english val3', fr: 'val3 français' } } ] } ]; @@ -218,24 +149,22 @@ test('Render InputSelect with grouped choice type without labels', () => { choices, containsHtml: true, size: 'medium', - label: { - fr: `Texte en français`, - en: `English text` - } + label: { fr: 'Texte en français', en: 'English text' } }; const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); - }); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputString.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputString.test.tsx index 0b77b19ef..dd25550bf 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputString.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputString.test.tsx @@ -14,16 +14,15 @@ import { interviewAttributes } from './interviewData'; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, pages: [], showUserInfo: true -} - -test('Should correctly render InputString with all parameters', () =>{ +}; +test('Should correctly render InputString with all parameters', () => { const widgetConfig = { type: 'question' as const, twoColumns: true, @@ -37,16 +36,15 @@ test('Should correctly render InputString with all parameters', () =>{ placeholder: 'example placeholder' as const, containsHtml: true, size: 'medium' as const, - label: { - fr: `Texte en français`, - en: `English text` - } - } + label: { fr: 'Texte en français', en: 'English text' } + }; const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={undefined} inputRef={React.createRef()} @@ -59,8 +57,7 @@ test('Should correctly render InputString with all parameters', () =>{ expect(container).toMatchSnapshot(); }); -test('Should correctly render InputString with default value as function', () =>{ - +test('Should correctly render InputString with default value as function', () => { const widgetConfig = { type: 'question' as const, twoColumns: true, @@ -71,16 +68,15 @@ test('Should correctly render InputString with default value as function', () => size: 'medium' as const, defaultValue: jest.fn().mockReturnValue('fctDefault'), containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - } - } + label: { fr: 'Texte en français', en: 'English text' } + }; const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={undefined} inputRef={React.createRef()} @@ -94,22 +90,18 @@ test('Should correctly render InputString with default value as function', () => expect(widgetConfig.defaultValue).toHaveBeenCalledWith(interviewAttributes, 'path', userAttributes); }); -test('Should correctly render InputString with base parameters', () =>{ - +test('Should correctly render InputString with base parameters', () => { const widgetConfig = { type: 'question' as const, path: 'test.foo', inputType: 'string' as const, - label: { - fr: `Texte en français`, - en: `English text` - } - } + label: { fr: 'Texte en français', en: 'English text' } + }; const { container } = render( { expect(container).toMatchSnapshot(); }); -test('Should correctly render InputString with a value of 0', () =>{ - +test('Should correctly render InputString with a value of 0', () => { const widgetConfig = { type: 'question' as const, path: 'test.foo', inputType: 'string' as const, - label: { - fr: `Texte en français`, - en: `English text` - } - } + label: { fr: 'Texte en français', en: 'English text' } + }; const { container } = render( { type: 'question' as const, path: 'test.foo', inputType: 'string' as const, - label: { - fr: `Texte en français`, - en: `English text` - } - } + label: { fr: 'Texte en français', en: 'English text' } + }; const testId = 'test'; const onValueChangeMock = jest.fn(); - const { rerender } = render(); + const { rerender } = render( + + ); // Validate initial values expect(screen.getByRole('textbox')).toHaveValue('value'); @@ -188,7 +175,7 @@ test('Test update value through props', () => { user: userAttributes, onValueChange: onValueChangeMock }; - rerender(); + rerender(); expect(screen.getByRole('textbox')).toHaveValue(newValue); // Change value through props and change updateKey, should be updated @@ -203,6 +190,6 @@ test('Test update value through props', () => { user: userAttributes, onValueChange: onValueChangeMock }; - rerender(); + rerender(); expect(screen.getByRole('textbox')).toHaveValue(updateByServerVal); }); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/InputText.test.tsx b/packages/evolution-frontend/src/components/inputs/__tests__/InputText.test.tsx index f2bac5ad0..fe7e81af9 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/InputText.test.tsx +++ b/packages/evolution-frontend/src/components/inputs/__tests__/InputText.test.tsx @@ -13,16 +13,15 @@ import { InputText } from '../InputText'; const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, pages: [], showUserInfo: true -} - -test('Should correctly render InputText with all parameters', () =>{ +}; +test('Should correctly render InputText with all parameters', () => { const widgetConfig = { type: 'question' as const, twoColumns: true, @@ -35,18 +34,17 @@ test('Should correctly render InputText with all parameters', () =>{ rows: 10, size: 'medium' as const, containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - } - } + label: { fr: 'Texte en français', en: 'English text' } + }; const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} - value='value' + value="value" inputRef={React.createRef()} interview={interviewAttributes} path={'path'} @@ -56,22 +54,18 @@ test('Should correctly render InputText with all parameters', () =>{ expect(container).toMatchSnapshot(); }); -test('Should correctly render InputText with base parameters', () =>{ - +test('Should correctly render InputText with base parameters', () => { const widgetConfig = { type: 'question' as const, path: 'test.foo', inputType: 'text' as const, - label: { - fr: `Texte en français`, - en: `English text` - } - } + label: { fr: 'Texte en français', en: 'English text' } + }; const { container } = render( { expect(container).toMatchSnapshot(); }); -test('Should correctly render InputText with defaultValue', () =>{ - +test('Should correctly render InputText with defaultValue', () => { const widgetConfig = { type: 'question' as const, path: 'test.foo', inputType: 'text' as const, defaultValue: 'This is the text that should be present', - label: { - fr: `Texte en français`, - en: `English text` - } - } + label: { fr: 'Texte en français', en: 'English text' } + }; const { container } = render( true, is_admin: false, pages: [], showUserInfo: true -} +}; describe('Should correctly render InputTime with minimal parameters', () => { - const widgetConfig = { type: 'question' as const, twoColumns: true, datatype: 'integer' as const, path: 'test.foo', containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - }, + label: { fr: 'Texte en français', en: 'English text' }, inputType: 'time' as const - } + }; test('Test without value', () => { // Should have all times, at 5 minutes interval const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={undefined} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -59,51 +57,50 @@ describe('Should correctly render InputTime with minimal parameters', () => { const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={value} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); }); - }); describe('Should correctly render InputTime with various parameters', () => { - const baseWidgetConfig = { type: 'question' as const, twoColumns: true, path: 'test.foo', datatype: 'integer' as const, containsHtml: true, - label: { - fr: `Texte en français`, - en: `English text` - }, + label: { fr: 'Texte en français', en: 'English text' }, inputType: 'time' as const - } + }; test('Test with min max times values', () => { // Should times between 10 and 12. - const widgetConfig = Object.assign({ - minTimeSecondsSinceMidnight: 10 * 60 * 60, - maxTimeSecondsSinceMidnight: 12 * 60 * 60 - }, baseWidgetConfig); + const widgetConfig = Object.assign( + { minTimeSecondsSinceMidnight: 10 * 60 * 60, maxTimeSecondsSinceMidnight: 12 * 60 * 60 }, + baseWidgetConfig + ); const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={undefined} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -114,21 +111,22 @@ describe('Should correctly render InputTime with various parameters', () => { const maxTimesFct = jest.fn().mockReturnValue(12 * 60 * 60); const stepFct = jest.fn().mockReturnValue(10); // Should display times between 10 and 12 in 10 minutes increments - const widgetConfig = Object.assign({ - minTimeSecondsSinceMidnight: minTimesFct, - maxTimeSecondsSinceMidnight: maxTimesFct, - minuteStep: stepFct - }, baseWidgetConfig); + const widgetConfig = Object.assign( + { minTimeSecondsSinceMidnight: minTimesFct, maxTimeSecondsSinceMidnight: maxTimesFct, minuteStep: stepFct }, + baseWidgetConfig + ); const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={undefined} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); @@ -142,26 +140,21 @@ describe('Should correctly render InputTime with various parameters', () => { test('Test with suffixTimes, minutes steps and hour separator', () => { // Add a suffix at 10 and 13 - const suffixTimeFct = jest.fn().mockReturnValue({ - [(10 * 60 * 60).toString()]: ' 10 suffix', - [(13 * 60 * 60).toString()]: ' 13 suffix' - }); + const suffixTimeFct = jest.fn().mockReturnValue({ [(10 * 60 * 60).toString()]: ' 10 suffix', [(13 * 60 * 60).toString()]: ' 13 suffix' }); // have all times, at 15 minutes interval, separator between hours and some suffixes - const widgetConfig = Object.assign({ - minuteStep: 15, - suffixTimes: suffixTimeFct, - addHourSeparators: true - }, baseWidgetConfig); + const widgetConfig = Object.assign({ minuteStep: 15, suffixTimes: suffixTimeFct, addHourSeparators: true }, baseWidgetConfig); const { container } = render( { /* nothing to do */}} + onValueChange={() => { + /* nothing to do */ + }} widgetConfig={widgetConfig} value={undefined} inputRef={React.createRef()} interview={interviewAttributes} user={userAttributes} - path='foo.test' + path="foo.test" /> ); expect(container).toMatchSnapshot(); diff --git a/packages/evolution-frontend/src/components/inputs/__tests__/interviewData.ts b/packages/evolution-frontend/src/components/inputs/__tests__/interviewData.ts index 281c5e4d4..2311a0db1 100644 --- a/packages/evolution-frontend/src/components/inputs/__tests__/interviewData.ts +++ b/packages/evolution-frontend/src/components/inputs/__tests__/interviewData.ts @@ -5,24 +5,8 @@ export const interviewAttributes: UserInterviewAttributes = { uuid: 'arbitrary uuid', participant_id: 1, is_completed: false, - response: { - section1: { - q1: 'abc', - q2: 3 - }, - section2: { - q1: 'test' - } - } as any, - validations: { - section1: { - q1: true, - q2: false - }, - section2: { - q1: true - } - } as any, + response: { section1: { q1: 'abc', q2: 3 }, section2: { q1: 'test' } } as any, + validations: { section1: { q1: true, q2: false }, section2: { q1: true } } as any, is_valid: true }; @@ -45,4 +29,4 @@ export const surveyContext = { personVisitedPlacesMap: {}, buttonConfirmNextSection: {} } -}; \ No newline at end of file +}; diff --git a/packages/evolution-frontend/src/components/modal/__tests__/SimpleModal.test.tsx b/packages/evolution-frontend/src/components/modal/__tests__/SimpleModal.test.tsx index d4345ae4d..ecd738da5 100644 --- a/packages/evolution-frontend/src/components/modal/__tests__/SimpleModal.test.tsx +++ b/packages/evolution-frontend/src/components/modal/__tests__/SimpleModal.test.tsx @@ -14,27 +14,20 @@ jest.mock('remark-gfm', () => 'remark-gfm'); jest.mock('react-i18next', () => ({ // this mock makes sure any components using the translate HoC receive the t function as a prop - withTranslation: () => Component => { + withTranslation: () => (Component) => { Component.defaultProps = { ...Component.defaultProps, t: (key) => key }; return Component; - }, + } })); -test('Test simple modal with action on close', () =>{ +test('Test simple modal with action on close', () => { const handleClose = jest.fn(); const action = jest.fn(); const title = 'Simple modal title'; const baseText = 'Text in the simple modal'; const text = `${baseText} bold`; const { queryByText } = render( - + ); // Html was added, so the complete text is not there, it is actually composed of many texts expect(queryByText(baseText)).toBeTruthy(); @@ -49,22 +42,15 @@ test('Test simple modal with action on close', () =>{ expect(action).toHaveBeenCalledTimes(1); }); -test('Test simple modal with minimal parameters', () =>{ +test('Test simple modal with minimal parameters', () => { const handleClose = jest.fn(); const text = 'Text in the simple modal bold'; const title = 'Simple modal title'; - const { getByText } = render( - - ); + const { getByText } = render(); expect(getByText(text)).toBeTruthy(); expect(getByText(title)).toBeTruthy(); // Click on the close button fireEvent.click(getByText(/main:Ok/i)); expect(handleClose).toHaveBeenCalledTimes(1); -}); \ No newline at end of file +}); diff --git a/packages/evolution-frontend/src/components/pageParts/__tests__/ConsentAndStartForm.test.tsx b/packages/evolution-frontend/src/components/pageParts/__tests__/ConsentAndStartForm.test.tsx index 26c4361ce..4f5039a7c 100644 --- a/packages/evolution-frontend/src/components/pageParts/__tests__/ConsentAndStartForm.test.tsx +++ b/packages/evolution-frontend/src/components/pageParts/__tests__/ConsentAndStartForm.test.tsx @@ -19,36 +19,29 @@ let translatedString = ''; jest.mock('react-i18next', () => ({ ...jest.requireActual('react-i18next'), useTranslation: () => ({ - t: (str: string | string[]) => typeof str === 'string' ? str : str.findIndex((s) => s.includes('AgreementText')) !== -1 ? translatedString : str[0] + t: (str: string | string[]) => + typeof str === 'string' ? str : str.findIndex((s) => s.includes('AgreementText')) !== -1 ? translatedString : str[0] }) })); let store = configureStore(); jest.mock('../../../actions/Survey', () => ({ - addConsent: jest.fn().mockImplementation((consented: boolean) => ({ - type: 'ADD_CONSENT', - consented - })) + addConsent: jest.fn().mockImplementation((consented: boolean) => ({ type: 'ADD_CONSENT', consented })) })); const mockAddConsent = addConsent as jest.MockedFunction; - beforeEach(() => { mockAddConsent.mockClear(); store = configureStore(); }); describe('Render ConsentAndStartForm', () => { - test('Without consent checkbox', () => { translatedString = ''; const { container } = render( - + - ); expect(container).toMatchSnapshot(); }); @@ -57,32 +50,24 @@ describe('Render ConsentAndStartForm', () => { translatedString = 'I agree'; const { container } = render( - + ); expect(container).toMatchSnapshot(); }); - }); describe('Button click', () => { - test('With consent true', async () => { - store = configureStore({ - survey: { - hasConsent: true, - } - } as any); + store = configureStore({ survey: { hasConsent: true } } as any); const afterClick = jest.fn(); translatedString = ''; - render( - - ); + render( + + + + ); const user = userEvent.setup(); // Click on button and make sure it accepts the change @@ -94,19 +79,15 @@ describe('Button click', () => { }); test('With consent false', async () => { - store = configureStore({ - survey: { - hasConsent: false, - } - } as any); + store = configureStore({ survey: { hasConsent: false } } as any); const afterClick = jest.fn(); translatedString = 'I agree'; - const { container } = render( - - ); + const { container } = render( + + + + ); const user = userEvent.setup(); // Click on button, it should not agree @@ -118,20 +99,18 @@ describe('Button click', () => { const errorElement = container.querySelectorAll('.apptr__form-errors-container'); expect(errorElement.length).toBe(1); }); - }); describe('State update', () => { - test('No consent', () => { // no consent box, the consent should be set automatically const afterClick = jest.fn(); translatedString = ''; - render( - - ); + render( + + + + ); // the addConsent survey action should have been called expect(mockAddConsent).toHaveBeenCalledTimes(1); @@ -139,18 +118,14 @@ describe('State update', () => { }); test('With consent, not initially checked, check the box', async () => { - store = configureStore({ - survey: { - hasConsent: false, - } - } as any); + store = configureStore({ survey: { hasConsent: false } } as any); const afterClick = jest.fn(); translatedString = 'I agree'; - render( - - ); + render( + + + + ); const user = userEvent.setup(); // the addConsent survey action should not have been called yet @@ -166,19 +141,15 @@ describe('State update', () => { }); test('With consent, initially checked, then unchecked', async () => { - store = configureStore({ - survey: { - hasConsent: true, - } - } as any); + store = configureStore({ survey: { hasConsent: true } } as any); const afterClick = jest.fn(); translatedString = 'I agree'; - render( - - ); + render( + + + + ); const user = userEvent.setup(); // the addConsent survey action should not have been called yet @@ -192,5 +163,4 @@ describe('State update', () => { expect(mockAddConsent).toHaveBeenCalledTimes(1); expect(mockAddConsent).toHaveBeenCalledWith(false); }); - }); diff --git a/packages/evolution-frontend/src/components/routers/__tests__/ParticipantSurveyRouter.test.ts b/packages/evolution-frontend/src/components/routers/__tests__/ParticipantSurveyRouter.test.ts index 7f7f46c07..c4a5de78c 100644 --- a/packages/evolution-frontend/src/components/routers/__tests__/ParticipantSurveyRouter.test.ts +++ b/packages/evolution-frontend/src/components/routers/__tests__/ParticipantSurveyRouter.test.ts @@ -10,10 +10,7 @@ import { render } from '@testing-library/react'; import getParticipantSurveyRoutes from '../ParticipantSurveyRouter'; // Mock the components since we're testing route configuration, not component rendering -jest.mock('../ParticipantRootLayout', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'ParticipantRootLayout') -})); +jest.mock('../ParticipantRootLayout', () => ({ __esModule: true, default: () => React.createElement('div', null, 'ParticipantRootLayout') })); jest.mock('chaire-lib-frontend/lib/components/routers/PrivateRoute', () => ({ __esModule: true, @@ -25,41 +22,23 @@ jest.mock('chaire-lib-frontend/lib/components/routers/PublicRoute', () => ({ default: () => React.createElement('div', null, 'PublicRoute') })); -jest.mock('../ConsentedRoute', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'ConsentedRoute') -})); +jest.mock('../ConsentedRoute', () => ({ __esModule: true, default: () => React.createElement('div', null, 'ConsentedRoute') })); // Mock page components -jest.mock('../../pages/HomePage', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'HomePage') -})); +jest.mock('../../pages/HomePage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'HomePage') })); -jest.mock('../../pages/UnauthorizedPage', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'UnauthorizedPage') -})); +jest.mock('../../pages/UnauthorizedPage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'UnauthorizedPage') })); -jest.mock('../../pages/SurveyErrorPage', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'SurveyErrorPage') -})); +jest.mock('../../pages/SurveyErrorPage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'SurveyErrorPage') })); -jest.mock('../../pages/auth/AuthPage', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'AuthPage') -})); +jest.mock('../../pages/auth/AuthPage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'AuthPage') })); jest.mock('../../pages/SurveyUnavailablePage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'SurveyUnavailablePage') })); -jest.mock('../../pages/NotFoundPage', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'NotFoundPage') -})); +jest.mock('../../pages/NotFoundPage', () => ({ __esModule: true, default: () => React.createElement('div', null, 'NotFoundPage') })); jest.mock('chaire-lib-frontend/lib/components/forms/auth/passwordless/MagicLinkVerify', () => ({ __esModule: true, @@ -71,15 +50,10 @@ jest.mock('chaire-lib-frontend/lib/components/forms/auth/passwordless/CheckMagic default: () => React.createElement('div', null, 'CheckMagicEmailPage') })); -jest.mock('../../hoc/SurveyWithErrorBoundary', () => ({ - __esModule: true, - default: () => React.createElement('div', null, 'Survey') -})); +jest.mock('../../hoc/SurveyWithErrorBoundary', () => ({ __esModule: true, default: () => React.createElement('div', null, 'Survey') })); // Mock the setShowUserInfoPerm action -jest.mock('chaire-lib-frontend/lib/actions/Auth', () => ({ - setShowUserInfoPerm: jest.fn() -})); +jest.mock('chaire-lib-frontend/lib/actions/Auth', () => ({ setShowUserInfoPerm: jest.fn() })); describe('getParticipantSurveyRoutes', () => { let routes: RouteObject[]; @@ -122,7 +96,7 @@ describe('getParticipantSurveyRoutes', () => { { path: '/*', wrapperType: 'PublicRoute' }, { path: '/unavailable', wrapperType: 'PrivateRoute' }, { path: '/survey/:sectionShortname', wrapperType: 'PrivateRoute' }, - { path: '/survey', wrapperType: 'PrivateRoute' }, + { path: '/survey', wrapperType: 'PrivateRoute' } ]; it('Should include all expected routes with correct total count', () => { @@ -131,32 +105,29 @@ describe('getParticipantSurveyRoutes', () => { expect(children.length).toBe(expectedRoutes.length); }); - it.each(expectedRoutes)( - 'Should have route $path with correct path and $wrapperType wrapper', - ({ path, wrapperType }) => { - const rootRoute = routes[0]; - const children = rootRoute.children || []; - - const getWrapperType = (pathToCheck: string): string => { - const route = children.find((child) => child.path === pathToCheck); - if (!route || !route.element) return 'Unknown'; - - // Render the element and check the output - const { container, unmount } = render(route.element as React.ReactElement); - const textContent = container.textContent || ''; - - // Clean up the render before returning to avoid memory leaks - unmount(); - - if (textContent.includes('ConsentedRoute')) return 'ConsentedRoute'; - if (textContent.includes('PrivateRoute')) return 'PrivateRoute'; - if (textContent.includes('PublicRoute')) return 'PublicRoute'; - return 'Unknown'; - }; - - const paths = children.map((child) => child.path); - expect(paths).toContain(path); - expect(getWrapperType(path)).toBe(wrapperType); - } - ); + it.each(expectedRoutes)('Should have route $path with correct path and $wrapperType wrapper', ({ path, wrapperType }) => { + const rootRoute = routes[0]; + const children = rootRoute.children || []; + + const getWrapperType = (pathToCheck: string): string => { + const route = children.find((child) => child.path === pathToCheck); + if (!route || !route.element) return 'Unknown'; + + // Render the element and check the output + const { container, unmount } = render(route.element as React.ReactElement); + const textContent = container.textContent || ''; + + // Clean up the render before returning to avoid memory leaks + unmount(); + + if (textContent.includes('ConsentedRoute')) return 'ConsentedRoute'; + if (textContent.includes('PrivateRoute')) return 'PrivateRoute'; + if (textContent.includes('PublicRoute')) return 'PublicRoute'; + return 'Unknown'; + }; + + const paths = children.map((child) => child.path); + expect(paths).toContain(path); + expect(getWrapperType(path)).toBe(wrapperType); + }); }); diff --git a/packages/evolution-frontend/src/components/survey/__tests__/Button.test.tsx b/packages/evolution-frontend/src/components/survey/__tests__/Button.test.tsx index 592df91b4..f1cdece12 100644 --- a/packages/evolution-frontend/src/components/survey/__tests__/Button.test.tsx +++ b/packages/evolution-frontend/src/components/survey/__tests__/Button.test.tsx @@ -26,7 +26,7 @@ jest.mock('remark-gfm', () => 'remark-gfm'); const userAttributes = { id: 1, username: 'foo', - preferences: { }, + preferences: {}, serializedPermissions: [], isAuthorized: () => true, is_admin: false, @@ -34,11 +34,7 @@ const userAttributes = { showUserInfo: true }; -const commonWidgetConfig: ButtonWidgetConfig = { - type: 'button' as const, - label: 'label', - action: jest.fn() -}; +const commonWidgetConfig: ButtonWidgetConfig = { type: 'button' as const, label: 'label', action: jest.fn() }; const defaultWidgetStatus: WidgetStatus = { path: 'foo', @@ -66,29 +62,27 @@ beforeEach(() => { each([ ['Default values', commonWidgetConfig], - ['All values set', { - ...commonWidgetConfig, - label: 'newLabel', - hideWhenRefreshing: true, - containsHtml: true, - color: 'red', - iconPath: 'path/to/somewhere', - align: 'right', - saveCallback: jest.fn(), - confirmPopup: { - title: 'popupTitle', - content: 'popupContent', - }, - size: 'small', - conditional: jest.fn() - }], + [ + 'All values set', + { + ...commonWidgetConfig, + label: 'newLabel', + hideWhenRefreshing: true, + containsHtml: true, + color: 'red', + iconPath: 'path/to/somewhere', + align: 'right', + saveCallback: jest.fn(), + confirmPopup: { title: 'popupTitle', content: 'popupContent' }, + size: 'small', + conditional: jest.fn() + } + ] ]).describe('Button widget: %s', (_widget, widgetConfig) => { - test('Render widget', () => { - const { container } = render(