From 15bd199eff7468b23acae5e39b2245ce4b4a5220 Mon Sep 17 00:00:00 2001 From: Joshua Kuestersteffen Date: Wed, 22 Nov 2023 15:25:21 -0600 Subject: [PATCH] feat(#7462) add cht-form web component for standalone Enketo form support (#8153) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mokhtar Co-authored-by: Tatiana Lépiz <94494491+tatilepizs@users.noreply.github.com> Co-authored-by: tatilepizs --- .github/workflows/build.yml | 19 +- package.json | 4 +- scripts/build/build-ci.sh | 4 + .../default/death-report.wdio-spec.js | 60 ++ .../cht-form/default/delivery.wdio-spec.js | 139 ++++ .../default/enketo-widgets.wdio-spec.js | 99 +++ .../default/forms/death_report_es.xml | 458 ++++++++++ .../cht-form/default/forms/enketo_widgets.xml | 491 +++++++++++ .../default/person-create.wdio-spec.js | 36 + .../cht-form/default/person-edit.wdio-spec.js | 81 ++ ...nc-danger-sign-follow-up-baby.wdio-spec.js | 50 ++ ...-danger-sign-follow-up-mother.wdio-spec.js | 41 + ...egnancy-danger-sign-follow-up.wdio-spec.js | 40 + .../pregnancy-danger-sign.wdio-spec.js | 53 ++ ...nancy-facility-visit-reminder.wdio-spec.js | 41 + .../default/pregnancy-visit.wdio-spec.js | 71 ++ .../cht-form/default/pregnancy.wdio-spec.js | 84 ++ .../default/replace-user.wdio-spec.js | 47 ++ .../default/undo-death-report.wdio-spec.js | 30 + tests/e2e/cht-form/index.html | 13 + tests/e2e/cht-form/mock-config.js | 67 ++ .../child-health-registration.wdio-spec.js | 53 ++ .../standard/death-confirmation.wdio-spec.js | 52 ++ .../cht-form/standard/delivery.wdio-spec.js | 62 ++ .../health-center-create.wdio-spec.js | 43 + .../standard/health-center-edit.wdio-spec.js | 51 ++ .../standard/immunization-visit.wdio-spec.js | 130 +++ .../standard/postnatal-visit.wdio-spec.js | 62 ++ .../standard/pregnancy-visit.wdio-spec.js | 51 ++ .../cht-form/standard/pregnancy.wdio-spec.js | 61 ++ tests/e2e/cht-form/wdio.conf.js | 56 ++ ...gnancy-danger-sign-follow-up.wdio-spec.js} | 32 +- ...nancy-facility-visit-reminder.wdio-spec.js | 2 +- .../e2e/default/enketo/pregnancy.wdio-spec.js | 39 +- .../e2e/standard/enketo/delivery.wdio-spec.js | 12 +- .../enketo/immunization-visit.wdio-spec.js | 17 +- .../enketo/pregnancy-visit.wdio-spec.js | 10 +- .../standard/enketo/pregnancy.wdio-spec.js | 12 +- tests/e2e/wdio.conf.js | 2 +- .../default/common/common.wdio.page.js | 1 - .../default/contacts/contacts.wdio.page.js | 55 +- .../default/enketo/danger-sign.wdio.page.js | 47 ++ .../default/enketo/death-report.page.js | 25 +- .../default/enketo/delivery.wdio.page.js | 164 ++-- .../enketo/enketo-widgets.wdio.page.js | 30 +- .../default/enketo/generic-form.wdio.page.js | 8 + ...gnancy-danger-sign-follow-up.wdio.page.js} | 0 ...nancy-facility-visit-reminder.wdio.page.js | 20 +- .../enketo/pregnancy-visit.wdio.page.js | 48 +- .../default/enketo/pregnancy.wdio.page.js | 112 ++- .../default/enketo/replace-user.wdio.page.js | 12 +- .../default/enketo/undo-death-report.page.js | 13 +- .../default/sms/messages.wdio.page.js | 2 +- .../standard/contacts/contacts.wdio.page.js | 26 +- .../child-health-registration.wdio.page.js | 51 ++ .../enketo/death-confirmation.wdio.page.js | 57 ++ .../standard/enketo/delivery.wdio.page.js | 75 +- .../standard/enketo/enketo.wdio.page.js | 14 +- .../enketo/immunization-visit.wdio.page.js | 45 +- .../enketo/postnatal-visit.wdio.page.js | 75 ++ .../enketo/pregnancy-visit.wdio.page.js | 32 +- .../standard/enketo/pregnancy.wdio.page.js | 56 +- webapp/angular.json | 133 ++- webapp/package-lock.json | 24 + webapp/package.json | 3 + .../contacts/contacts-edit.component.ts | 9 +- .../src/ts/services/contact-save.service.ts | 84 +- webapp/src/ts/services/form.service.ts | 49 +- webapp/tests/.eslintrc | 2 +- webapp/tests/karma/karma-unit.base.conf.js | 45 + webapp/tests/karma/karma-unit.conf.js | 48 +- .../contacts/contacts-edit.component.spec.ts | 31 +- .../ts/services/contact-save.service.spec.ts | 120 +-- .../karma/ts/services/form.service.spec.ts | 293 ++++++- webapp/web-components/cht-form/README.md | 103 +++ webapp/web-components/cht-form/package.json | 15 + .../cht-form/src/app.component.html | 29 + .../cht-form/src/app.component.ts | 246 ++++++ .../web-components/cht-form/src/app.module.ts | 51 ++ webapp/web-components/cht-form/src/index.html | 98 +++ webapp/web-components/cht-form/src/main.ts | 32 + .../cht-form/src/stubs/db.service.ts | 23 + .../web-components/cht-form/tests/.eslintrc | 14 + .../tests/karma/app.component.spec.ts | 786 ++++++++++++++++++ .../cht-form/tests/karma/karma-unit.conf.js | 16 + .../web-components/cht-form/tsconfig.app.json | 21 + .../cht-form/tsconfig.spec.json | 28 + 87 files changed, 5337 insertions(+), 609 deletions(-) create mode 100644 tests/e2e/cht-form/default/death-report.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/delivery.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/enketo-widgets.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/forms/death_report_es.xml create mode 100644 tests/e2e/cht-form/default/forms/enketo_widgets.xml create mode 100644 tests/e2e/cht-form/default/person-create.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/person-edit.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/pnc-danger-sign-follow-up-baby.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/pnc-danger-sign-follow-up-mother.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/pregnancy-danger-sign-follow-up.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/pregnancy-danger-sign.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/pregnancy-facility-visit-reminder.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/pregnancy-visit.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/pregnancy.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/replace-user.wdio-spec.js create mode 100644 tests/e2e/cht-form/default/undo-death-report.wdio-spec.js create mode 100644 tests/e2e/cht-form/index.html create mode 100644 tests/e2e/cht-form/mock-config.js create mode 100644 tests/e2e/cht-form/standard/child-health-registration.wdio-spec.js create mode 100644 tests/e2e/cht-form/standard/death-confirmation.wdio-spec.js create mode 100644 tests/e2e/cht-form/standard/delivery.wdio-spec.js create mode 100644 tests/e2e/cht-form/standard/health-center-create.wdio-spec.js create mode 100644 tests/e2e/cht-form/standard/health-center-edit.wdio-spec.js create mode 100644 tests/e2e/cht-form/standard/immunization-visit.wdio-spec.js create mode 100644 tests/e2e/cht-form/standard/postnatal-visit.wdio-spec.js create mode 100644 tests/e2e/cht-form/standard/pregnancy-visit.wdio-spec.js create mode 100644 tests/e2e/cht-form/standard/pregnancy.wdio-spec.js create mode 100644 tests/e2e/cht-form/wdio.conf.js rename tests/e2e/default/enketo/{pregnancy-danger-sign.wdio-spec.js => pregnancy-danger-sign-follow-up.wdio-spec.js} (72%) create mode 100644 tests/page-objects/default/enketo/danger-sign.wdio.page.js rename tests/page-objects/default/enketo/{pregnancy-danger-sign.wdio.page.js => pregnancy-danger-sign-follow-up.wdio.page.js} (100%) create mode 100644 tests/page-objects/standard/enketo/child-health-registration.wdio.page.js create mode 100644 tests/page-objects/standard/enketo/death-confirmation.wdio.page.js create mode 100644 tests/page-objects/standard/enketo/postnatal-visit.wdio.page.js create mode 100644 webapp/tests/karma/karma-unit.base.conf.js create mode 100644 webapp/web-components/cht-form/README.md create mode 100644 webapp/web-components/cht-form/package.json create mode 100644 webapp/web-components/cht-form/src/app.component.html create mode 100644 webapp/web-components/cht-form/src/app.component.ts create mode 100644 webapp/web-components/cht-form/src/app.module.ts create mode 100644 webapp/web-components/cht-form/src/index.html create mode 100644 webapp/web-components/cht-form/src/main.ts create mode 100644 webapp/web-components/cht-form/src/stubs/db.service.ts create mode 100644 webapp/web-components/cht-form/tests/.eslintrc create mode 100644 webapp/web-components/cht-form/tests/karma/app.component.spec.ts create mode 100644 webapp/web-components/cht-form/tests/karma/karma-unit.conf.js create mode 100644 webapp/web-components/cht-form/tsconfig.app.json create mode 100755 webapp/web-components/cht-form/tsconfig.spec.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3b11a6f4c31..45bd9821f38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -91,6 +91,23 @@ jobs: - name: Run Tests run: npm run ${{ matrix.cmd }} + test-cht-form: + needs: build + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 16.x + uses: actions/setup-node@v3 + with: + node-version: 16.x + - run: sudo apt-get install -y xsltproc + - run: npm ci + - name: Build cht-form Web Component + run: npm run build-cht-form + - name: Run Tests + run: npm run wdio-cht-form + tests: needs: build name: ${{ matrix.cmd }}-${{ matrix.suite || '' }}${{ matrix.chrome-version == '90' && '-minimum-browser' || '' }} @@ -247,7 +264,7 @@ jobs: if: ${{ failure() }} publish: - needs: [tests, config-tests] + needs: [tests, config-tests, test-cht-form] name: Publish branch build runs-on: ubuntu-22.04 if: ${{ github.event_name != 'pull_request' }} diff --git a/package.json b/package.json index 1451b67f07e..5e1d01dea94 100755 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "build-dev-watch": "npm run build-dev && cd webapp && npm run build -- --configuration=development --watch=true & node ./scripts/build/watch.js", "build-documentation": "./scripts/build/build-documentation.sh", "build-webapp-dev": "cd webapp && npm run build -- --configuration=development && npm run compile", + "build-cht-form": "./scripts/build/build-prepare.sh && cd webapp && npm run build:cht-form", "copy-api-resources": "cp -r api/src/public/* api/build/static/", "dev-api": "./scripts/build/copy-static-files.sh && TZ=UTC nodemon --inspect=0.0.0.0:9229 --ignore 'api/build/static' --ignore 'api/build/public' --watch api --watch 'shared-libs/**/src/**' api/server.js -- --allow-cors", "dev-sentinel": "TZ=UTC nodemon --inspect=0.0.0.0:9228 --watch sentinel --watch 'shared-libs/**/src/**' sentinel/server.js", @@ -40,11 +41,12 @@ "unit-api": "UNIT_TEST_ENV=1 mocha 'api/tests/mocha/**/*.js'", "unit-sentinel": "UNIT_TEST_ENV=1 mocha 'sentinel/tests/**/*.js'", "unit-shared-lib": "UNIT_TEST_ENV=1 npm test --workspaces --if-present", - "unit-webapp": "UNIT_TEST_ENV=1 mocha 'webapp/tests/mocha/**/*.spec.js' && cd webapp && npm run unit -- --watch=false", + "unit-webapp": "UNIT_TEST_ENV=1 mocha 'webapp/tests/mocha/**/*.spec.js' && cd webapp && npm run unit -- --watch=false && npm run unit:cht-form -- --watch=false", "unit-webapp-continuous": "cd webapp && npm run unit -- --watch=true", "unit-nginx": "cd nginx/tests && make test", "unit-haproxy": "cd haproxy/tests && make test", "wdio-local": "export VERSION=$(node ./scripts/build/get-version.js) && ./scripts/build/build-service-images.sh && wdio run ./tests/e2e/default/wdio.conf.js", + "wdio-cht-form": "wdio run ./tests/e2e/cht-form/wdio.conf.js", "-- CI SCRIPTS ": "-----------------------------------------------------------------------------------------------", "build": "./scripts/build/build-ci.sh", "ci-compile": "node scripts/ci/check-versions.js && node scripts/build/cli npmCiModules && npm run lint && npm run build && npm run integration-api && npm run unit && npm run unit-nginx && npm run unit-haproxy", diff --git a/scripts/build/build-ci.sh b/scripts/build/build-ci.sh index 204726e9fce..55c9a071e02 100755 --- a/scripts/build/build-ci.sh +++ b/scripts/build/build-ci.sh @@ -14,6 +14,10 @@ echo "build-ci: building webapp" cd webapp npm run build -- --configuration=production npm run compile + +echo "build-ci: building cht-form" +npm run build:cht-form -- --configuration=production + cd .. node ./scripts/build/cli createStagingDoc diff --git a/tests/e2e/cht-form/default/death-report.wdio-spec.js b/tests/e2e/cht-form/default/death-report.wdio-spec.js new file mode 100644 index 00000000000..72b200dafb6 --- /dev/null +++ b/tests/e2e/cht-form/default/death-report.wdio-spec.js @@ -0,0 +1,60 @@ +const mockConfig = require('../mock-config'); +const moment = require('moment'); +const deathReportForm = require('@page-objects/default/enketo/death-report.page'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); + +describe('cht-form web component - Death Report Form', () => { + + it('should submit a death report', async () => { + await mockConfig.loadForm('default', 'app', 'death_report'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { contact: { _id: '12345'} }; + }); + + const date = moment().format('YYYY-MM-DD'); + const deathNote = 'Test note'; + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Death report'); + + await deathReportForm.selectDeathPlace(deathReportForm.PLACE_OF_DEATH.healthFacility); + await deathReportForm.setDeathInformation(deathNote); + await deathReportForm.setDeathDate(date); + await genericForm.nextPage(); + + const {deathDate, deathInformation} = await deathReportForm.getSummaryDetails(); + expect(deathDate).to.equal(date); + expect(deathInformation).to.equal(deathNote); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + const jsonObj = doc.fields.death_details; + + expect(additionalDocs).to.be.empty; + + expect(jsonObj.date_of_death).to.equal(date); + expect(jsonObj.death_information).to.equal(deathNote); + expect(jsonObj.place_of_death).to.equal(deathReportForm.PLACE_OF_DEATH.healthFacility); + }); + + it('should render form in the language set for the user', async () => { + await mockConfig.loadForm('default', 'test', 'death_report_es'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { contact: { _id: '12345', name: 'John' } }; + myForm.user = { language: 'es' }; + }); + + const labelsValues = await deathReportForm.getLabelsValues(); + expect(labelsValues.details).to.equal('Detalles del fallecimiento'); + expect(labelsValues.date).to.equal('Fecha del fallecimiento'); + expect(labelsValues.place).to.equal('Lugar del fallecimiento'); + expect(labelsValues.healthFacility).to.equal('Centro de salud'); + expect(labelsValues.home).to.equal('Casa'); + expect(labelsValues.other).to.equal('Otro'); + expect(labelsValues.notes) + .to.equal('Provea cualquier información relevante relacionada con el fallecimiento de John.'); + }); + +}); diff --git a/tests/e2e/cht-form/default/delivery.wdio-spec.js b/tests/e2e/cht-form/default/delivery.wdio-spec.js new file mode 100644 index 00000000000..c746bb776d4 --- /dev/null +++ b/tests/e2e/cht-form/default/delivery.wdio-spec.js @@ -0,0 +1,139 @@ +const mockConfig = require('../mock-config'); +const moment = require('moment'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const deliveryForm = require('@page-objects/default/enketo/delivery.wdio.page'); + +describe('cht-form web component - Delivery Form', () => { + const DATE = moment().format('YYYY-MM-DD'); + + it('should submit a delivery - Alive mother and baby', async () => { + await mockConfig.loadForm('default', 'app', 'delivery'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.contactSummary = { pregnancy_uuid: 'test UUID' }; + myForm.content = { contact: { _id: '12345', patient_id: '79376', name: 'Pregnant Woman' } }; + }); + + const BABY_NAME = 'Benja'; + const BABY_SEX = 'male'; + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Delivery'); + + await deliveryForm.selectDeliveryConditionWomanOutcome('alive_well'); + await genericForm.nextPage(); + await deliveryForm.selectDeliveryPostnatalDangerSignsFever('no'); + await deliveryForm.selectDeliveryPostnatalDangerSevereHeadache('no'); + await deliveryForm.selectDeliveryPostnatalDangerVaginalBleeding('no'); + await deliveryForm.selectDeliveryPostnatalDangerVaginalDischarge('no'); + await deliveryForm.selectDeliveryPostnatalDangerConvulsion('no'); + + await genericForm.nextPage(); + await deliveryForm.selectDeliveryOutcomeBabiesDelivered('1'); + await deliveryForm.selectDeliveryOutcomeBabiesAlive('1'); + await deliveryForm.selectDeliveryOutcomeDeliveryPlace('health_facility'); + await deliveryForm.selectDeliveryOutcomeDeliveryMode('vaginal'); + await deliveryForm.setDeliveryOutcomeDateOfDelivery(DATE); + + await genericForm.nextPage(); + + await deliveryForm.selectDeliveryBabyCondition('alive_well'); + await deliveryForm.setDeliveryBabyName(BABY_NAME); + await deliveryForm.selectDeliveryBabySex(BABY_SEX); + await deliveryForm.selectDeliveryBabyBirthWeightKnown('no'); + await deliveryForm.selectDeliveryBabyBirthLengthKnown('no'); + await deliveryForm.selectDeliveryBabyVaccinesReceived('none'); + await deliveryForm.selectDeliveryBabyBreastfeeding('yes'); + await deliveryForm.selectDeliveryBabyBreastfeedingWithin1Hour('yes'); + await deliveryForm.selectDeliveryBabyInfectedUmbilicalCord('no'); + await deliveryForm.selectDeliveryBabyConvulsion('no'); + await deliveryForm.selectDeliveryBabyDifficultyFeeding('no'); + await deliveryForm.selectDeliveryBabyVomit('no'); + await deliveryForm.selectDeliveryBabyDrowsy('no'); + await deliveryForm.selectDeliveryBabyStiff('no'); + await deliveryForm.selectDeliveryBabyYellowSkin('no'); + await deliveryForm.selectDeliveryBabyFever('no'); + await deliveryForm.selectDeliveryBabyBlueSkin('no'); + await genericForm.nextPage(); + await genericForm.nextPage(); + await deliveryForm.selectDeliveryPncVisits('none'); + await genericForm.nextPage(); + + const summaryInfo = await deliveryForm.getSummaryInfo(); + expect(summaryInfo.womanCondition).to.equal('Alive and well'); + expect(summaryInfo.deliveryDate).to.equal(DATE); + expect(summaryInfo.deliveryPlace).to.equal('Health facility'); + expect(summaryInfo.deliveredBabies).to.equal('1'); + expect(summaryInfo.deceasedBabies).to.equal('0'); + expect(summaryInfo.pncVisits).to.equal('None'); + + const data = await mockConfig.submitForm(); + const jsonObjMother = data[0].fields; + const jsonObjBaby = data[1]; + + expect(jsonObjMother.patient_uuid).to.equal('12345'); + expect(jsonObjMother.patient_id).to.equal('79376'); + expect(jsonObjMother.patient_name).to.equal('Pregnant Woman'); + expect(jsonObjMother.data.meta.__pregnancy_uuid).to.equal('test UUID'); + expect(jsonObjMother.condition.woman_outcome).to.equal('alive_well'); + expect(jsonObjMother.pnc_danger_sign_check.r_pnc_danger_sign_present).to.equal('no'); + expect(jsonObjMother.delivery_outcome.babies_delivered).to.equal('1'); + expect(jsonObjMother.delivery_outcome.babies_alive).to.equal('1'); + expect(jsonObjMother.delivery_outcome.delivery_date).to.equal(DATE); + expect(jsonObjMother.delivery_outcome.delivery_place).to.equal('health_facility'); + expect(jsonObjMother.delivery_outcome.delivery_mode).to.equal('vaginal'); + expect(jsonObjMother.babys_condition.baby_repeat[0].baby_details.baby_condition).to.equal('alive_well'); + expect(jsonObjMother.babys_condition.baby_repeat[0].baby_details.baby_name).to.equal(BABY_NAME); + expect(jsonObjMother.babys_condition.baby_repeat[0].baby_details.baby_sex).to.equal(BABY_SEX); + expect(jsonObjMother.babys_condition.r_baby_danger_sign_present_any).to.equal('no'); + + expect(jsonObjBaby.name).to.equal(BABY_NAME); + expect(jsonObjBaby.sex).to.equal(BABY_SEX); + expect(jsonObjBaby.date_of_birth).to.equal(DATE); + expect(jsonObjBaby.t_danger_signs_referral_follow_up).to.equal('no'); + }); + + it('should submit a delivery - Death mother and baby', async () => { + await mockConfig.loadForm('default', 'app', 'delivery'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { contact: { _id: '12345' } }; + myForm.user = { phone: '+50689999999' }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Delivery'); + + await deliveryForm.selectDeliveryConditionWomanOutcome('deceased'); + await genericForm.nextPage(); + await deliveryForm.fillWomanDeathInformation({ + date: DATE, + place: 'health_facility', + notes: 'Test notes - Mother\'s death' + }); + await $('.form-footer').click(); + await genericForm.nextPage(); + + const data = await mockConfig.submitForm(); + const jsonObjDeliveryReport = data[0].fields; + const jsonObjDeathReport = data[1]; + + expect(jsonObjDeliveryReport.inputs.user.phone).to.equal('+50689999999'); + expect(jsonObjDeliveryReport.patient_uuid).to.equal('12345'); + expect(jsonObjDeliveryReport.condition.woman_outcome).to.equal('deceased'); + expect(jsonObjDeliveryReport.death_info_woman.woman_death_date).to.equal(DATE); + expect(jsonObjDeliveryReport.death_info_woman.woman_death_place).to.equal('health_facility'); + expect(jsonObjDeliveryReport.death_info_woman.woman_death_birth).to.equal('no'); + expect(jsonObjDeliveryReport.death_info_woman.woman_death_add_notes).to.equal('Test notes - Mother\'s death'); + expect(jsonObjDeliveryReport.death_info_woman.death_report.form).to.equal('death_report'); + expect(jsonObjDeliveryReport.death_info_woman.death_report.from).to.equal('+50689999999'); + + expect(jsonObjDeathReport.form).to.equal('death_report'); + expect(jsonObjDeathReport.from).to.equal('+50689999999'); + expect(jsonObjDeathReport.fields.death_details.date_of_death).to.equal(DATE); + expect(jsonObjDeathReport.fields.death_details.place_of_death).to.equal('health_facility'); + expect(jsonObjDeathReport.fields.death_details.death_information).to.equal('Test notes - Mother\'s death'); + }); + +}); diff --git a/tests/e2e/cht-form/default/enketo-widgets.wdio-spec.js b/tests/e2e/cht-form/default/enketo-widgets.wdio-spec.js new file mode 100644 index 00000000000..d168f6996b2 --- /dev/null +++ b/tests/e2e/cht-form/default/enketo-widgets.wdio-spec.js @@ -0,0 +1,99 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const enketoWidgetsPage = require('@page-objects/default/enketo/enketo-widgets.wdio.page'); + +describe('cht-form web component - Enketo Widgets', () => { + + it('should submit the Enketo Widgets form', async () => { + await mockConfig.loadForm('default', 'test', 'enketo_widgets'); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Enketo Widgets'); + + await enketoWidgetsPage.openDropdown(await enketoWidgetsPage.selectMultipleDropdown()); + await enketoWidgetsPage.selectDropdownOptions(await enketoWidgetsPage.selectMultipleDropdown(), 'checkbox', 'a'); + await enketoWidgetsPage.selectDropdownOptions(await enketoWidgetsPage.selectMultipleDropdown(), 'checkbox', 'c'); + expect(await enketoWidgetsPage.getDropdownValue(await enketoWidgetsPage.selectMultipleDropdown())) + .to.contain('numberselected'); + + await enketoWidgetsPage.openDropdown(await enketoWidgetsPage.selectOneDropdown()); + await enketoWidgetsPage.selectDropdownOptions(await enketoWidgetsPage.selectOneDropdown(), 'radio', 'd'); + expect(await enketoWidgetsPage.getDropdownValue(await enketoWidgetsPage.selectOneDropdown())) + .to.equal('option d'); + + // try to move to next page without filling the mandatory phone number field + await genericForm.nextPage(1, false); + expect(await enketoWidgetsPage.phoneFieldRequiredMessage().getAttribute('data-i18n')) + .to.equal('constraint.required'); + + // try to move to next page with an invalid phone number + await enketoWidgetsPage.setPhoneNumber('+4076'); + await genericForm.nextPage(1, false); + expect(await enketoWidgetsPage.phoneFieldConstraintMessage().getAttribute('data-itext-id')) + .to.equal('/enketo_widgets/enketo_test_select/phone:jr:constraintMsg'); + + // finally set a valid phone number and continue + await enketoWidgetsPage.setPhoneNumber('+40766565656'); + + await $('.form-footer').click(); + await genericForm.nextPage(); + + await enketoWidgetsPage.selectCountryRadio('usa'); + await enketoWidgetsPage.selectCityRadio('nyc'); + await enketoWidgetsPage.selectNeighborhoodRadio('bronx'); + await enketoWidgetsPage.openDropdown(await enketoWidgetsPage.countryDropdown()); + await enketoWidgetsPage.selectDropdownOptions(await enketoWidgetsPage.countryDropdown(), 'radio', 'nl'); + await enketoWidgetsPage.openDropdown(await enketoWidgetsPage.cityDropdown()); + expect(await enketoWidgetsPage.getDropdownTotalOptions(await enketoWidgetsPage.cityDropdown())) + .to.equal(3); + await enketoWidgetsPage.selectDropdownOptions(await enketoWidgetsPage.cityDropdown(), 'radio', 'dro'); + await enketoWidgetsPage.openDropdown(await enketoWidgetsPage.neighborhoodDropdown()); + expect(await enketoWidgetsPage.getDropdownTotalOptions(await enketoWidgetsPage.neighborhoodDropdown())) + .to.equal(1); + await enketoWidgetsPage.selectDropdownOptions( + await enketoWidgetsPage.neighborhoodDropdown(), 'radio', 'havendr' + ); + + await genericForm.nextPage(); + await enketoWidgetsPage.setPatientName('Eli'); + await enketoWidgetsPage.setPatientUuid('123 456 789'); + + expect(await (await enketoWidgetsPage.patientNameErrorLabel()).isExisting()).to.be.true; + + await enketoWidgetsPage.setPatientName('Elias'); + await enketoWidgetsPage.setPatientId('12345'); + + expect(await (await enketoWidgetsPage.patientNameErrorLabel()).isExisting()).to.be.false; + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + const jsonObj = doc.fields; + + expect(additionalDocs).to.be.empty; + + expect(jsonObj.patient_uuid).to.equal('123 456 789'); + expect(jsonObj.patient_id).to.equal('12345'); + expect(jsonObj.patient_name).to.equal('Elias'); + expect(jsonObj.enketo_test_select.select_spinner).to.equal('a c'); + expect(jsonObj.enketo_test_select.select1_spinner).to.equal('d'); + expect(jsonObj.enketo_test_select.phone).to.equal('+40766565656'); + expect(jsonObj.cascading_widgets.group1.country).to.equal('usa'); + expect(jsonObj.cascading_widgets.group1.city).to.equal('nyc'); + expect(jsonObj.cascading_widgets.group1.neighborhood).to.equal('bronx'); + expect(jsonObj.cascading_widgets.group2.country2).to.equal('nl'); + expect(jsonObj.cascading_widgets.group2.city2).to.equal('dro'); + expect(jsonObj.cascading_widgets.group2.neighborhood2).to.equal('havendr'); + }); + + it('should verify the cancel button', async () => { + await mockConfig.loadForm('default', 'test', 'enketo_widgets'); + expect(await genericForm.getFormTitle()).to.equal('Enketo Widgets'); + + const cancelResult = await browser.executeAsync((resolve) => { + const myForm = document.getElementById('myform'); + myForm.addEventListener('onCancel', () => resolve('Form Canceled')); + $('.enketo .cancel').click(); + }); + expect(cancelResult).to.equal('Form Canceled'); + }); + +}); diff --git a/tests/e2e/cht-form/default/forms/death_report_es.xml b/tests/e2e/cht-form/default/forms/death_report_es.xml new file mode 100644 index 00000000000..4e5906e58a3 --- /dev/null +++ b/tests/e2e/cht-form/default/forms/death_report_es.xml @@ -0,0 +1,458 @@ + + + + Death report + + + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + Fecha del fallecimiento + + + Provea cualquier información relevante relacionada con el fallecimiento de . + + + Centro de salud + + + Casa + + + Otro + + + Lugar del fallecimiento + + + - + + + Detalles del fallecimiento + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + + + Date of death can only be from today up to 1 year ago. + + + Date of Death + + + Provide any relevant information related to the death of . + + + Health facility + + + Home + + + Other + + + Place of death + + + Specify other + + + Death details + + + <div style="text-align:center;">Relevant Information: </div> + + + <b>Important Information</b><i class="fa fa-warning"></i> + + + <div style="text-align:center;"> + + Date of Death: </div> + + + <b>You will never be able to do any follow ups on when you submit this death report.</b> + + + Patient Details<I class="fa fa-user"></i> + + + You will be able to undo this death report later, if needed. + + + <h4 style="text-align:center;">To finish, be sure to click the Submit button.</h4> + + + What is the patient's name? + + + Date of Birth + + + Name + + + Household ID + + + CHW name + + + CHW phone + + + Contact + + + Patient ID + + + Sex + + + Short Name + + + Contact + + + Source + + + Source ID + + + + + + + + + + + + + + + user + + + <_id/> + + + + 0 + + + <_id/> + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + <__date_of_death/> + <__place_of_death/> + <__place_of_death_other/> + <__death_information/> + + <__patient_uuid/> + <__patient_id/> + <__household_uuid/> + <__source/> + <__source_id/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/e2e/cht-form/default/forms/enketo_widgets.xml b/tests/e2e/cht-form/default/forms/enketo_widgets.xml new file mode 100644 index 00000000000..ee1950b444e --- /dev/null +++ b/tests/e2e/cht-form/default/forms/enketo_widgets.xml @@ -0,0 +1,491 @@ + + + + Enketo Widgets + + + + + City + + + The Netherlands + + + United States + + + Country + + + Neighborhood + + + Cascading Selects with Radio Buttons + + + City + + + The Netherlands + + + United States + + + Country + + + Neighborhood + + + Cascading Selects with Pulldowns + + + Cascading Select widgets + + + Please enter a valid local number, or use the standard international format, which includes a plus sign (+) and country code. For example: +254712345678 + + + Phone Number + + + option a + + + option b + + + option c + + + option d + + + Select one: pulldown + + + option a + + + option b + + + option c + + + option d + + + Select multiple: pulldown + + + Select widgets + + + What is the patient's uuid? + + + Name should contain more than 4 characters + + + What is the patient's name? + + + What is the patient's id? + + + Patient + + + Patient ID + + + Patient Name + + + Patient UUID + + + Amsterdam + + + Denver + + + New York City + + + Los Angeles + + + Rotterdam + + + Dronten + + + The Netherlands + + + United States + + + option a + + + option b + + + option c + + + option d + + + Bronx + + + Harlem + + + Bel Air + + + Westerpark + + + Park Hill + + + Harbor + + + Dam + + + Downtown + + + Harbor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + user + + + <_id/> + + + + + + + + + + + + + + + + + static_instance-cities-0 + nl + ams + + + static_instance-cities-1 + usa + den + + + static_instance-cities-2 + usa + nyc + + + static_instance-cities-3 + usa + la + + + static_instance-cities-4 + nl + rot + + + static_instance-cities-5 + nl + dro + + + + + + + static_instance-list-0 + a + + + static_instance-list-1 + b + + + static_instance-list-2 + c + + + static_instance-list-3 + d + + + + + + + static_instance-neighborhoods-0 + usa + bronx + nyc + + + static_instance-neighborhoods-1 + usa + harlem + nyc + + + static_instance-neighborhoods-2 + usa + belair + la + + + static_instance-neighborhoods-3 + nl + wes + ams + + + static_instance-neighborhoods-4 + usa + parkhill + den + + + static_instance-neighborhoods-5 + nl + haven + rot + + + static_instance-neighborhoods-6 + nl + dam + ams + + + static_instance-neighborhoods-7 + nl + centrum + rot + + + static_instance-neighborhoods-8 + nl + havendr + dro + + + + + + + static_instance-countries-0 + nl + + + static_instance-countries-1 + usa + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/e2e/cht-form/default/person-create.wdio-spec.js b/tests/e2e/cht-form/default/person-create.wdio-spec.js new file mode 100644 index 00000000000..424f4c53f7e --- /dev/null +++ b/tests/e2e/cht-form/default/person-create.wdio-spec.js @@ -0,0 +1,36 @@ +const mockConfig = require('../mock-config'); +const contactPage = require('@page-objects/default/contacts/contacts.wdio.page'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); + +describe('cht-form web component - Create Person Form', () => { + + it('should create a new person', async () => { + await mockConfig.loadForm('default', 'contact', 'person-create'); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('New Person'); + + await (await contactPage.nameField('person')).setValue('Filippo'); + await (await contactPage.sexField('person', 'male')).click(); + await (await contactPage.phoneField()).setValue('+50689999999'); + await (await contactPage.dateOfBirthField()).setValue('2000-09-20'); + await (await contactPage.roleField('person', 'chw')).click(); + await (await contactPage.externalIdField('person')).setValue('12345'); + await (await contactPage.notes('person')).setValue('Test notes - create new person'); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + + expect(additionalDocs).to.be.empty; + + expect(doc.name).to.equal('Filippo'); + expect(doc.date_of_birth).to.equal('2000-09-20'); + expect(doc.sex).to.equal('male'); + expect(doc.phone).to.equal('+50689999999'); + expect(doc.role).to.equal('chw'); + expect(doc.external_id).to.equal('12345'); + expect(doc.notes).to.equal('Test notes - create new person'); + expect(doc.type).to.equal('person'); + }); + +}); + diff --git a/tests/e2e/cht-form/default/person-edit.wdio-spec.js b/tests/e2e/cht-form/default/person-edit.wdio-spec.js new file mode 100644 index 00000000000..bf5ba8336d5 --- /dev/null +++ b/tests/e2e/cht-form/default/person-edit.wdio-spec.js @@ -0,0 +1,81 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const contactPage = require('@page-objects/default/contacts/contacts.wdio.page'); + +describe('cht-form web component - Edit Person Form', () => { + + it('should edit a person', async () => { + await mockConfig.loadForm('default', 'contact', 'person-edit'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { + person: { + parent: 'PARENT', + type: 'person', + name: 'Filippo', + short_name: 'Fili', + date_of_birth: '2000-09-20', + date_of_birth_method: '', + ephemeral_dob: { + dob_calendar: '2000-09-20', + dob_method: '', + dob_approx: '2023-10-11T00:00:00.000-06:00', + dob_raw: '2000-09-20', + dob_iso: '2000-09-20' + }, + sex: 'male', + phone: '+50689999999', + phone_alternate: '', + role: 'chw', + external_id: '12345', + notes: 'Test notes', + user_for_contact: { + create: 'true' + }, + meta: { + created_by: '', + created_by_person_uuid: 'default_user', + created_by_place_uuid: '' + } + }, + meta: { + instanceID: 'uuid:c558e232-0951-4ed8-8392-5ed8ac3c81b3' + } + }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Edit Person'); + + await genericForm.nextPage(); + + const personInfo = await contactPage.getCurrentPersonEditFormValues('male', 'chw'); + expect(personInfo.name).to.equal('Filippo'); + expect(personInfo.shortName).to.equal('Fili'); + expect(personInfo.dateOfBirth).to.equal('2000-09-20'); + expect(personInfo.sex).to.equal('true'); + expect(personInfo.phone).to.equal('+50689999999'); + expect(personInfo.role).to.equal('true'); + expect(personInfo.externalId).to.equal('12345'); + expect(personInfo.notes).to.equal('Test notes'); + + await (await contactPage.nameField('person')).addValue(' Dog'); + await (await contactPage.phoneField()).setValue('+50688888888'); + await (await contactPage.notes('person')).addValue(' - New note'); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + + expect(additionalDocs).to.be.empty; + + expect(doc.name).to.equal('Filippo Dog'); + expect(doc.date_of_birth).to.equal('2000-09-20'); + expect(doc.sex).to.equal('male'); + expect(doc.phone).to.equal('+50688888888'); + expect(doc.role).to.equal('chw'); + expect(doc.external_id).to.equal('12345'); + expect(doc.notes).to.equal('Test notes - New note'); + expect(doc.type).to.equal('person'); + }); + +}); diff --git a/tests/e2e/cht-form/default/pnc-danger-sign-follow-up-baby.wdio-spec.js b/tests/e2e/cht-form/default/pnc-danger-sign-follow-up-baby.wdio-spec.js new file mode 100644 index 00000000000..49b1dfc7b77 --- /dev/null +++ b/tests/e2e/cht-form/default/pnc-danger-sign-follow-up-baby.wdio-spec.js @@ -0,0 +1,50 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const dangerSignPage = require('@page-objects/default/enketo/danger-sign.wdio.page'); + +describe('cht-form web component - PNC Danger Sign Follow-up Baby', () => { + + it('should submit a PNC danger sign follow-up - baby form', async () => { + await mockConfig.loadForm('default', 'app', 'pnc_danger_sign_follow_up_baby'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { contact: { _id: '12345'} }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('PNC danger sign follow-up - baby'); + + await genericForm.selectYesNoOption(dangerSignPage.visitConfirmation('pnc_danger_sign_follow_up_baby')); + await genericForm.selectYesNoOption(dangerSignPage.dangerSignsPresent('pnc_danger_sign_follow_up_baby')); + await genericForm.selectYesNoOption(dangerSignPage.infectedUmbilicalCord('pnc_danger_sign_follow_up_baby')); + await genericForm.selectYesNoOption(dangerSignPage.convulsion('pnc_danger_sign_follow_up_baby'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.difficultyFeeding('pnc_danger_sign_follow_up_baby')); + await genericForm.selectYesNoOption(dangerSignPage.vomit('pnc_danger_sign_follow_up_baby'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.drowsy('pnc_danger_sign_follow_up_baby')); + await genericForm.selectYesNoOption(dangerSignPage.stiffness('pnc_danger_sign_follow_up_baby'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.yellowSkin('pnc_danger_sign_follow_up_baby')); + await genericForm.selectYesNoOption(dangerSignPage.fever('pnc_danger_sign_follow_up_baby'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.blueSkin('pnc_danger_sign_follow_up_baby')); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + const jsonObj = doc.fields; + + expect(additionalDocs).to.be.empty; + + expect(jsonObj.t_danger_signs_referral_follow_up).to.equal('yes'); + expect(jsonObj.danger_signs.visit_confirm).to.equal('yes'); + expect(jsonObj.danger_signs.danger_sign_present).to.equal('yes'); + expect(jsonObj.danger_signs.infected_umbilical_cord).to.equal('yes'); + expect(jsonObj.danger_signs.convulsion).to.equal('no'); + expect(jsonObj.danger_signs.difficulty_feeding).to.equal('yes'); + expect(jsonObj.danger_signs.vomit).to.equal('no'); + expect(jsonObj.danger_signs.drowsy).to.equal('yes'); + expect(jsonObj.danger_signs.stiff).to.equal('no'); + expect(jsonObj.danger_signs.yellow_skin).to.equal('yes'); + expect(jsonObj.danger_signs.fever).to.equal('no'); + expect(jsonObj.danger_signs.blue_skin).to.equal('yes'); + expect(jsonObj.danger_signs.r_danger_sign_present).to.equal('yes'); + }); + +}); diff --git a/tests/e2e/cht-form/default/pnc-danger-sign-follow-up-mother.wdio-spec.js b/tests/e2e/cht-form/default/pnc-danger-sign-follow-up-mother.wdio-spec.js new file mode 100644 index 00000000000..fa1ae934383 --- /dev/null +++ b/tests/e2e/cht-form/default/pnc-danger-sign-follow-up-mother.wdio-spec.js @@ -0,0 +1,41 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const dangerSignPage = require('@page-objects/default/enketo/danger-sign.wdio.page'); + +describe('cht-form web component - PNC Danger Sign Follow-up Mother', () => { + + it('should submit PNC danger sign follow-up - mother form', async () => { + await mockConfig.loadForm('default', 'app', 'pnc_danger_sign_follow_up_mother'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { contact: { _id: '12345'} }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('PNC danger sign follow-up - mother'); + + await genericForm.selectYesNoOption(dangerSignPage.visitConfirmation('pnc_danger_sign_follow_up_mother')); + await genericForm.selectYesNoOption(dangerSignPage.dangerSignsPresent('pnc_danger_sign_follow_up_mother')); + await genericForm.selectYesNoOption(dangerSignPage.fever('pnc_danger_sign_follow_up_mother')); + await genericForm.selectYesNoOption(dangerSignPage.headache('pnc_danger_sign_follow_up_mother'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.vaginalBleeding('pnc_danger_sign_follow_up_mother')); + await genericForm.selectYesNoOption(dangerSignPage.vaginalDischarge('pnc_danger_sign_follow_up_mother'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.convulsion('pnc_danger_sign_follow_up_mother')); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + const jsonObj = doc.fields; + + expect(additionalDocs).to.be.empty; + + expect(jsonObj.t_danger_signs_referral_follow_up).to.equal('yes'); + expect(jsonObj.danger_signs.visit_confirm).to.equal('yes'); + expect(jsonObj.danger_signs.danger_sign_present).to.equal('yes'); + expect(jsonObj.danger_signs.fever).to.equal('yes'); + expect(jsonObj.danger_signs.severe_headache).to.equal('no'); + expect(jsonObj.danger_signs.vaginal_bleeding).to.equal('yes'); + expect(jsonObj.danger_signs.vaginal_discharge).to.equal('no'); + expect(jsonObj.danger_signs.convulsion).to.equal('yes'); + }); + +}); diff --git a/tests/e2e/cht-form/default/pregnancy-danger-sign-follow-up.wdio-spec.js b/tests/e2e/cht-form/default/pregnancy-danger-sign-follow-up.wdio-spec.js new file mode 100644 index 00000000000..2b4b005fc20 --- /dev/null +++ b/tests/e2e/cht-form/default/pregnancy-danger-sign-follow-up.wdio-spec.js @@ -0,0 +1,40 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const pregDangerSignFollowUpForm = require('@page-objects/default/enketo/pregnancy-danger-sign-follow-up.wdio.page'); + +describe('cht-form web component - Pregnancy Danger Sign Form', () => { + + it('should submit a pregnancy danger sign form', async () => { + await mockConfig.loadForm('default', 'app', 'pregnancy_danger_sign_follow_up'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { contact: { _id: '12345'} }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Pregnancy danger sign follow-up'); + + await pregDangerSignFollowUpForm.selectVisitedHealthFacility(true); + await pregDangerSignFollowUpForm.selectDangerSigns(true); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + const jsonObj = doc.fields; + + expect(additionalDocs).to.be.empty; + + expect(jsonObj.t_danger_signs_referral_follow_up).to.equal('yes'); + expect(jsonObj.danger_signs.vaginal_bleeding).to.equal('yes'); + expect(jsonObj.danger_signs.fits).to.equal('yes'); + expect(jsonObj.danger_signs.severe_abdominal_pain).to.equal('yes'); + expect(jsonObj.danger_signs.severe_headache).to.equal('yes'); + expect(jsonObj.danger_signs.very_pale).to.equal('yes'); + expect(jsonObj.danger_signs.fever).to.equal('yes'); + expect(jsonObj.danger_signs.reduced_or_no_fetal_movements).to.equal('yes'); + expect(jsonObj.danger_signs.breaking_water).to.equal('yes'); + expect(jsonObj.danger_signs.easily_tired).to.equal('yes'); + expect(jsonObj.danger_signs.face_hand_swelling).to.equal('yes'); + expect(jsonObj.danger_signs.breathlessness).to.equal('yes'); + }); + +}); diff --git a/tests/e2e/cht-form/default/pregnancy-danger-sign.wdio-spec.js b/tests/e2e/cht-form/default/pregnancy-danger-sign.wdio-spec.js new file mode 100644 index 00000000000..ed6bdce100b --- /dev/null +++ b/tests/e2e/cht-form/default/pregnancy-danger-sign.wdio-spec.js @@ -0,0 +1,53 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const dangerSignPage = require('@page-objects/default/enketo/danger-sign.wdio.page'); + +describe('cht-form web component - Pregnancy Danger Sign Form', () => { + + it('should ', async () => { + await mockConfig.loadForm('default', 'app', 'pregnancy_danger_sign'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.contactSummary = { pregnancy_uuid: 'test pregnancy UUID' }; + myForm.content = { contact: { _id: '12345', patient_id: '79376', name: 'Pregnant Woman' } }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Pregnancy danger sign'); + + await genericForm.selectYesNoOption(dangerSignPage.vaginalBleeding('pregnancy_danger_sign')); + await genericForm.selectYesNoOption(dangerSignPage.fits('pregnancy_danger_sign'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.abdominalPain('pregnancy_danger_sign')); + await genericForm.selectYesNoOption(dangerSignPage.headache('pregnancy_danger_sign'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.veryPale('pregnancy_danger_sign')); + await genericForm.selectYesNoOption(dangerSignPage.fever('pregnancy_danger_sign'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.reduceFetalMov('pregnancy_danger_sign')); + await genericForm.selectYesNoOption(dangerSignPage.breakingOfWater('pregnancy_danger_sign'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.easilyTired('pregnancy_danger_sign')); + await genericForm.selectYesNoOption(dangerSignPage.swellingHands('pregnancy_danger_sign'), 'no'); + await genericForm.selectYesNoOption(dangerSignPage.breathlessness('pregnancy_danger_sign')); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + const jsonObj = doc.fields; + + expect(additionalDocs).to.be.empty; + + expect(jsonObj.patient_uuid).to.equal('12345'); + expect(jsonObj.patient_id).to.equal('79376'); + expect(jsonObj.patient_name).to.equal('Pregnant Woman'); + expect(jsonObj.pregnancy_uuid_ctx).to.equal('test pregnancy UUID'); + expect(jsonObj.danger_signs.vaginal_bleeding).to.equal('yes'); + expect(jsonObj.danger_signs.fits).to.equal('no'); + expect(jsonObj.danger_signs.severe_abdominal_pain).to.equal('yes'); + expect(jsonObj.danger_signs.severe_headache).to.equal('no'); + expect(jsonObj.danger_signs.very_pale).to.equal('yes'); + expect(jsonObj.danger_signs.fever).to.equal('no'); + expect(jsonObj.danger_signs.reduced_or_no_fetal_movements).to.equal('yes'); + expect(jsonObj.danger_signs.breaking_water).to.equal('no'); + expect(jsonObj.danger_signs.easily_tired).to.equal('yes'); + expect(jsonObj.danger_signs.face_hand_swelling).to.equal('no'); + expect(jsonObj.danger_signs.breathlessness).to.equal('yes'); + }); + +}); diff --git a/tests/e2e/cht-form/default/pregnancy-facility-visit-reminder.wdio-spec.js b/tests/e2e/cht-form/default/pregnancy-facility-visit-reminder.wdio-spec.js new file mode 100644 index 00000000000..a99929d5839 --- /dev/null +++ b/tests/e2e/cht-form/default/pregnancy-facility-visit-reminder.wdio-spec.js @@ -0,0 +1,41 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const pregnancyFacilityVisitReminderPage = require( + '@page-objects/default/enketo/pregnancy-facility-visit-reminder.wdio.page' +); + +describe('cht-form web component - Pregnancy Facility Visit Reminder Form', () => { + + it('should submit a pregnancy facility visit reminder form', async () => { + await mockConfig.loadForm('default', 'app', 'pregnancy_facility_visit_reminder'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.contactSummary = { pregnancy_uuid: 'pregnancy test UUID' }; + myForm.content = { + contact: { _id: '12345', patient_id: '79376', name: 'Pregnant Woman'}, + source_visit_date: '2023-07-25' + }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Health facility ANC reminder'); + + const { visitDate} = await pregnancyFacilityVisitReminderPage.getAncReminderInfo(); + expect(Date.parse(visitDate)).to.equal(Date.parse('25 Jul, 2023')); + await pregnancyFacilityVisitReminderPage.selectReminderMethod(); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + const jsonObj = doc.fields; + + expect(additionalDocs).to.be.empty; + + expect(jsonObj.patient_uuid).to.equal('12345'); + expect(jsonObj.patient_id).to.equal('79376'); + expect(jsonObj.patient_name).to.equal('Pregnant Woman'); + expect(jsonObj.pregnancy_uuid_ctx).to.equal('pregnancy test UUID'); + expect(jsonObj.visit_date_for_task).to.equal('25 Jul, 2023'); + expect(jsonObj.facility_visit_reminder.remind_method).to.equal('in_person'); + }); + +}); diff --git a/tests/e2e/cht-form/default/pregnancy-visit.wdio-spec.js b/tests/e2e/cht-form/default/pregnancy-visit.wdio-spec.js new file mode 100644 index 00000000000..e0ba0c26968 --- /dev/null +++ b/tests/e2e/cht-form/default/pregnancy-visit.wdio-spec.js @@ -0,0 +1,71 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const pregnancyVisitForm = require('@page-objects/default/enketo/pregnancy-visit.wdio.page'); +const dangerSignPage = require('@page-objects/default/enketo/danger-sign.wdio.page'); + +describe('cht-form web component - Pregnancy Visit Form', () => { + + it('should submit a pregnancy home visit', async () => { + await mockConfig.loadForm('default', 'app', 'pregnancy_home_visit'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { contact: { _id: '12345'} }; + }); + + let countDangerSigns = 0; + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Pregnancy home visit'); + + await pregnancyVisitForm.selectVisitOption(); + await pregnancyVisitForm.confirmGestationalAge(); + await genericForm.nextPage(); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.vaginalBleeding('pregnancy_home_visit')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.fits('pregnancy_home_visit')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.abdominalPain('pregnancy_home_visit')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.headache('pregnancy_home_visit')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.veryPale('pregnancy_home_visit')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.fever('pregnancy_home_visit')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.reduceFetalMov('pregnancy_home_visit')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.breakingOfWater('pregnancy_home_visit')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.easilyTired('pregnancy_home_visit')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.swellingHands('pregnancy_home_visit')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.breathlessness('pregnancy_home_visit')); + await genericForm.nextPage(); + await genericForm.selectYesNoOption(pregnancyVisitForm.LLIN); + await genericForm.nextPage(); + await genericForm.selectYesNoOption(pregnancyVisitForm.IRON_FOLATE); + await genericForm.nextPage(); + await genericForm.selectYesNoOption(pregnancyVisitForm.HIV_TESTED); + await genericForm.nextPage(); + + const countSummaryDangerSigns = await pregnancyVisitForm.countSummaryDangerSigns(); + expect(countSummaryDangerSigns).to.equal(countDangerSigns); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + const jsonObj = doc.fields; + + expect(additionalDocs).to.be.empty; + + expect(jsonObj.pregnancy_summary.visit_option).to.equal('yes'); + expect(jsonObj.pregnancy_summary.g_age_correct).to.equal('yes'); + + expect(jsonObj.danger_signs.vaginal_bleeding).to.equal('yes'); + expect(jsonObj.danger_signs.fits).to.equal('yes'); + expect(jsonObj.danger_signs.severe_abdominal_pain).to.equal('yes'); + expect(jsonObj.danger_signs.severe_headache).to.equal('yes'); + expect(jsonObj.danger_signs.very_pale).to.equal('yes'); + expect(jsonObj.danger_signs.fever).to.equal('yes'); + expect(jsonObj.danger_signs.reduced_or_no_fetal_movements).to.equal('yes'); + expect(jsonObj.danger_signs.breaking_water).to.equal('yes'); + expect(jsonObj.danger_signs.easily_tired).to.equal('yes'); + expect(jsonObj.danger_signs.face_hand_swelling).to.equal('yes'); + expect(jsonObj.danger_signs.breathlessness).to.equal('yes'); + expect(jsonObj.danger_signs.r_danger_sign_present).to.equal('yes'); + + expect(jsonObj.safe_pregnancy_practices.malaria.llin_use).to.equal('yes'); + expect(jsonObj.safe_pregnancy_practices.iron_folate.iron_folate_daily).to.equal('yes'); + expect(jsonObj.safe_pregnancy_practices.hiv_status.hiv_tested).to.equal('yes'); + }); + +}); diff --git a/tests/e2e/cht-form/default/pregnancy.wdio-spec.js b/tests/e2e/cht-form/default/pregnancy.wdio-spec.js new file mode 100644 index 00000000000..16f044186fd --- /dev/null +++ b/tests/e2e/cht-form/default/pregnancy.wdio-spec.js @@ -0,0 +1,84 @@ +const mockConfig = require('../mock-config'); +const moment = require('moment'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const pregnancyForm = require('@page-objects/default/enketo/pregnancy.wdio.page'); +const dangerSignPage = require('@page-objects/default/enketo/danger-sign.wdio.page'); + +describe('cht-form web component - Pregnancy Form', () => { + + it('should submit a new pregnancy ', async () => { + await mockConfig.loadForm('default', 'app', 'pregnancy'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { contact: { _id: '12345'} }; + }); + + let countRiskFactors = 0; + let countDangerSigns = 0; + const edd = moment().add(30, 'days'); + const nextANCVisit = moment().add(1, 'day').format('YYYY-MM-DD'); + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Pregnancy registration'); + + await pregnancyForm.selectGestationAge(); + await genericForm.nextPage(); + await pregnancyForm.setDeliveryDate(edd.format('YYYY-MM-DD')); + await genericForm.nextPage(); + + const confirmationDetails = await pregnancyForm.getConfirmationDetails(); + expect(Date.parse(confirmationDetails.eddConfirm)).to.equal(Date.parse(edd.format('D MMM, YYYY'))); + + await genericForm.nextPage(); + await pregnancyForm.setANCVisitsPast(); + await genericForm.nextPage(); + await genericForm.selectYesNoOption(pregnancyForm.KNOWN_FUTURE_VISITS); + await pregnancyForm.setFutureVisitDate(nextANCVisit); + await genericForm.nextPage(); + countRiskFactors += await genericForm.selectYesNoOption(pregnancyForm.FIRST_PREGNANCY, 'no'); + countRiskFactors += await genericForm.selectYesNoOption(pregnancyForm.MISCARRIAGE); + await genericForm.nextPage(); + countRiskFactors += await pregnancyForm.selectAllRiskFactors(pregnancyForm.FIRST_PREGNANCY_VALUE.no); + countRiskFactors += await genericForm.selectYesNoOption(pregnancyForm.ADDITIONAL_FACTORS, 'no'); + await genericForm.nextPage(); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.vaginalBleeding('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.fits('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.abdominalPain('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.headache('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.veryPale('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.fever('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.reduceFetalMov('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.breakingOfWater('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.easilyTired('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.swellingHands('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.breathlessness('pregnancy')); + await genericForm.nextPage(); + await genericForm.selectYesNoOption(pregnancyForm.LLIN); + await genericForm.nextPage(); + await genericForm.selectYesNoOption(pregnancyForm.IRON_FOLATE); + await genericForm.nextPage(); + await genericForm.selectYesNoOption(pregnancyForm.DEWORMING_MEDICATION); + await genericForm.nextPage(); + await genericForm.nextPage(); + await genericForm.selectYesNoOption(pregnancyForm.HIV_TESTED); + await genericForm.nextPage(); + + const summaryDetails = await pregnancyForm.getSummaryDetails(); + expect(summaryDetails.weeksPregnantSumm).to.equal(confirmationDetails.weeksPregnantConfirm); + expect(Date.parse(summaryDetails.eddSumm)).to.equal(Date.parse(edd.format('D MMM, YYYY'))); + expect(summaryDetails.riskFactorsSumm).to.equal(countRiskFactors); + expect(summaryDetails.dangerSignsSumm).to.equal(countDangerSigns); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + const jsonObj = doc.fields; + + expect(additionalDocs).to.be.empty; + + expect(Date.parse(jsonObj.gestational_age.g_edd)).to.equal(Date.parse(edd.format('D MMM, YYYY'))); + expect(jsonObj.t_pregnancy_follow_up).to.equal('yes'); + expect(Date.parse(jsonObj.t_pregnancy_follow_up_date)).to.equal(Date.parse(nextANCVisit)); + expect(jsonObj.danger_signs.r_danger_sign_present).to.equal('yes'); + expect(jsonObj.risk_factors.r_risk_factor_present).to.equal('yes'); + }); + +}); diff --git a/tests/e2e/cht-form/default/replace-user.wdio-spec.js b/tests/e2e/cht-form/default/replace-user.wdio-spec.js new file mode 100644 index 00000000000..20a1bc4f99c --- /dev/null +++ b/tests/e2e/cht-form/default/replace-user.wdio-spec.js @@ -0,0 +1,47 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const replaceUserForm = require('@page-objects/default/enketo/replace-user.wdio.page'); + +describe('cht-form web component - Replace User Form', () => { + + it('should submit the replace user form', async () => { + await mockConfig.loadForm('default', 'app', 'replace_user'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { contact: { _id: '12345', role: 'chw' } }; + myForm.user = { phone: '+50689999999' }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Replace User'); + + await replaceUserForm.selectAdminCode('1234'); + await genericForm.nextPage(); + await replaceUserForm.selectContactFullName('Replacement User'); + await replaceUserForm.selectContactSex(replaceUserForm.SEX.female); + await replaceUserForm.selectContactDobUnknown(); + await replaceUserForm.selectContactAgeYears(22); + await genericForm.nextPage(); + + const { phone, contactName } = await replaceUserForm.getWarningMsgDetails(); + expect(phone).to.equal('+50689999999'); + expect(contactName).to.equal('Replacement User'); + + const data = await mockConfig.submitForm(); + const jsonObjContact = data[0].fields; + const jsonObjNewContact = data[1]; + + expect(jsonObjContact.patient_id).to.equal('12345'); + expect(jsonObjContact.user_phone).to.equal('+50689999999'); + expect(jsonObjContact.contact_role).to.equal('chw'); + expect(jsonObjContact.new_contact_name).to.equal('Replacement User'); + expect(jsonObjContact.new_contact_phone).to.equal('+50689999999'); + + expect(jsonObjNewContact.name).to.equal('Replacement User'); + expect(jsonObjNewContact.sex).to.equal('female'); + expect(jsonObjNewContact.phone).to.equal('+50689999999'); + expect(jsonObjNewContact.role).to.equal('chw'); + }); + +}); diff --git a/tests/e2e/cht-form/default/undo-death-report.wdio-spec.js b/tests/e2e/cht-form/default/undo-death-report.wdio-spec.js new file mode 100644 index 00000000000..bdb6d7fe7ed --- /dev/null +++ b/tests/e2e/cht-form/default/undo-death-report.wdio-spec.js @@ -0,0 +1,30 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const undoDeathReportForm = require('@page-objects/default/enketo/undo-death-report.page'); + +describe('cht-form web component - Undo Death Report Form', () => { + + it('should submit an undo death report', async () => { + await mockConfig.loadForm('default', 'app', 'undo_death_report'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { contact: { _id: '12345', name: 'John'} }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Undo death report'); + + expect(await undoDeathReportForm.getConfirmationPatientName()).to.equal('John'); + await undoDeathReportForm.setConfirmUndoDeathOption(); + + const [doc, ...additionalDocs] = await mockConfig.submitForm(); + const jsonObj = doc.fields; + + expect(additionalDocs).to.be.empty; + + expect(jsonObj.patient_name).to.equal('John'); + expect(jsonObj.undo.undo_information).to.equal('yes'); + }); + +}); diff --git a/tests/e2e/cht-form/index.html b/tests/e2e/cht-form/index.html new file mode 100644 index 00000000000..b8cae720bdd --- /dev/null +++ b/tests/e2e/cht-form/index.html @@ -0,0 +1,13 @@ + + + + Example Website with Embedded cht-form Web Component + + + + + + + + + diff --git a/tests/e2e/cht-form/mock-config.js b/tests/e2e/cht-form/mock-config.js new file mode 100644 index 00000000000..bc4e909aef9 --- /dev/null +++ b/tests/e2e/cht-form/mock-config.js @@ -0,0 +1,67 @@ +const express = require('express'); +const path = require('path'); +const fs = require('fs/promises'); +const generateXformService = require('../../../api/src/services/generate-xform'); + +let server; +const mockApp = express(); +mockApp.get('/', (req, res) => res.sendFile(path.join(__dirname, 'index.html'))); +mockApp.use(express.static(path.join(__dirname, '../../../build/cht-form'))); + +const getBaseURL = () => `http://localhost:${server.address().port}`; + +const getFormPath = (config, formType, formName) => { + if (formType === 'test') { + return path.join(__dirname, config, 'forms', `${formName}.xml`); + } + return path.join(__dirname, '../../../config', config, 'forms', formType, `${formName}.xml`); +}; + +const generateFormData = async (formPath) => { + const formXml = await fs.readFile(formPath, 'utf-8'); + // This is essentially the same code used by the test-harness to generate the form html and model. + // If this code changes, the test-harness will need to be updated as well. + const { form: formHtml, model: formModel } = await generateXformService.generate(formXml); + return { formHtml, formModel, formXml }; +}; + +const loadForm = async (config, formType, formName) => { + const formPath = getFormPath(config, formType, formName); + const formData = await generateFormData(formPath); + await browser.url(getBaseURL()); + await browser.execute((formData, formType, formName) => { + const myForm = document.getElementById('myform'); + myForm.formHtml = formData.formHtml; + myForm.formModel = formData.formModel; + myForm.formXml = formData.formXml; + if (formType === 'contact') { + myForm.contactType = formName.split('-')[0]; + } + }, formData, formType, formName); +}; + +const startMockApp = () => { + server = mockApp.listen(); + return getBaseURL(); +}; + +const stopMockApp = () => { + server && server.close(); +}; + +const submitForm = async () => { + await $('.form-footer').click(); + return await browser.executeAsync((resolve) => { + const myForm = document.getElementById('myform'); + myForm.addEventListener('onSubmit', (e) => resolve(e.detail)); + $('.enketo .submit') + .click(); + }); +}; + +module.exports = { + loadForm, + startMockApp, + stopMockApp, + submitForm +}; diff --git a/tests/e2e/cht-form/standard/child-health-registration.wdio-spec.js b/tests/e2e/cht-form/standard/child-health-registration.wdio-spec.js new file mode 100644 index 00000000000..6961df631c2 --- /dev/null +++ b/tests/e2e/cht-form/standard/child-health-registration.wdio-spec.js @@ -0,0 +1,53 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const childHealthRegistrationPage = require('@page-objects/standard/enketo/child-health-registration.wdio.page'); + +describe('cht-form web component - Child Health Registration Form', () => { + + it('should submit a child health registration', async () => { + await mockConfig.loadForm('standard', 'app', 'child_health_registration'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { + contact: { + _id: '12345', + patient_id: '98765', + name: 'Cleo', + parent: { contact: { phone: '+50689252525', name: 'Luna' } } + } + }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Child Health Registration'); + + const defaultSms = 'Good news, Luna! Cleo (98765) has been registered for Child Health messages. Thank you!'; + + const formInfo = await childHealthRegistrationPage.getFormInformation(); + expect(formInfo.chwName).to.equal('Luna'); + expect(formInfo.chwPhone).to.equal('+50689252525'); + expect(formInfo.defaultSms).to.equal(defaultSms); + + await childHealthRegistrationPage.setPersonalNote('Test note - child health registration'); + await genericForm.nextPage(); + + const summaryInformation = await childHealthRegistrationPage.getSummaryInformation(); + expect(summaryInformation.childId).to.equal('98765'); + expect(summaryInformation.childName).to.equal('Cleo'); + expect(summaryInformation.chwName).to.equal('Luna'); + expect(summaryInformation.chwPhone).to.equal('+50689252525'); + expect(summaryInformation.smsContent).to.equal(`${defaultSms} Test note - child health registration`); + + const data = await mockConfig.submitForm(); + const jsonObj = data[0].fields; + + expect(jsonObj.patient_uuid).to.equal('12345'); + expect(jsonObj.patient_id).to.equal('98765'); + expect(jsonObj.patient_name).to.equal('Cleo'); + expect(jsonObj.chw_name).to.equal('Luna'); + expect(jsonObj.chw_phone).to.equal('+50689252525'); + expect(jsonObj.chw_sms).to.equal(`${defaultSms} Test note - child health registration`); + }); + +}); diff --git a/tests/e2e/cht-form/standard/death-confirmation.wdio-spec.js b/tests/e2e/cht-form/standard/death-confirmation.wdio-spec.js new file mode 100644 index 00000000000..0fc24446fb4 --- /dev/null +++ b/tests/e2e/cht-form/standard/death-confirmation.wdio-spec.js @@ -0,0 +1,52 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const deathConfirmationPage = require('@page-objects/standard/enketo/death-confirmation.wdio.page'); +const moment = require('moment'); + +describe('cht-form web component - Death Confirmation Form', () => { + it('should submit a death confirmation', async () => { + await mockConfig.loadForm('standard', 'app', 'death_confirmation'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { + contact: { + _id: '12345', + patient_id: '98765', + name: 'Cleo', + parent: { contact: { phone: '+50689252525', name: 'Luna' } } + } + }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Death Confirmation'); + + const date = moment().format('YYYY-MM-DD'); + + const formInfo = await deathConfirmationPage.getFormInformation(); + expect(formInfo.submittedReportChildName).to.equal('Cleo'); + expect(formInfo.deathConfirmationNoteChildName).to.equal('Cleo'); + expect(formInfo.deathConfirmationNoteChwName).to.equal('Luna'); + expect(formInfo.deathConfirmationNoteChwPhone).to.equal('+50689252525'); + + await deathConfirmationPage.selectConfirmDeathValue('yes'); + await deathConfirmationPage.selectDeathPlace('facility'); + await deathConfirmationPage.setAdditionalNotes('Test notes - death confirmation'); + await deathConfirmationPage.setDeathDate(date); + + const data = await mockConfig.submitForm(); + const jsonObj = data[0].fields; + + expect(jsonObj.patient_uuid).to.equal('12345'); + expect(jsonObj.patient_id).to.equal('98765'); + expect(jsonObj.child_name).to.equal('Cleo'); + expect(jsonObj.chw_name).to.equal('Luna'); + expect(jsonObj.chw_phone).to.equal('+50689252525'); + expect(jsonObj.death_report.death).to.equal('yes'); + expect(jsonObj.death_report.date_of_death).to.equal(date); + expect(jsonObj.death_report.place).to.equal('facility'); + expect(jsonObj.death_report.notes).to.equal('Test notes - death confirmation'); + }); + +}); diff --git a/tests/e2e/cht-form/standard/delivery.wdio-spec.js b/tests/e2e/cht-form/standard/delivery.wdio-spec.js new file mode 100644 index 00000000000..08baf70103f --- /dev/null +++ b/tests/e2e/cht-form/standard/delivery.wdio-spec.js @@ -0,0 +1,62 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const deliveryForm = require('@page-objects/standard/enketo/delivery.wdio.page'); +const moment = require('moment'); + +describe('cht-form web component - Delivery Form', () => { + + it('should submit a delivery report', async () => { + await mockConfig.loadForm('standard', 'app', 'delivery'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { + contact: { + _id: '12345', + patient_id: '98765', + name: 'Cleo', + parent: { contact: { phone: '+50689252525', name: 'Luna' } } + } + }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Delivery Report'); + + const note = 'Test note - Delivery report'; + const date = moment().format('YYYY-MM-DD'); + const followUpSms = 'Good news, Luna! ' + + 'Cleo (98765) has delivered at the health facility. ' + + 'We will alert you when it is time to refer them for PNC. Please monitor them for danger signs. ' + + `Thank you! ${note}`; + + const pregnancyOutcome = await deliveryForm.selectPregnancyOutcome(); + const locationDelivery = await deliveryForm.selectDeliveryLocation(); + await deliveryForm.setDeliveryDate(date); + await genericForm.nextPage(); + await deliveryForm.setNote(note); + await genericForm.nextPage(); + + const summaryDetails = await deliveryForm.getSummaryDetails(); + expect(summaryDetails.patientName).to.equal('Cleo'); + expect(summaryDetails.patientId).to.equal('98765'); + expect(summaryDetails.outcome).to.equal(pregnancyOutcome); + expect(summaryDetails.location).to.equal(locationDelivery); + expect(summaryDetails.followUpSmsNote1).to.equal('The following will be sent as a SMS to Luna (+50689252525)'); + expect(summaryDetails.followUpSmsNote2).to.equal(followUpSms); + + const data = await mockConfig.submitForm(); + const jsonObj = data[0].fields; + + expect(jsonObj.patient_uuid).to.equal('12345'); + expect(jsonObj.patient_id).to.equal('98765'); + expect(jsonObj.patient_name).to.equal('Cleo'); + expect(jsonObj.chw_name).to.equal('Luna'); + expect(jsonObj.chw_phone).to.equal('+50689252525'); + expect(jsonObj.birth_date).to.equal(date); + expect(jsonObj.label_pregnancy_outcome).to.equal(pregnancyOutcome); + expect(jsonObj.label_delivery_code).to.equal(locationDelivery); + expect(jsonObj.chw_sms).to.equal(followUpSms); + }); + +}); diff --git a/tests/e2e/cht-form/standard/health-center-create.wdio-spec.js b/tests/e2e/cht-form/standard/health-center-create.wdio-spec.js new file mode 100644 index 00000000000..65c5e20c6c7 --- /dev/null +++ b/tests/e2e/cht-form/standard/health-center-create.wdio-spec.js @@ -0,0 +1,43 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const contactPageDefault = require('@page-objects/default/contacts/contacts.wdio.page'); + +describe('cht-form web component - Create an Health Center', () => { + + it('should create an health center', async () => { + await mockConfig.loadForm('standard', 'contact', 'health_center-create'); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('New Health Center'); + + await (await contactPageDefault.newPrimaryContactButton()).click(); + await (await contactPageDefault.newPrimaryContactName()).setValue('Filippo'); + await (await contactPageDefault.phoneField()).setValue('+50689888888'); + await (await contactPageDefault.roleField('contact', 'chw')).click(); + await (await contactPageDefault.externalIdField('contact')).setValue('123 contact'); + await (await contactPageDefault.notes('contact')).setValue('Test notes - new contact'); + await contactPageDefault.genericForm.nextPage(); + await (await contactPageDefault.writeNamePlace('health_center')).click(); + await (await contactPageDefault.customPlaceNameField()).setValue('Filippo\'s health center test'); + await (await contactPageDefault.externalIdField('health_center')).setValue('123 HC'); + await (await contactPageDefault.notes('health_center')).setValue('Test notes - new health center'); + + const data = await mockConfig.submitForm(); + + expect(data[0].is_name_generated).to.equal('false'); + expect(data[0].name).to.equal('Filippo\'s health center test'); + expect(data[0].external_id).to.equal('123 HC'); + expect(data[0].notes).to.equal('Test notes - new health center'); + expect(data[0].contact._id).to.equal(data[1]._id); + expect(data[0].type).to.equal('health_center'); + + expect(data[1].name).to.equal('Filippo'); + expect(data[1].phone).to.equal('+50689888888'); + expect(data[1].role).to.equal('chw'); + expect(data[1].external_id).to.equal('123 contact'); + expect(data[1].notes).to.equal('Test notes - new contact'); + expect(data[1].type).to.equal('person'); + + }); + +}); diff --git a/tests/e2e/cht-form/standard/health-center-edit.wdio-spec.js b/tests/e2e/cht-form/standard/health-center-edit.wdio-spec.js new file mode 100644 index 00000000000..32ba21119c8 --- /dev/null +++ b/tests/e2e/cht-form/standard/health-center-edit.wdio-spec.js @@ -0,0 +1,51 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const contactPage = require('@page-objects/standard/contacts/contacts.wdio.page'); + +describe('cht-form web component - Edit an Health Center', () => { + + it('should edit an health center', async () => { + await mockConfig.loadForm('standard', 'contact', 'health_center-edit'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { + health_center: { + parent: 'PARENT', + type: '', + is_name_generated: 'false', + name: 'Filippo\'s health center test', + external_id: '123 HC', + notes: 'Test notes - new health center', + contact: '', + geolocation: '', + meta: { + created_by: '', + created_by_person_uuid: 'default_user', + created_by_place_uuid: '' + } + } + }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Edit Health Center'); + + const placeInfo = await contactPage.getCurrentPlaceEditFormValues('health_center'); + expect(placeInfo.name).to.equal('Filippo\'s health center test'); + expect(placeInfo.externalId).to.equal('123 HC'); + expect(placeInfo.notes).to.equal('Test notes - new health center'); + + await (await contactPage.contactPageDefault.nameField('health_center')).addValue(' - Edited'); + await (await contactPage.contactPageDefault.externalIdField('health_center')).addValue(' - Edited'); + await (await contactPage.contactPageDefault.notes('health_center')).addValue(' - Edited'); + + const data = await mockConfig.submitForm(); + + expect(data[0].name).to.equal('Filippo\'s health center test - Edited'); + expect(data[0].external_id).to.equal('123 HC - Edited'); + expect(data[0].notes).to.equal('Test notes - new health center - Edited'); + expect(data[0].type).to.equal('health_center'); + }); + +}); diff --git a/tests/e2e/cht-form/standard/immunization-visit.wdio-spec.js b/tests/e2e/cht-form/standard/immunization-visit.wdio-spec.js new file mode 100644 index 00000000000..2103f046085 --- /dev/null +++ b/tests/e2e/cht-form/standard/immunization-visit.wdio-spec.js @@ -0,0 +1,130 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const immVisitForm = require('@page-objects/standard/enketo/immunization-visit.wdio.page'); + +describe('cht-form web component - Immunization Visit Form', () => { + + it('should submit an immunization visit', async () => { + await mockConfig.loadForm('standard', 'app', 'immunization_visit'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { + contact: { + _id: '12345', + patient_id: '98765', + name: 'Cleo', + parent: { contact: { phone: '+50689252525', name: 'Luna' } } + } + }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Immunization Visit'); + + let countAppliedVaccines = 0; + const note = 'Test notes - immunization visit'; + const followUpSms = 'Nice work, Luna! Cleo (98765) attended their immunizations visit at the health facility. ' + + `Keep up the good work. Thank you! ${note}`; + + await immVisitForm.selectAllVaccines(); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.BCG_VACCINE, 'yes'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.CHOLERA_VACCINE, 'CH'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.HEPATITIS_A_VACCINE, 'HA'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.HEPATITIS_B_VACCINE, 'yes'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.HPV_VACCINE, 'HPV'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.FLU_VACCINE, 'yes'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.JAP_ENCEPHALITIS_VACCINE, 'yes'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.MENINGOCOCCAL_VACCINE, 'MN'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.MMR_VACCINE, 'MMR'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.MMRV_VACCINE, 'MMRV'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.POLIO_VACCINE, 'PV'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.PENTAVALENT_VACCINE, 'DT'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.DPT_BOOSTER_VACCINE, 'DPT'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.PNEUMOCOCCAL_VACCINE, 'PCV'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.ROTAVIRUS_VACCINE, 'RV'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.TYPHOID_VACCINE, 'TY'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.VITAMIN_A_VACCINE, 'yes'); + await genericForm.nextPage(); + countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.YELLOW_FEVER_VACCINE, 'yes'); + await genericForm.nextPage(); + await immVisitForm.addNotes(note); + await genericForm.nextPage(); + + const summaryDetails = await immVisitForm.getSummaryDetails(); + expect(summaryDetails.patientName).to.equal('Cleo'); + expect(summaryDetails.patientId).to.equal('98765'); + expect(summaryDetails.appliedVaccines).to.equal(countAppliedVaccines); + expect(summaryDetails.followUpSmsNote1).to.include('The following will be sent as a SMS to Luna +50689252525'); + expect(summaryDetails.followUpSmsNote2).to.include(followUpSms); + + const data = await mockConfig.submitForm(); + const jsonObj = data[0].fields; + + expect(jsonObj.chw_sms).to.equal(followUpSms); + expect(jsonObj.visit_confirmed).to.equal('yes'); + expect(jsonObj.vaccines_received.received_bcg).to.equal('yes'); + expect(jsonObj.vaccines_received.received_cholera_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_cholera_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_cholera_3).to.equal('yes'); + expect(jsonObj.vaccines_received.received_hep_a_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_hep_a_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_hep_b).to.equal('yes'); + expect(jsonObj.vaccines_received.received_hpv_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_hpv_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_hpv_3).to.equal('yes'); + expect(jsonObj.vaccines_received.received_flu).to.equal('yes'); + expect(jsonObj.vaccines_received.received_jap_enc).to.equal('yes'); + expect(jsonObj.vaccines_received.received_meningococcal_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_meningococcal_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_meningococcal_3).to.equal('yes'); + expect(jsonObj.vaccines_received.received_meningococcal_4).to.equal('yes'); + expect(jsonObj.vaccines_received.received_mmr_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_mmr_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_mmrv_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_mmrv_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_polio_0).to.equal('yes'); + expect(jsonObj.vaccines_received.received_polio_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_polio_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_polio_3).to.equal('yes'); + expect(jsonObj.vaccines_received.received_ipv_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_ipv_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_ipv_3).to.equal('yes'); + expect(jsonObj.vaccines_received.received_fipv_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_fipv_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_penta_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_penta_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_penta_3).to.equal('yes'); + expect(jsonObj.vaccines_received.received_dpt_4).to.equal('yes'); + expect(jsonObj.vaccines_received.received_dpt_5).to.equal('yes'); + expect(jsonObj.vaccines_received.received_pneumococcal_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_pneumococcal_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_pneumococcal_3).to.equal('yes'); + expect(jsonObj.vaccines_received.received_pneumococcal_4).to.equal('yes'); + expect(jsonObj.vaccines_received.received_rotavirus_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_rotavirus_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_rotavirus_3).to.equal('yes'); + expect(jsonObj.vaccines_received.received_typhoid_1).to.equal('yes'); + expect(jsonObj.vaccines_received.received_typhoid_2).to.equal('yes'); + expect(jsonObj.vaccines_received.received_vitamin_a).to.equal('yes'); + expect(jsonObj.vaccines_received.received_yellow_fever).to.equal('yes'); + }); + +}); diff --git a/tests/e2e/cht-form/standard/postnatal-visit.wdio-spec.js b/tests/e2e/cht-form/standard/postnatal-visit.wdio-spec.js new file mode 100644 index 00000000000..5ede16ff7d5 --- /dev/null +++ b/tests/e2e/cht-form/standard/postnatal-visit.wdio-spec.js @@ -0,0 +1,62 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const postnatalVisitForm = require('@page-objects/standard/enketo/postnatal-visit.wdio.page'); + +describe('cht-form web component - Postnatal Visit Report', () => { + + it('should submit a postnatal visit report', async () => { + await mockConfig.loadForm('standard', 'app', 'postnatal_visit'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { + contact: { + _id: '12345', + patient_id: '98765', + name: 'Cleo', + parent: { contact: { phone: '+50689252525', name: 'Luna' } } + } + }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Postnatal Visit'); + + const note = 'Test note - postnatal visit'; + const followUpSms = 'Hi, Luna, Mother Cleo (98765) and baby attended PNC. ' + + 'The nurse reported danger signs in the mother and baby. ' + + `Please follow up to see if they need additional support. Thank you! ${note}`; + + await postnatalVisitForm.selectAssessingTo('both'); + await genericForm.nextPage(); + const momDangerSigns = await postnatalVisitForm.selectAllDangerSigns('mom'); + await postnatalVisitForm.setOtherDangerSign('mom', 'Other sign - mom'); + await genericForm.nextPage(); + const babyDangerSigns = await postnatalVisitForm.selectAllDangerSigns('baby'); + await postnatalVisitForm.setOtherDangerSign('baby', 'Other sign - baby'); + await genericForm.nextPage(); + await postnatalVisitForm.setSmsNote(note); + await genericForm.nextPage(); + + const summaryDetails = await postnatalVisitForm.getSummaryDetails(); + expect(summaryDetails.patientName).to.equal('Cleo'); + expect(summaryDetails.patientId).to.equal('98765'); + expect(summaryDetails.visitInformation).to.equal('Postnatal care visit completed'); + expect(summaryDetails.countDangerSignsMom).to.equal(momDangerSigns); + expect(summaryDetails.countDangerSignsBaby).to.equal(babyDangerSigns); + expect(summaryDetails.followUpSmsNote1).to.equal('The following will be sent as a SMS to Luna +50689252525'); + expect(summaryDetails.followUpSmsNote2).to.equal(followUpSms); + + const data = await mockConfig.submitForm(); + const jsonObj = data[0].fields; + + expect(jsonObj.danger_signs_mom).to.equal('d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14'); + expect(jsonObj.danger_signs_baby).to.equal('bd1 bd2 bd3 bd4 bd5 bd6 bd7 bd8 bd9 bd10 bd11'); + expect(jsonObj.chw_sms).to.equal(followUpSms); + expect(jsonObj.visit_confirmed).to.equal('yes'); + expect(jsonObj.group_danger_signs_mom.danger_signs_mom_other).to.equal('Other sign - mom'); + expect(jsonObj.group_danger_signs_baby.danger_signs_baby_other).to.equal('Other sign - baby'); + + }); + +}); diff --git a/tests/e2e/cht-form/standard/pregnancy-visit.wdio-spec.js b/tests/e2e/cht-form/standard/pregnancy-visit.wdio-spec.js new file mode 100644 index 00000000000..bca44eac7db --- /dev/null +++ b/tests/e2e/cht-form/standard/pregnancy-visit.wdio-spec.js @@ -0,0 +1,51 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const pregnancyVisitForm = require('@page-objects/standard/enketo/pregnancy-visit.wdio.page'); + +describe('cht-form web component - Pregnancy Visit Report', () => { + + it('should submit a pregnancy visit', async () => { + await mockConfig.loadForm('standard', 'app', 'pregnancy_visit'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { + contact: { + _id: '12345', + patient_id: '98765', + name: 'Cleo', + parent: { contact: { phone: '+50689252525', name: 'Luna' } } + } + }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('Pregnancy Visit'); + + const note = 'Test note - pregnancy visit'; + const followUpSms = 'Nice work, Luna! Cleo (98765) has attended ANC at the health facility. ' + + 'Please note that Cleo has one or more danger signs for a high risk pregnancy. ' + + `We will send you a message when they are due for their next visit. Thank you! ${note}`; + + const dangerSigns = await pregnancyVisitForm.selectAllDangerSigns(); + await genericForm.nextPage(); + await pregnancyVisitForm.setNote(note); + await genericForm.nextPage(); + + const summaryDetails = await pregnancyVisitForm.getSummaryDetails(); + expect(summaryDetails.patientName).to.equal('Cleo'); + expect(summaryDetails.patientId).to.equal('98765'); + expect(summaryDetails.countDangerSigns).to.equal(dangerSigns.length); + expect(summaryDetails.followUpSmsNote1).to.equal('The following will be sent as a SMS to Luna +50689252525'); + expect(summaryDetails.followUpSmsNote2).to.equal(followUpSms); + + const data = await mockConfig.submitForm(); + const jsonObj = data[0].fields; + + expect(jsonObj.danger_signs).to.equal('d1 d2 d3 d4 d5 d6 d7 d8 d9'); + expect(jsonObj.referral_follow_up_needed).to.equal('true'); + expect(jsonObj.chw_sms).to.equal(followUpSms); + expect(jsonObj.visit_confirmed).to.equal('yes'); + }); + +}); diff --git a/tests/e2e/cht-form/standard/pregnancy.wdio-spec.js b/tests/e2e/cht-form/standard/pregnancy.wdio-spec.js new file mode 100644 index 00000000000..5fa8e366c2c --- /dev/null +++ b/tests/e2e/cht-form/standard/pregnancy.wdio-spec.js @@ -0,0 +1,61 @@ +const mockConfig = require('../mock-config'); +const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); +const pregnancyForm = require('@page-objects/standard/enketo/pregnancy.wdio.page'); + +describe('cht-form web component - Pregnancy Registration Form', () => { + + it('should register a new pregnancy', async () => { + await mockConfig.loadForm('standard', 'app', 'pregnancy'); + + await browser.execute(() => { + const myForm = document.getElementById('myform'); + myForm.content = { + contact: { + _id: '12345', + patient_id: '98765', + name: 'Cleo', + parent: { contact: { phone: '+50689252525', name: 'Luna' } } + } + }; + }); + + const title = await genericForm.getFormTitle(); + expect(title).to.equal('New Pregnancy'); + + const note = 'Test note - New pregnancy'; + const followUpSms = 'Hi Luna, a pregnancy with danger signs for Cleo (98765) has been registered ' + + 'by the health facility. This is a high-risk pregnancy. You will receive ANC notifications for this patient. ' + + `Please follow up with the nurse to identify the patient. Thank you! ${note}`; + + await pregnancyForm.selectKnowLMP(); + await pregnancyForm.selectAproxLMP(pregnancyForm.APROX_LMP.b7To8Months); + + expect(await (await pregnancyForm.getEstDeliveryDate()).isDisplayed()).to.be.true; + + await genericForm.nextPage(); + const riskFactors = await pregnancyForm.selectAllRiskFactors(); + await genericForm.nextPage(); + const dangerSigns = await pregnancyForm.selectAllDangerSigns(); + await genericForm.nextPage(); + await pregnancyForm.setNote(note); + await genericForm.nextPage(); + + const summaryDetails = await pregnancyForm.getSummaryDetails(); + expect(summaryDetails.patientName).to.equal('Cleo'); + expect(summaryDetails.patientId).to.equal('98765'); + expect(summaryDetails.countRiskFactors).to.equal(riskFactors.length); + expect(summaryDetails.countDangerSigns).to.equal(dangerSigns.length); + expect(summaryDetails.followUpSmsNote1).to.equal('The following will be sent as a SMS to Luna +50689252525'); + expect(summaryDetails.followUpSmsNote2).to.equal(followUpSms); + + const data = await mockConfig.submitForm(); + const jsonObj = data[0].fields; + + expect(jsonObj.chw_sms).to.equal(followUpSms); + expect(jsonObj.lmp_method).to.equal('approx'); + expect(jsonObj.risk_factors).to.equal('r2 r3 r4 r5 r6'); + expect(jsonObj.danger_signs).to.equal('d1 d2 d3 d4 d5 d6 d7 d8 d9'); + expect(jsonObj.days_since_lmp).to.equal('244'); + }); + +}); diff --git a/tests/e2e/cht-form/wdio.conf.js b/tests/e2e/cht-form/wdio.conf.js new file mode 100644 index 00000000000..dc3285fe9e5 --- /dev/null +++ b/tests/e2e/cht-form/wdio.conf.js @@ -0,0 +1,56 @@ +const wdioBaseConfig = require('../wdio.conf'); + +const path = require('path'); +const fs = require('fs'); + +const chai = require('chai'); +chai.use(require('chai-exclude')); +chai.use(require('chai-as-promised')); +const ALLURE_OUTPUT = 'allure-results'; +const logPath = path.join('tests', 'logs'); +const browserLogPath = path.join(logPath, 'browser.console.log'); +const mockConfig = require('./mock-config'); + +// Override specific properties from wdio base config +const defaultConfig = { + ...wdioBaseConfig.config, + specs: ['**/*.wdio-spec.js'], + baseUrl: mockConfig.startMockApp(), + + onPrepare: () => { + // delete all previous test + if (fs.existsSync(ALLURE_OUTPUT)) { + const files = fs.readdirSync(ALLURE_OUTPUT) || []; + files.forEach(fileName => { + if (fileName !== 'history') { + const filePath = path.join(ALLURE_OUTPUT, fileName); + fs.unlinkSync(filePath); + } + }); + } + // clear the main log file + if (fs.existsSync(browserLogPath)) { + fs.unlinkSync(browserLogPath); + } + // Create tests/logs if it does not exist. + if (!fs.existsSync(logPath)) { + fs.mkdirSync(logPath); + } + }, + + beforeTest: (test) => { + const testTile = test.title; + const title = `~~~~~~~~~~~~~ ${testTile} ~~~~~~~~~~~~~~~~~~~~~~\n`; + fs.appendFileSync(browserLogPath, title); + }, + + after: () => { + mockConfig.stopMockApp(); + }, + + afterTest: () => {}, + + onComplete: () => {}, +}; + +exports.config = defaultConfig; diff --git a/tests/e2e/default/enketo/pregnancy-danger-sign.wdio-spec.js b/tests/e2e/default/enketo/pregnancy-danger-sign-follow-up.wdio-spec.js similarity index 72% rename from tests/e2e/default/enketo/pregnancy-danger-sign.wdio-spec.js rename to tests/e2e/default/enketo/pregnancy-danger-sign-follow-up.wdio-spec.js index 2edb9d843f6..fdebad30b48 100644 --- a/tests/e2e/default/enketo/pregnancy-danger-sign.wdio-spec.js +++ b/tests/e2e/default/enketo/pregnancy-danger-sign-follow-up.wdio-spec.js @@ -4,7 +4,7 @@ const loginPage = require('@page-objects/default/login/login.wdio.page'); const commonPage = require('@page-objects/default/common/common.wdio.page'); const reportsPage = require('@page-objects/default/reports/reports.wdio.page'); const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); -const pregnancyDangerSignForm = require('@page-objects/default/enketo/pregnancy-danger-sign.wdio.page'); +const dangerSignFollowUpForm = require('@page-objects/default/enketo/pregnancy-danger-sign-follow-up.wdio.page'); describe('Pregnancy danger sign follow-up form', () => { before(async () => { @@ -17,10 +17,10 @@ describe('Pregnancy danger sign follow-up form', () => { await commonPage.goToReports(); await commonPage.openFastActionReport('pregnancy_danger_sign_follow_up', false); - await pregnancyDangerSignForm.selectPatient('jack'); + await dangerSignFollowUpForm.selectPatient('jack'); await genericForm.nextPage(); - await pregnancyDangerSignForm.selectVisitedHealthFacility(true); - await pregnancyDangerSignForm.selectDangerSigns(false); + await dangerSignFollowUpForm.selectVisitedHealthFacility(true); + await dangerSignFollowUpForm.selectDangerSigns(false); await reportsPage.submitForm(); await genericForm.verifyReport(); @@ -30,10 +30,10 @@ describe('Pregnancy danger sign follow-up form', () => { await commonPage.goToReports(); await commonPage.openFastActionReport('pregnancy_danger_sign_follow_up', false); - await pregnancyDangerSignForm.selectPatient('jill'); + await dangerSignFollowUpForm.selectPatient('jill'); await genericForm.nextPage(); - await pregnancyDangerSignForm.selectVisitedHealthFacility(true); - await pregnancyDangerSignForm.selectDangerSigns(false); + await dangerSignFollowUpForm.selectVisitedHealthFacility(true); + await dangerSignFollowUpForm.selectDangerSigns(false); await reportsPage.submitForm(); const reportId = await reportsPage.getCurrentReportId(); @@ -54,10 +54,10 @@ describe('Pregnancy danger sign follow-up form', () => { await commonPage.goToReports(); await commonPage.openFastActionReport('pregnancy_danger_sign_follow_up', false); - await pregnancyDangerSignForm.selectPatient('jill'); + await dangerSignFollowUpForm.selectPatient('jill'); await genericForm.nextPage(); - await pregnancyDangerSignForm.selectVisitedHealthFacility(true); - await pregnancyDangerSignForm.selectDangerSigns(false); + await dangerSignFollowUpForm.selectVisitedHealthFacility(true); + await dangerSignFollowUpForm.selectDangerSigns(false); await reportsPage.submitForm(); const reportId = await reportsPage.getCurrentReportId(); @@ -66,19 +66,19 @@ describe('Pregnancy danger sign follow-up form', () => { expect(initialReport._attachments).to.equal(undefined); await reportsPage.editReport(reportId); - await pregnancyDangerSignForm.selectPatient('jack'); + await dangerSignFollowUpForm.selectPatient('jack'); await genericForm.nextPage(); - await pregnancyDangerSignForm.selectVisitedHealthFacility(false); - await pregnancyDangerSignForm.selectDangerSigns(true); + await dangerSignFollowUpForm.selectVisitedHealthFacility(false); + await dangerSignFollowUpForm.selectDangerSigns(true); await reportsPage.submitForm(); const updatedReport = await utils.getDoc(reportId); await commonPage.openFastActionReport('pregnancy_danger_sign_follow_up', false); - await pregnancyDangerSignForm.selectPatient('jack'); + await dangerSignFollowUpForm.selectPatient('jack'); await genericForm.nextPage(); - await pregnancyDangerSignForm.selectVisitedHealthFacility(false); - await pregnancyDangerSignForm.selectDangerSigns(true); + await dangerSignFollowUpForm.selectVisitedHealthFacility(false); + await dangerSignFollowUpForm.selectDangerSigns(true); await reportsPage.submitForm(); const compareReportId = await reportsPage.getCurrentReportId(); diff --git a/tests/e2e/default/enketo/pregnancy-facility-visit-reminder.wdio-spec.js b/tests/e2e/default/enketo/pregnancy-facility-visit-reminder.wdio-spec.js index 6c9602372da..30ed31c60ec 100644 --- a/tests/e2e/default/enketo/pregnancy-facility-visit-reminder.wdio-spec.js +++ b/tests/e2e/default/enketo/pregnancy-facility-visit-reminder.wdio-spec.js @@ -1,4 +1,4 @@ -const moment = require('moment/moment'); +const moment = require('moment'); const utils = require('@utils'); const placeFactory = require('@factories/cht/contacts/place'); const userFactory = require('@factories/cht/users/users'); diff --git a/tests/e2e/default/enketo/pregnancy.wdio-spec.js b/tests/e2e/default/enketo/pregnancy.wdio-spec.js index 94cf4dfc4c1..36a99e10c17 100644 --- a/tests/e2e/default/enketo/pregnancy.wdio-spec.js +++ b/tests/e2e/default/enketo/pregnancy.wdio-spec.js @@ -11,6 +11,7 @@ const reportsPage = require('@page-objects/default/reports/reports.wdio.page'); const analyticsPage = require('@page-objects/default/analytics/analytics.wdio.page'); const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); const pregnancyForm = require('@page-objects/default/enketo/pregnancy.wdio.page'); +const dangerSignPage = require('@page-objects/default/enketo/danger-sign.wdio.page'); const { TARGET_MET_COLOR, TARGET_UNMET_COLOR } = analyticsPage; describe('Pregnancy registration', () => { @@ -47,35 +48,35 @@ describe('Pregnancy registration', () => { await genericForm.nextPage(); await pregnancyForm.setANCVisitsPast(); await genericForm.nextPage(); - await pregnancyForm.selectYesNoOption(pregnancyForm.KNOWN_FUTURE_VISITS); + await genericForm.selectYesNoOption(pregnancyForm.KNOWN_FUTURE_VISITS); await pregnancyForm.setFutureVisitDate(nextANCVisit.format('YYYY-MM-DD')); await genericForm.nextPage(); - countRiskFactors += await pregnancyForm.selectYesNoOption(pregnancyForm.FIRST_PREGNANCY, 'no'); - countRiskFactors += await pregnancyForm.selectYesNoOption(pregnancyForm.MISCARRIAGE); + countRiskFactors += await genericForm.selectYesNoOption(pregnancyForm.FIRST_PREGNANCY, 'no'); + countRiskFactors += await genericForm.selectYesNoOption(pregnancyForm.MISCARRIAGE); await genericForm.nextPage(); countRiskFactors += await pregnancyForm.selectAllRiskFactors(pregnancyForm.FIRST_PREGNANCY_VALUE.no); - countRiskFactors += await pregnancyForm.selectYesNoOption(pregnancyForm.ADDITIONAL_FACTORS, 'no'); + countRiskFactors += await genericForm.selectYesNoOption(pregnancyForm.ADDITIONAL_FACTORS, 'no'); await genericForm.nextPage(); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.VAGINAL_BLEEDING); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.FITS); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.ABDOMINAL_PAIN); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.HEADACHE); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.VERY_PALE); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.FEVER); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.REDUCE_FETAL_MOV); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.BREAKING_OF_WATER); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.EASILY_TIRED); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.SWELLING_HANDS); - countDangerSigns += await pregnancyForm.selectYesNoOption(pregnancyForm.BREATHLESSNESS); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.vaginalBleeding('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.fits('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.abdominalPain('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.headache('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.veryPale('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.fever('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.reduceFetalMov('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.breakingOfWater('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.easilyTired('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.swellingHands('pregnancy')); + countDangerSigns += await genericForm.selectYesNoOption(dangerSignPage.breathlessness('pregnancy')); await genericForm.nextPage(); - await pregnancyForm.selectYesNoOption(pregnancyForm.LLIN); + await genericForm.selectYesNoOption(pregnancyForm.LLIN); await genericForm.nextPage(); - await pregnancyForm.selectYesNoOption(pregnancyForm.IRON_FOLATE); + await genericForm.selectYesNoOption(pregnancyForm.IRON_FOLATE); await genericForm.nextPage(); - await pregnancyForm.selectYesNoOption(pregnancyForm.DEWORMING_MEDICATION); + await genericForm.selectYesNoOption(pregnancyForm.DEWORMING_MEDICATION); await genericForm.nextPage(); await genericForm.nextPage(); - await pregnancyForm.selectYesNoOption(pregnancyForm.HIV_TESTED); + await genericForm.selectYesNoOption(pregnancyForm.HIV_TESTED); await genericForm.nextPage(); const summaryDetails = await pregnancyForm.getSummaryDetails(); diff --git a/tests/e2e/standard/enketo/delivery.wdio-spec.js b/tests/e2e/standard/enketo/delivery.wdio-spec.js index 5797d853b98..f2fd2ab0636 100644 --- a/tests/e2e/standard/enketo/delivery.wdio-spec.js +++ b/tests/e2e/standard/enketo/delivery.wdio-spec.js @@ -76,12 +76,12 @@ describe('Delivery', () => { await deliveryForm.setNote(note); await genericForm.nextPage(); - expect(await deliveryForm.getOutcomeSummary()).to.equal(pregnancyOutcome); - expect(await deliveryForm.getLocationSummary()).to.equal(locationDelivery); - const followUpSMS = await deliveryForm.getFollowUpSMS(); - expect(followUpSMS).to.include(pregnantWoman1); - expect(followUpSMS).to.include(medicIDW1); - expect(followUpSMS).to.include(note); + const summaryDetails = await deliveryForm.getSummaryDetails(); + expect(summaryDetails.outcome).to.equal(pregnancyOutcome); + expect(summaryDetails.location).to.equal(locationDelivery); + expect(summaryDetails.followUpSmsNote2).to.include(pregnantWoman1); + expect(summaryDetails.followUpSmsNote2).to.include(medicIDW1); + expect(summaryDetails.followUpSmsNote2).to.include(note); await genericForm.submitForm(); await commonPage.waitForPageLoaded(); diff --git a/tests/e2e/standard/enketo/immunization-visit.wdio-spec.js b/tests/e2e/standard/enketo/immunization-visit.wdio-spec.js index 2000caa849c..8d9bb26efb1 100644 --- a/tests/e2e/standard/enketo/immunization-visit.wdio-spec.js +++ b/tests/e2e/standard/enketo/immunization-visit.wdio-spec.js @@ -53,6 +53,8 @@ describe('Immunization Visit', () => { }); it('Submit immunization visit - webapp', async () => { + const notes = 'Test notes - immunization visit'; + let countAppliedVaccines = 0; await loginPage.login(user); @@ -101,14 +103,15 @@ describe('Immunization Visit', () => { await genericForm.nextPage(); countAppliedVaccines += await immVisitForm.selectAppliedVaccines(immVisitForm.YELLOW_FEVER_VACCINE, 'yes'); await genericForm.nextPage(); - await immVisitForm.addNotes(); + await immVisitForm.addNotes(notes); await genericForm.nextPage(); - expect(await immVisitForm.getPatientNameSummaryPage()).to.equal(babyName); - expect(countAppliedVaccines).to.equal(await immVisitForm.getAppliedVaccinesSummary()); - expect(await immVisitForm.getFollowUpSMS()).to.include(babyName); - expect(await immVisitForm.getFollowUpSMS()).to.include(babyMedicID); - expect(await immVisitForm.getFollowUpSMS()).to.include(await immVisitForm.getNotes()); + const summaryDetails = await immVisitForm.getSummaryDetails(); + expect(summaryDetails.patientName).to.equal(babyName); + expect(summaryDetails.appliedVaccines).to.equal(countAppliedVaccines); + expect(summaryDetails.followUpSmsNote2).to.include(babyName); + expect(summaryDetails.followUpSmsNote2).to.include(babyMedicID); + expect(summaryDetails.followUpSmsNote2).to.include(notes); await genericForm.submitForm(); await commonPage.waitForPageLoaded(); @@ -147,7 +150,7 @@ describe('Immunization Visit', () => { expect((await reportsPage.getDetailReportRowContent('chw_sms')).rowValues[0]).to.equal('Nice work, ! ' + `${babyName} (${babyMedicID}) attended their immunizations visit at the health facility. ` + - 'Keep up the good work. Thank you! Test notes'); + `Keep up the good work. Thank you! ${notes}`); const { rowValues } = await reportsPage.getDetailReportRowContent('vaccines_received.'); rowValues.forEach(value => expect(value).to.equal('yes')); diff --git a/tests/e2e/standard/enketo/pregnancy-visit.wdio-spec.js b/tests/e2e/standard/enketo/pregnancy-visit.wdio-spec.js index 59fc3b0cf42..3655a4916f7 100644 --- a/tests/e2e/standard/enketo/pregnancy-visit.wdio-spec.js +++ b/tests/e2e/standard/enketo/pregnancy-visit.wdio-spec.js @@ -42,11 +42,11 @@ describe('Pregnancy Visit', () => { await pregnancyVisitForm.setNote(note); await genericForm.nextPage(); - expect((await pregnancyVisitForm.dangerSignSummary()).length).to.equal(dangerSigns.length); - const followUpSMS = await pregnancyVisitForm.getFollowUpSMS(); - expect(followUpSMS).to.include(pregnantWoman.name); - expect(followUpSMS).to.include(pregnantWoman.patient_id); - expect(followUpSMS).to.include(note); + const summaryDetails = await pregnancyVisitForm.getSummaryDetails(); + expect(summaryDetails.countDangerSigns).to.equal(dangerSigns.length); + expect(summaryDetails.followUpSmsNote2).to.include(pregnantWoman.name); + expect(summaryDetails.followUpSmsNote2).to.include(pregnantWoman.patient_id); + expect(summaryDetails.followUpSmsNote2).to.include(note); await genericForm.submitForm(); await commonPage.waitForPageLoaded(); diff --git a/tests/e2e/standard/enketo/pregnancy.wdio-spec.js b/tests/e2e/standard/enketo/pregnancy.wdio-spec.js index 41c46dcd076..e52685c7478 100644 --- a/tests/e2e/standard/enketo/pregnancy.wdio-spec.js +++ b/tests/e2e/standard/enketo/pregnancy.wdio-spec.js @@ -68,12 +68,12 @@ describe('New pregnancy', () => { await pregnancyForm.setNote(note); await genericForm.nextPage(); - expect((await pregnancyForm.riskFactorsSummary()).length).to.equal(riskFactors.length); - expect((await pregnancyForm.dangerSignsSummary()).length).to.equal(dangerSigns.length); - const followUpSMS = await pregnancyForm.getFollowUpSMS(); - expect(followUpSMS).to.include(pregnantWoman1); - expect(followUpSMS).to.include(medicIDW1); - expect(followUpSMS).to.include(note); + const summaryDetails = await pregnancyForm.getSummaryDetails(); + expect(summaryDetails.countRiskFactors).to.equal(riskFactors.length); + expect(summaryDetails.countDangerSigns).to.equal(dangerSigns.length); + expect(summaryDetails.followUpSmsNote2).to.include(pregnantWoman1); + expect(summaryDetails.followUpSmsNote2).to.include(medicIDW1); + expect(summaryDetails.followUpSmsNote2).to.include(note); await genericForm.submitForm(); await commonPage.waitForPageLoaded(); diff --git a/tests/e2e/wdio.conf.js b/tests/e2e/wdio.conf.js index cae302108fd..09e95754e0a 100644 --- a/tests/e2e/wdio.conf.js +++ b/tests/e2e/wdio.conf.js @@ -365,7 +365,7 @@ const baseConfig = { await utils.tearDownServices(); const reportError = new Error('Could not generate Allure report'); const timeoutError = new Error('Timeout generating report'); - const generation = allure(['generate', 'allure-results']); + const generation = allure(['generate', 'allure-results', '--clean']); return new Promise((resolve, reject) => { const generationTimeout = setTimeout( diff --git a/tests/page-objects/default/common/common.wdio.page.js b/tests/page-objects/default/common/common.wdio.page.js index 1b500994704..86646477066 100644 --- a/tests/page-objects/default/common/common.wdio.page.js +++ b/tests/page-objects/default/common/common.wdio.page.js @@ -67,7 +67,6 @@ const clickFastActionById = async (id) => { // Wait for the Angular Material's animation to complete. await browser.pause(500); await (await fastActionListContainer()).waitForDisplayed(); - await (await fastActionById(id)).scrollIntoView(); await (await fastActionById(id)).waitForClickable(); await (await fastActionById(id)).click(); }; diff --git a/tests/page-objects/default/contacts/contacts.wdio.page.js b/tests/page-objects/default/contacts/contacts.wdio.page.js index 72b5ed53abc..a247abb398d 100644 --- a/tests/page-objects/default/contacts/contacts.wdio.page.js +++ b/tests/page-objects/default/contacts/contacts.wdio.page.js @@ -19,14 +19,14 @@ const taskFilter = () => $(taskFilterSelector); const taskFilters = () => $$(taskFilterSelector); const contactList = () => $('#contacts-list'); const contactListLoadingStatus = () => $('#contacts-list .loading-status'); -const newPlaceName = () => $('[name="/data/init/custom_place_name"]'); const newPrimaryContactName = () => $('[name="/data/contact/name"]'); const newPrimaryContactButton = () => $('[name="/data/init/create_new_person"][value="new_person"]'); const dateOfBirthField = () => $('[placeholder="yyyy-mm-dd"]'); const sexField = (type, value) => $(`[data-name="/data/${type}/sex"][value="${value}"]`); const roleField = (type, role) => $(`[data-name="/data/${type}/role"][value="${role}"]`); const phoneField = () => $('input.ignore[type="tel"]'); -const personName = () => $('[name="/data/person/name"]'); +const nameField = (type) => $(`[name="/data/${type}/name"]`); +const customPlaceNameField = () => $('input[name="/data/init/custom_place_name"]'); const topContact = () => $('#contacts-list > ul > li:nth-child(1) > a > div.content > div > h4 > span'); const name = () => $('.children h4 span'); const externalIdField = (place) => $(`[name="/data/${place}/external_id"]`); @@ -53,7 +53,6 @@ const exportButton = () => $('.mat-mdc-menu-content .mat-mdc-menu-item[test-id=" const editContactButton = () => $('.mat-mdc-menu-content .mat-mdc-menu-item[test-id="edit-contacts"]'); const deleteContactButton = () => $('.mat-mdc-menu-content .mat-mdc-menu-item[test-id="delete-contacts"]'); const contactCards = () => $$('.card.children'); -const districtHospitalName = () => $('[name="/data/district_hospital/name"]'); const childrenCards = () => $$('.right-pane .card.children'); const contactCardTitle = () => $('.inbox .content-pane .material .body .action-header'); const contactInfoName = () => $('h2[test-id="contact-name"]'); @@ -156,16 +155,16 @@ rightSideAction = true,) => { } await (await newPrimaryContactButton()).waitForDisplayed(); await (await newPrimaryContactButton()).click(); - await (await newPrimaryContactName()).addValue(contactNameValue); - await (await phoneField()).addValue(phoneValue); - await (await dateOfBirthField()).addValue(dobValue); + await (await newPrimaryContactName()).setValue(contactNameValue); + await (await phoneField()).setValue(phoneValue); + await (await dateOfBirthField()).setValue(dobValue); await (await sexField('contact', sexValue)).click(); await (await roleField('contact', roleValue)).click(); await genericForm.nextPage(); await (await writeNamePlace(typeValue)).click(); - await (await newPlaceName()).addValue(placeNameValue); - await (await externalIdField(typeValue)).addValue(externalIDValue); - await (await notes(typeValue)).addValue(notesValue); + await (await customPlaceNameField()).setValue(placeNameValue); + await (await externalIdField(typeValue)).setValue(externalIDValue); + await (await notes(typeValue)).setValue(notesValue); await (await genericForm.submitButton()).waitForClickable(); await (await genericForm.submitButton()).click(); const dashedType = typeValue.replace('_', '-'); @@ -183,10 +182,9 @@ const addPerson = async ({ } = {}, waitForSentinel = true) => { const type = 'person'; await commonPage.clickFastActionFAB({ actionId: type }); - await (await genericForm.formTitle()).waitForDisplayed(); - await (await personName()).addValue(nameValue); + await (await nameField(type)).addValue(nameValue); await (await dateOfBirthField()).addValue(dobValue); - await (await genericForm.formTitle()).click(); // blur the datepicker field so the sex field is visible + await (await nameField(type)).click(); // blur the datepicker field so the sex field is visible await (await phoneField()).addValue(phoneValue); await (await sexField(type, sexValue)).click(); await (await roleField(type, roleValue)).click(); @@ -210,16 +208,13 @@ const editPerson = async (currentName, { name, phone, dob }) => { await (await genericForm.nextPage()); if (name !== undefined) { - await (await personName()).clearValue(); - await (await personName()).addValue(name); + await (await nameField('person')).setValue(name); } if (phone !== undefined) { - await (await phoneField()).clearValue(); - await (await phoneField()).addValue(phone); + await (await phoneField()).setValue(phone); } if (dob !== undefined) { - await (await dateOfBirthField()).clearValue(); - await (await dateOfBirthField()).addValue(dob); + await (await dateOfBirthField()).setValue(dob); } await submitForm(); @@ -285,7 +280,7 @@ const editDistrict = async (districtName, editedName) => { await (await editContactButton()).waitForClickable(); await (await editContactButton()).click(); - await (await districtHospitalName()).setValue(editedName); + await (await nameField('district_hospital')).setValue(editedName); // blur field to trigger Enketo validation await (await notes('district_hospital')).click(); await submitForm(); @@ -397,6 +392,20 @@ const getDisplayedContactsNames = async () => { } return contacts; }; + +const getCurrentPersonEditFormValues = async (sexValue, roleValue) => { + return { + name: await nameField('person').getValue(), + shortName: await $('[name="/data/person/short_name"]').getValue(), + dateOfBirth: await dateOfBirthField().getValue(), + sex: await sexField('person', sexValue).parentElement().getAttribute('data-checked'), + role: await roleField('person', roleValue).parentElement().getAttribute('data-checked'), + phone: await phoneField().getValue(), + externalId: await externalIdField('person').getValue(), + notes: await notes('person').getValue(), + }; +}; + module.exports = { genericForm, selectLHSRowByText, @@ -435,7 +444,6 @@ module.exports = { newPrimaryContactButton, newPrimaryContactName, writeNamePlace, - newPlaceName, externalIdField, notes, contactCardIcon, @@ -457,4 +465,11 @@ module.exports = { getVisitLabel, getNumberOfReports, getDisplayedContactsNames, + nameField, + customPlaceNameField, + dateOfBirthField, + phoneField, + sexField, + roleField, + getCurrentPersonEditFormValues, }; diff --git a/tests/page-objects/default/enketo/danger-sign.wdio.page.js b/tests/page-objects/default/enketo/danger-sign.wdio.page.js new file mode 100644 index 00000000000..c503deeabde --- /dev/null +++ b/tests/page-objects/default/enketo/danger-sign.wdio.page.js @@ -0,0 +1,47 @@ +const visitConfirmation = (form) => `input[name="/${form}/danger_signs/visit_confirm"]`; +const dangerSignsPresent = (form) => `input[name="/${form}/danger_signs/danger_sign_present"]`; +const infectedUmbilicalCord = (form) => `input[name="/${form}/danger_signs/infected_umbilical_cord"]`; +const convulsion = (form) => `input[name="/${form}/danger_signs/convulsion"]`; +const difficultyFeeding = (form) => `input[name="/${form}/danger_signs/difficulty_feeding"]`; +const vomit = (form) => `input[name="/${form}/danger_signs/vomit"]`; +const drowsy = (form) => `input[name="/${form}/danger_signs/drowsy"]`; +const stiffness = (form) => `input[name="/${form}/danger_signs/stiff"]`; +const yellowSkin = (form) => `input[name="/${form}/danger_signs/yellow_skin"]`; +const fever = (form) => `input[name="/${form}/danger_signs/fever"]`; +const blueSkin = (form) => `input[name="/${form}/danger_signs/blue_skin"]`; +const headache = (form) => `input[name="/${form}/danger_signs/severe_headache"]`; +const vaginalBleeding = (form) => `input[name="/${form}/danger_signs/vaginal_bleeding"]`; +const vaginalDischarge = (form) => `input[name="/${form}/danger_signs/vaginal_discharge"]`; +const fits = (form) => `input[name="/${form}/danger_signs/fits"]`; +const abdominalPain = (form) => `input[name="/${form}/danger_signs/severe_abdominal_pain"]`; +const veryPale = (form) => `input[name="/${form}/danger_signs/very_pale"]`; +const reduceFetalMov = (form) => `input[name="/${form}/danger_signs/reduced_or_no_fetal_movements"]`; +const breakingOfWater = (form) => `input[name="/${form}/danger_signs/breaking_water"]`; +const easilyTired = (form) => `input[name="/${form}/danger_signs/easily_tired"]`; +const swellingHands = (form) => `input[name="/${form}/danger_signs/face_hand_swelling"]`; +const breathlessness = (form) => `input[name="/${form}/danger_signs/breathlessness"]`; + +module.exports = { + visitConfirmation, + dangerSignsPresent, + infectedUmbilicalCord, + convulsion, + difficultyFeeding, + vomit, + drowsy, + stiffness, + yellowSkin, + fever, + blueSkin, + headache, + vaginalBleeding, + vaginalDischarge, + fits, + abdominalPain, + veryPale, + reduceFetalMov, + breakingOfWater, + easilyTired, + swellingHands, + breathlessness, +}; diff --git a/tests/page-objects/default/enketo/death-report.page.js b/tests/page-objects/default/enketo/death-report.page.js index 62c159fda0d..e797c5e8684 100644 --- a/tests/page-objects/default/enketo/death-report.page.js +++ b/tests/page-objects/default/enketo/death-report.page.js @@ -9,12 +9,15 @@ const SUMMARY_SECTION = `${FORM} section[name="/death_report/group_review"]`; const deathDate = () => $(`${FORM} section[name="/death_report/death_details"] input.ignore.input-small`); const deathPlace = (value) => $(`${FORM} input[name="/death_report/death_details/place_of_death"][value="${value}"]`); const deathInformation = () => $(`${FORM} textarea[name="/death_report/death_details/death_information"]`); -const patientNameSummary = () => $(SUMMARY_SECTION + - ' span[data-itext-id="/death_report/group_review/r_patient_details:label"]' + - ' span[data-value=" /death_report/patient_display_name "]'); +const patientNameSummary = () => { + return $(SUMMARY_SECTION + + ' span[data-itext-id="/death_report/group_review/r_patient_details:label"]' + + ' span[data-value=" /death_report/patient_display_name "]'); +}; const deathDateSummary = () => $(`${SUMMARY_SECTION} span[data-value=" /death_report/death_details/date_of_death "]`); -const deathInformationSummary = () => $(SUMMARY_SECTION + - ' span[data-value=" /death_report/death_details/death_information "]'); +const deathInformationSummary = () => { + return $(`${SUMMARY_SECTION} span[data-value=" /death_report/death_details/death_information "]`); +}; const setDeathDate = async (value = moment().format('YYYY-MM-DD')) => { const date = await deathDate(); @@ -52,7 +55,18 @@ const submitDeathReport = async ({ await setDeathDate(deathDateValue); await genericForm.nextPage(); await genericForm.submitForm(); +}; +const getLabelsValues = async () => { + return { + details: await $('span[data-itext-id="/death_report/death_details:label"].active').getText(), + date: await $('span[data-itext-id="/death_report/death_details/date_of_death:label"].active').getText(), + place: await $('span[data-itext-id="/death_report/death_details/place_of_death:label"].active').getText(), + healthFacility: await deathPlace(PLACE_OF_DEATH.healthFacility).nextElement().getText(), + home: await deathPlace(PLACE_OF_DEATH.home).nextElement().getText(), + other: await deathPlace(PLACE_OF_DEATH.other).nextElement().getText(), + notes: await $('span[data-itext-id="/death_report/death_details/death_information:label"].active').getText(), + }; }; module.exports = { @@ -62,4 +76,5 @@ module.exports = { setDeathInformation, getSummaryDetails, submitDeathReport, + getLabelsValues, }; diff --git a/tests/page-objects/default/enketo/delivery.wdio.page.js b/tests/page-objects/default/enketo/delivery.wdio.page.js index 9e302ec5fbc..419d8ce16c7 100644 --- a/tests/page-objects/default/enketo/delivery.wdio.page.js +++ b/tests/page-objects/default/enketo/delivery.wdio.page.js @@ -1,90 +1,71 @@ -const deliveryConditionWomanOutcomeField = (value) => - $(`input[type="radio"][name="/delivery/condition/woman_outcome"][value="${value}"]`); -const deliveryPostnatalDangerFeverField = (value) => - $(`input[type="radio"][name="/delivery/pnc_danger_sign_check/fever"][value="${value}"]`); -const deliveryPostnatalDangerSevereHeadacheField = (value) => - $(`input[type="radio"][name="/delivery/pnc_danger_sign_check/severe_headache"][value="${value}"]`); -const deliveryPostnatalDangerVaginalBleedingField = (value) => - $(`input[type="radio"][name="/delivery/pnc_danger_sign_check/vaginal_bleeding"][value="${value}"]`); -const deliveryPostnatalDangerVaginalDischargeField = (value) => - $(`input[type="radio"][name="/delivery/pnc_danger_sign_check/vaginal_discharge"][value="${value}"]`); -const deliveryPostnatalDangerConvulsionField = (value) => - $(`input[type="radio"][name="/delivery/pnc_danger_sign_check/convulsion"][value="${value}"]`); -const deliveryOutcomeBabiesDeliveredField = (value) => - $(`input[type="radio"][name="/delivery/delivery_outcome/babies_delivered"][value="${value}"]`); -const deliveryOutcomeBabiesAliveField = (value) => - $(`input[type="radio"][name="/delivery/delivery_outcome/babies_alive"][value="${value}"]`); -const dateOfDeliveryField = () => - $('form > section.or-group.or-branch.or-appearance-field-list.current > label:nth-child(6) > div > input'); -const deliveryPlaceField = (value) => - $(`input[type="radio"][name="/delivery/delivery_outcome/delivery_place"][value="${value}"]`); -const deliveryModeField = (value) => - $(`input[type="radio"][name="/delivery/delivery_outcome/delivery_mode"][value="${value}"]`); -const babyConditionField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/baby_condition"][value="${value}"]`); -const babyNameField = () => - $(`input[type="text"][name="/delivery/babys_condition/baby_repeat/baby_details/baby_name"]`); -const babySexField = (value) => - $(`input[type="radio"][data-name="/delivery/babys_condition/baby_repeat/baby_details/baby_sex"][value="${value}"]`); -const babyBirthWeightKnowField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/birth_weight_know"][value="${value}"]`); -const babyBirthLengthKnowField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/birth_length_know"][value="${value}"]`); -const babyVaccinesReveivedField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/vaccines_received"][value="${value}"]`); -const babyBreastfeedingField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/breastfeeding"][value="${value}"]`); -const babyBreastfeedingWithin1HourField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/breastfed_within_1_hour"][value="${value}"]`); -const babyInfectedUmbilicalCordField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/infected_umbilical_cord"][value="${value}"]`); -const babyConvulsionField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/convulsion"][value="${value}"]`); -const babyDifficultyFeedingField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/difficulty_feeding"][value="${value}"]`); -const babyVomitField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/vomit"][value="${value}"]`); -const babyDrowsyField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/drowsy"][value="${value}"]`); -const babyStiffField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/stiff"][value="${value}"]`); -const babyYellowSkinField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/yellow_skin"][value="${value}"]`); -const babyFeverField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/fever"][value="${value}"]`); -const babyBlueSkinField = (value) => - $(`input[type="radio"]` + - `[data-name="/delivery/babys_condition/baby_repeat/baby_details/blue_skin"][value="${value}"]`); -const deliveryPncVisitsField = (value) => - $(`input[type="checkbox"]` + - `[name="/delivery/pnc_visits/pnc_visits_attended"][value="${value}"]`); +const moment = require('moment'); + +const getField = (type, name, expression = '') => $(`input[type="${type}"][name="/delivery/${name}"]${expression}`); +const getRadioField = (name, value) => getField('radio', name, `[value="${value}"]`); + +const deliveryConditionWomanOutcomeField = (value) => getRadioField('condition/woman_outcome', value); +const deliveryPostnatalDangerFeverField = (value) => getRadioField('pnc_danger_sign_check/fever', value); +const deliveryPostnatalDangerSevereHeadacheField = (value) => { + return getRadioField('pnc_danger_sign_check/severe_headache', value); +}; +const deliveryPostnatalDangerVaginalBleedingField = (value) => { + return getRadioField('pnc_danger_sign_check/vaginal_bleeding', value); +}; +const deliveryPostnatalDangerVaginalDischargeField = (value) => { + return getRadioField('pnc_danger_sign_check/vaginal_discharge', value); +}; +const deliveryPostnatalDangerConvulsionField = (value) => getRadioField('pnc_danger_sign_check/convulsion', value); +const deliveryOutcomeBabiesDeliveredField = (value) => getRadioField('delivery_outcome/babies_delivered', value); +const deliveryOutcomeBabiesAliveField = (value) => getRadioField('delivery_outcome/babies_alive', value); +const dateOfDeliveryField = () => { + return $('form > section.or-group.or-branch.or-appearance-field-list.current > label:nth-child(6) > div > input'); +}; +const deliveryPlaceField = (value) => getRadioField('delivery_outcome/delivery_place', value); +const deliveryModeField = (value) => getRadioField('delivery_outcome/delivery_mode', value); + +const BABY_DETAILS_NAME = 'babys_condition/baby_repeat/baby_details/'; +const getBabyDetailsField = (name, value) => { + return $(`input[type="radio"][data-name="/delivery/${BABY_DETAILS_NAME}${name}"][value="${value}"]`); +}; +const babyConditionField = (value) => getBabyDetailsField(`baby_condition`, value); +const babyNameField = () => getField('text', `${BABY_DETAILS_NAME}baby_name`); +const babySexField = (value) => getBabyDetailsField(`baby_sex`, value); +const babyBirthWeightKnowField = (value) => getBabyDetailsField(`birth_weight_know`, value); +const babyBirthLengthKnowField = (value) => getBabyDetailsField(`birth_length_know`, value); +const babyVaccinesReveivedField = (value) => getBabyDetailsField(`vaccines_received`, value); +const babyBreastfeedingField = (value) => getBabyDetailsField(`breastfeeding`, value); +const babyBreastfeedingWithin1HourField = (value) => getBabyDetailsField(`breastfed_within_1_hour`, value); +const babyInfectedUmbilicalCordField = (value) => getBabyDetailsField(`infected_umbilical_cord`, value); +const babyConvulsionField = (value) => getBabyDetailsField(`convulsion`, value); +const babyDifficultyFeedingField = (value) => getBabyDetailsField(`difficulty_feeding`, value); +const babyVomitField = (value) => getBabyDetailsField(`vomit`, value); +const babyDrowsyField = (value) => getBabyDetailsField(`drowsy`, value); +const babyStiffField = (value) => getBabyDetailsField(`stiff`, value); +const babyYellowSkinField = (value) => getBabyDetailsField(`yellow_skin`, value); +const babyFeverField = (value) => getBabyDetailsField(`fever`, value); +const babyBlueSkinField = (value) => getBabyDetailsField(`blue_skin`, value); +const deliveryPncVisitsField = (value) => getField('checkbox', 'pnc_visits/pnc_visits_attended', `[value="${value}"]`); const SUMMARY_SECTION = 'section[name="/delivery/summary"]'; -const sumPatientName = () => $(`${SUMMARY_SECTION} span[data-value=" /delivery/patient_name "]`); -const sumPatientAge = () => $(`${SUMMARY_SECTION} span[data-value=" /delivery/patient_age_in_years "]`); -const sumWomanCondition = () => $(`${SUMMARY_SECTION} span[data-itext-id="/delivery/summary/r_condition_well:label"]`); -const sumDeliveryDate = () => $(`${SUMMARY_SECTION} span[data-value=" /delivery/delivery_outcome/delivery_date "]`); -const sumDeliveryPlace = () => $(SUMMARY_SECTION + - ' span[data-value=" /delivery/summary/custom_translations/delivery_place_label "]'); -const sumDeliveredBabies = () => $(SUMMARY_SECTION + - ' span[data-value=" /delivery/delivery_outcome/babies_delivered_num "]'); -const sumDeceasedBabies = () => $(SUMMARY_SECTION + - ' span[data-value=" /delivery/delivery_outcome/babies_deceased_num "]'); -const sumPncVisits = () => $(`${SUMMARY_SECTION} span[data-itext-id="/delivery/summary/r_pnc_visit_none:label"]`); +const getSummaryField = (name) => $(`${SUMMARY_SECTION} span[data-value=" /delivery/${name} "]`); +const getSummaryLabel = (name) => $(`${SUMMARY_SECTION} span[data-itext-id="/delivery/summary/${name}:label"]`); +const sumPatientName = () => getSummaryField('patient_name'); +const sumPatientAge = () => getSummaryField('patient_age_in_years'); +const sumWomanCondition = () => getSummaryLabel('r_condition_well'); +const sumDeliveryDate = () => getSummaryField('delivery_outcome/delivery_date'); +const sumDeliveryPlace = () => getSummaryField('summary/custom_translations/delivery_place_label'); +const sumDeliveredBabies = () => getSummaryField('delivery_outcome/babies_delivered_num'); +const sumDeceasedBabies = () => getSummaryField('delivery_outcome/babies_deceased_num'); +const sumPncVisits = () => getSummaryLabel('r_pnc_visit_none'); + +const womanDeathDate = () => $('section[name="/delivery/death_info_woman"] .widget.date .input-small'); +const womanDeathPlace = (value) => { + return $(`input[data-name="/delivery/death_info_woman/woman_death_place"][value="${value}"]`); +}; +const womanDeliveredBabies = (value) => { + return $(`input[data-name="/delivery/death_info_woman/woman_death_birth"][value="${value}"]`); +}; +const womanDeathNote = () => $('input[name="/delivery/death_info_woman/woman_death_add_notes"]'); const selectDeliveryConditionWomanOutcome = async (value) => { const womanOutcome = await deliveryConditionWomanOutcomeField(value); @@ -273,6 +254,18 @@ const getSummaryInfo = async () => { }; }; +const fillWomanDeathInformation = async ({ + date: dateValue = moment().format('YYYY-MM-DD'), + place: placeValue = 'health_facility', + deliveredBabies: deliveredBabiesValue = 'no', + notes: notesValue = 'Test notes - Mother\'s death ' +} = {}) => { + await womanDeathPlace(placeValue).click(); + await womanDeliveredBabies(deliveredBabiesValue).click(); + await womanDeathNote().setValue(notesValue); + await womanDeathDate().setValue(dateValue); +}; + module.exports = { selectDeliveryConditionWomanOutcome, selectDeliveryPostnatalDangerSignsFever, @@ -304,4 +297,5 @@ module.exports = { selectDeliveryBabyBlueSkin, selectDeliveryPncVisits, getSummaryInfo, + fillWomanDeathInformation, }; diff --git a/tests/page-objects/default/enketo/enketo-widgets.wdio.page.js b/tests/page-objects/default/enketo/enketo-widgets.wdio.page.js index 37cf0619fac..35b63f28cf4 100644 --- a/tests/page-objects/default/enketo/enketo-widgets.wdio.page.js +++ b/tests/page-objects/default/enketo/enketo-widgets.wdio.page.js @@ -3,12 +3,15 @@ const FORM = 'form[data-form-id="enketo_widgets"]'; const formTitle = () => $(`${FORM} #form-title`); const selectMultipleDropdown = () => $(`${FORM} select[name="/enketo_widgets/enketo_test_select/select_spinner"]`); const selectOneDropdown = () => $(`${FORM} select[name="/enketo_widgets/enketo_test_select/select1_spinner"]`); -const countryRadio = (value) => $(FORM + - ` input[name="/enketo_widgets/cascading_widgets/group1/country"][value="${value}"]`); -const cityRadio = (value) => $(FORM + - ` input[name="/enketo_widgets/cascading_widgets/group1/city"][value="${value}"]`); -const neighborhoodRadio = (value) => $(FORM + - ` input[name="/enketo_widgets/cascading_widgets/group1/neighborhood"][value="${value}"]`); +const countryRadio = (value) => { + return $(`${FORM} input[name="/enketo_widgets/cascading_widgets/group1/country"][value="${value}"]`); +}; +const cityRadio = (value) => { + return $(`${FORM} input[name="/enketo_widgets/cascading_widgets/group1/city"][value="${value}"]`); +}; +const neighborhoodRadio = (value) => { + return $(`${FORM} input[name="/enketo_widgets/cascading_widgets/group1/neighborhood"][value="${value}"]`); +}; const countryDropdown = () => $(`${FORM} select[name="/enketo_widgets/cascading_widgets/group2/country2"]`); const cityDropdown = () => $(`${FORM} select[name="/enketo_widgets/cascading_widgets/group2/city2"]`); const neighborhoodDropdown = () => $(`${FORM} select[name="/enketo_widgets/cascading_widgets/group2/neighborhood2"]`); @@ -17,10 +20,12 @@ const patientId = () => $(`${FORM} input[name="/enketo_widgets/inputs/contact/pa const patientName = () => $(`${FORM} input[name="/enketo_widgets/inputs/contact/name"]`); const patientNameErrorLabel = () => $(`${FORM} label.invalid-constraint`); const phoneField = () => $('input.ignore[type="tel"]:has(+ input[name="/enketo_widgets/enketo_test_select/phone"])'); -const phoneFieldRequiredMessage = () => - $('input[name="/enketo_widgets/enketo_test_select/phone"] ~ .or-required-msg.active'); -const phoneFieldConstraintMessage = () => - $('input[name="/enketo_widgets/enketo_test_select/phone"] ~ .or-constraint-msg.active'); +const phoneFieldRequiredMessage = () => { + return $('input[name="/enketo_widgets/enketo_test_select/phone"] ~ .or-required-msg.active'); +}; +const phoneFieldConstraintMessage = () => { + return $('input[name="/enketo_widgets/enketo_test_select/phone"] ~ .or-constraint-msg.active'); +}; const getFormTitle = async () => { const title = await formTitle(); @@ -91,8 +96,9 @@ const setPatientName = async (value = 'Emilio') => { }; const setPhoneNumber = async (value) => { - await phoneField().waitForDisplayed(); - await (await phoneField()).setValue(value); + const phone = await phoneField(); + await phone.waitForDisplayed(); + await (phone).setValue(value); }; module.exports = { diff --git a/tests/page-objects/default/enketo/generic-form.wdio.page.js b/tests/page-objects/default/enketo/generic-form.wdio.page.js index 8db53b3ac0c..e15590726ea 100644 --- a/tests/page-objects/default/enketo/generic-form.wdio.page.js +++ b/tests/page-objects/default/enketo/generic-form.wdio.page.js @@ -99,6 +99,13 @@ const getFormTitle = async () => { return await (await formTitle()).getText(); }; +const selectYesNoOption = async (selector, value = 'yes') => { + const element = await $(`${selector}[value="${value}"]`); + await element.waitForDisplayed(); + await element.click(); + return value === 'yes'; +}; + module.exports = { getFormTitle, getErrorMessage, @@ -116,4 +123,5 @@ module.exports = { submitForm, currentFormView, formTitle, + selectYesNoOption, }; diff --git a/tests/page-objects/default/enketo/pregnancy-danger-sign.wdio.page.js b/tests/page-objects/default/enketo/pregnancy-danger-sign-follow-up.wdio.page.js similarity index 100% rename from tests/page-objects/default/enketo/pregnancy-danger-sign.wdio.page.js rename to tests/page-objects/default/enketo/pregnancy-danger-sign-follow-up.wdio.page.js diff --git a/tests/page-objects/default/enketo/pregnancy-facility-visit-reminder.wdio.page.js b/tests/page-objects/default/enketo/pregnancy-facility-visit-reminder.wdio.page.js index 78c53fd8336..aaf96d50261 100644 --- a/tests/page-objects/default/enketo/pregnancy-facility-visit-reminder.wdio.page.js +++ b/tests/page-objects/default/enketo/pregnancy-facility-visit-reminder.wdio.page.js @@ -1,12 +1,15 @@ const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); -const FORM = 'form[data-form-id="pregnancy_facility_visit_reminder"]'; +const FORM_ID = 'pregnancy_facility_visit_reminder'; +const FORM = `form[data-form-id="${FORM_ID}"]`; const formTitle = () => $(`${FORM} #form-title`); -const ancVisitDate = () => $(FORM + - ' span[data-value=" /pregnancy_facility_visit_reminder/visit_date_for_task "]'); -const reminderMethod = (value) => $(FORM + - ` input[name="/pregnancy_facility_visit_reminder/facility_visit_reminder/remind_method"][value="${value}"]`); +const ancVisitDate = () => { + return $(`${FORM} span[data-value=" /${FORM_ID}/visit_date_for_task "]`); +}; +const reminderMethod = (value) => { + return $(`${FORM} input[name="/${FORM_ID}/facility_visit_reminder/remind_method"][value="${value}"]`); +}; const getAncReminderInfo = async () => { return { @@ -15,14 +18,19 @@ const getAncReminderInfo = async () => { }; }; -const submitAncReminder = async (method = 'in_person') => { +const selectReminderMethod = async (method = 'in_person') => { const reminderAnc = await reminderMethod(method); await reminderAnc.waitForClickable(); await reminderAnc.click(); +}; + +const submitAncReminder = async (method) => { + await selectReminderMethod(method); await genericForm.submitForm(); }; module.exports = { + selectReminderMethod, getAncReminderInfo, submitAncReminder, }; diff --git a/tests/page-objects/default/enketo/pregnancy-visit.wdio.page.js b/tests/page-objects/default/enketo/pregnancy-visit.wdio.page.js index c85fea9e2bb..7cba054aec1 100644 --- a/tests/page-objects/default/enketo/pregnancy-visit.wdio.page.js +++ b/tests/page-objects/default/enketo/pregnancy-visit.wdio.page.js @@ -14,10 +14,23 @@ const formDocument = { } } }; -const dangerSignLabel = () => - $('label.question.readonly.or-branch.non-select.or-appearance-h1.or-appearance-red > span.question-label'); -const dangerSignSummary = () => - $$('label.question.readonly.or-branch.non-select.or-appearance-li'); + +const FORM = 'form[data-form-id="pregnancy_home_visit"]'; +const LLIN = 'input[name="/pregnancy_home_visit/safe_pregnancy_practices/malaria/llin_use"]'; +const IRON_FOLATE = 'input[name="/pregnancy_home_visit/safe_pregnancy_practices/iron_folate/iron_folate_daily"]'; +const HIV_TESTED = 'input[name="/pregnancy_home_visit/safe_pregnancy_practices/hiv_status/hiv_tested"]'; + +const visitOption = (value) => { + return $(`${FORM} input[name="/pregnancy_home_visit/pregnancy_summary/visit_option"][value="${value}"]`); +}; +const gestationalAgeCorrect = (value) => { + return $(`${FORM} input[name="/pregnancy_home_visit/pregnancy_summary/g_age_correct"][value="${value}"]`); +}; + +const dangerSignLabel = () => { + return $('label.question.readonly.or-branch.non-select.or-appearance-h1.or-appearance-red > span.question-label'); +}; +const dangerSignSummary = () => $$('label.question.readonly.or-branch.non-select.or-appearance-li'); const followUpMessage = () => $('[data-value=" /pregnancy_visit/chw_sms "]'); const selectPatient = async (patientName) => { @@ -43,7 +56,29 @@ const uploadPregnancyVisitForm = async () => { await utils.saveDoc(formDocument); }; +const selectVisitOption = async (value = 'yes') => { + const option = await visitOption(value); + await option.waitForClickable(); + await option.click(); +}; + +const confirmGestationalAge = async (value = 'yes') => { + const gestationalAge = await gestationalAgeCorrect(value); + await gestationalAge.waitForClickable(); + await gestationalAge.click(); +}; + +const countSummaryDangerSigns = async () => { + return await $$( + 'section[name="/pregnancy_home_visit/summary"] ' + + ':not(label.disabled) span[data-itext-id*="/pregnancy_home_visit/summary/r_danger_sign_"]' + ).length; +}; + module.exports = { + LLIN, + IRON_FOLATE, + HIV_TESTED, selectPatient, selectDangerSign, selectAllDangerSigns, @@ -51,5 +86,8 @@ module.exports = { dangerSignLabel, dangerSignSummary, followUpMessage, - uploadPregnancyVisitForm + uploadPregnancyVisitForm, + selectVisitOption, + confirmGestationalAge, + countSummaryDangerSigns, }; diff --git a/tests/page-objects/default/enketo/pregnancy.wdio.page.js b/tests/page-objects/default/enketo/pregnancy.wdio.page.js index 91514163520..2c72a72e740 100644 --- a/tests/page-objects/default/enketo/pregnancy.wdio.page.js +++ b/tests/page-objects/default/enketo/pregnancy.wdio.page.js @@ -9,6 +9,7 @@ const moment = require('moment'); const genericForm = require('@page-objects/default/enketo/generic-form.wdio.page'); const commonPage = require('@page-objects/default/common/common.wdio.page'); +const dangerSignPage = require('@page-objects/default/enketo/danger-sign.wdio.page'); const GESTATION_AGE = {lmp: 'method_lmp', lmpApprox: 'method_approx', edd: 'method_edd', none: 'none'}; const FIRST_PREGNANCY_VALUE = {yes: 'primary', no: 'secondary'}; @@ -20,43 +21,43 @@ const KNOWN_FUTURE_VISITS = const FIRST_PREGNANCY = 'input[data-name="/pregnancy/risk_factors/risk_factors_history/first_pregnancy"]'; const MISCARRIAGE = 'input[name="/pregnancy/risk_factors/risk_factors_history/previous_miscarriage"]'; const ADDITIONAL_FACTORS = 'input[name="/pregnancy/risk_factors/risk_factors_present/additional_risk_check"]'; -const VAGINAL_BLEEDING = 'input[name="/pregnancy/danger_signs/vaginal_bleeding"]'; -const FITS = 'input[name="/pregnancy/danger_signs/fits"]'; -const ABDOMINAL_PAIN = 'input[name="/pregnancy/danger_signs/severe_abdominal_pain"]'; -const HEADACHE = 'input[name="/pregnancy/danger_signs/severe_headache"]'; -const VERY_PALE = 'input[name="/pregnancy/danger_signs/very_pale"]'; -const FEVER = 'input[name="/pregnancy/danger_signs/fever"]'; -const REDUCE_FETAL_MOV = 'input[name="/pregnancy/danger_signs/reduced_or_no_fetal_movements"]'; -const BREAKING_OF_WATER = 'input[name="/pregnancy/danger_signs/breaking_water"]'; -const EASILY_TIRED = 'input[name="/pregnancy/danger_signs/easily_tired"]'; -const SWELLING_HANDS = 'input[name="/pregnancy/danger_signs/face_hand_swelling"]'; -const BREATHLESSNESS = 'input[name="/pregnancy/danger_signs/breathlessness"]'; const LLIN = 'input[name="/pregnancy/safe_pregnancy_practices/malaria/uses_llin"]'; const IRON_FOLATE = 'input[name="/pregnancy/safe_pregnancy_practices/iron_folate/iron_folate_daily"]'; const DEWORMING_MEDICATION = 'input[name="/pregnancy/safe_pregnancy_practices/deworming/deworming_med"]'; const HIV_TESTED = 'input[name="/pregnancy/safe_pregnancy_practices/hiv_status/hiv_tested"]'; -const gestationalAge = (value) => $(`${FORM} input[name="/pregnancy/gestational_age/register_method/lmp_method"]` + - `[value="${value}"]`); +const gestationalAge = (value) => { + return $(`${FORM} input[name="/pregnancy/gestational_age/register_method/lmp_method"][value="${value}"]`); +}; const deliveryDate = () => $(`${FORM} section[name="/pregnancy/gestational_age/method_edd"] input.ignore.input-small`); const ancVisitsPast = () => $(`${FORM} input[name="/pregnancy/anc_visits_hf/anc_visits_hf_past/visited_hf_count"]`); -const eddConfirmation = () => $(`${FORM} ` + - `span[data-itext-id="/pregnancy/gestational_age/method_lmp_summary/u_edd_note:label"] ` + - `span[data-value=" /pregnancy/gestational_age/g_edd "]`); -const weeksPregnantConfirmation = () => $(`${FORM} ` + - `span[data-itext-id="/pregnancy/gestational_age/method_lmp_summary/lmp_note:label"] ` + - `span[data-value=" /pregnancy/weeks_since_lmp_rounded "] `); -const futureVisitDate = () => $(`${FORM} ` + - `section[name="/pregnancy/anc_visits_hf/anc_visits_hf_next/anc_next_visit_date"] input.ignore.input-small`); -const riskFactors = (value) => $$(`${FORM} ` + - `input[name="/pregnancy/risk_factors/risk_factors_present/${value}_condition"]`); +const eddConfirmation = () => { + return $(FORM + + ' span[data-itext-id="/pregnancy/gestational_age/method_lmp_summary/u_edd_note:label"]' + + ' span[data-value=" /pregnancy/gestational_age/g_edd "]'); +}; +const weeksPregnantConfirmation = () => { + return $(FORM + + ' span[data-itext-id="/pregnancy/gestational_age/method_lmp_summary/lmp_note:label"]' + + ' span[data-value=" /pregnancy/weeks_since_lmp_rounded "]'); +}; +const futureVisitDate = () => { + return $(FORM + + ' section[name="/pregnancy/anc_visits_hf/anc_visits_hf_next/anc_next_visit_date"] input.ignore.input-small'); +}; +const riskFactors = (value) => { + return $$(`${FORM} input[name="/pregnancy/risk_factors/risk_factors_present/${value}_condition"]`); +}; const patientNameSummary = () => $(`${SUMMARY_SECTION} span[data-value=" /pregnancy/patient_name "]`); const weeksPregnantSummary = () => $(`${SUMMARY_SECTION} span[data-value=" /pregnancy/weeks_since_lmp_rounded "]`); const eddSummary = () => $(`${SUMMARY_SECTION} span[data-value=" /pregnancy/summary/edd_summary "]`); -const riskFactorsSummary = () => $$(`${SUMMARY_SECTION} ` + - `:not(label.disabled):not(label.or-appearance-h3) span[data-itext-id*="/pregnancy/summary/r_risk"]`); -const dangerSignsSummary = () => $$(`${SUMMARY_SECTION} ` + - `:not(label.disabled) span[data-itext-id*="/pregnancy/summary/r_danger_sign_"]`); +const riskFactorsSummary = () => { + return $$(SUMMARY_SECTION + + ' :not(label.disabled):not(label.or-appearance-h3) span[data-itext-id*="/pregnancy/summary/r_risk"]'); +}; +const dangerSignsSummary = () => { + return $$(`${SUMMARY_SECTION} :not(label.disabled) span[data-itext-id*="/pregnancy/summary/r_danger_sign_"]`); +}; const selectGestationAge = async (value = GESTATION_AGE.edd) => { const getAge = await gestationalAge(value); @@ -90,13 +91,6 @@ const setFutureVisitDate = async (value = moment().add(1, 'day').format('YYYY-MM await date.setValue(value); }; -const selectYesNoOption = async (selector, value = 'yes') => { - const element = await $(`${FORM} ${selector}[value="${value}"]`); - await element.waitForDisplayed(); - await element.click(); - return value === 'yes'; -}; - // The selector for the 'riskFactors' is different depending on the option selected in the 'first pregnancy' question const selectAllRiskFactors = async (value = FIRST_PREGNANCY_VALUE.no) => { const cbRisks = await riskFactors(value); @@ -152,35 +146,35 @@ const submitPregnancy = async ({ await genericForm.nextPage(); await setANCVisitsPast(ancVisitPastValue); await genericForm.nextPage(); - await selectYesNoOption(KNOWN_FUTURE_VISITS, knowFutureVisitsValue); + await genericForm.selectYesNoOption(KNOWN_FUTURE_VISITS, knowFutureVisitsValue); await setFutureVisitDate(futureVisitDateValue); await genericForm.nextPage(); - await selectYesNoOption(FIRST_PREGNANCY, firstPregnancyValue); - await selectYesNoOption(MISCARRIAGE, miscarriageValue); + await genericForm.selectYesNoOption(FIRST_PREGNANCY, firstPregnancyValue); + await genericForm.selectYesNoOption(MISCARRIAGE, miscarriageValue); await genericForm.nextPage(); await selectAllRiskFactors(firstPregnancySelectedValue); - await selectYesNoOption(ADDITIONAL_FACTORS, aditionalFactorsValue); + await genericForm.selectYesNoOption(ADDITIONAL_FACTORS, aditionalFactorsValue); await genericForm.nextPage(); - await selectYesNoOption(VAGINAL_BLEEDING, vaginalBleedingValue); - await selectYesNoOption(FITS, fitsValue); - await selectYesNoOption(ABDOMINAL_PAIN, abdominalPainValue); - await selectYesNoOption(HEADACHE, headacheValue); - await selectYesNoOption(VERY_PALE, veryPaleValue); - await selectYesNoOption(FEVER, feverValue); - await selectYesNoOption(REDUCE_FETAL_MOV, reduceFetalMovValue); - await selectYesNoOption(BREAKING_OF_WATER, breakingWaterValue); - await selectYesNoOption(EASILY_TIRED, easilyTiredValue); - await selectYesNoOption(SWELLING_HANDS, swellingHandsValue); - await selectYesNoOption(BREATHLESSNESS, breathlessnessValue); + await genericForm.selectYesNoOption(dangerSignPage.vaginalBleeding('pregnancy'), vaginalBleedingValue); + await genericForm.selectYesNoOption(dangerSignPage.fits('pregnancy'), fitsValue); + await genericForm.selectYesNoOption(dangerSignPage.abdominalPain('pregnancy'), abdominalPainValue); + await genericForm.selectYesNoOption(dangerSignPage.headache('pregnancy'), headacheValue); + await genericForm.selectYesNoOption(dangerSignPage.veryPale('pregnancy'), veryPaleValue); + await genericForm.selectYesNoOption(dangerSignPage.fever('pregnancy'), feverValue); + await genericForm.selectYesNoOption(dangerSignPage.reduceFetalMov('pregnancy'), reduceFetalMovValue); + await genericForm.selectYesNoOption(dangerSignPage.breakingOfWater('pregnancy'), breakingWaterValue); + await genericForm.selectYesNoOption(dangerSignPage.easilyTired('pregnancy'), easilyTiredValue); + await genericForm.selectYesNoOption(dangerSignPage.swellingHands('pregnancy'), swellingHandsValue); + await genericForm.selectYesNoOption(dangerSignPage.breathlessness('pregnancy'), breathlessnessValue); await genericForm.nextPage(); - await selectYesNoOption(LLIN, llinValue); + await genericForm.selectYesNoOption(LLIN, llinValue); await genericForm.nextPage(); - await selectYesNoOption(IRON_FOLATE, ironFolateValue); + await genericForm.selectYesNoOption(IRON_FOLATE, ironFolateValue); await genericForm.nextPage(); - await selectYesNoOption(DEWORMING_MEDICATION, dewormingMedicationValue); + await genericForm.selectYesNoOption(DEWORMING_MEDICATION, dewormingMedicationValue); await genericForm.nextPage(); await genericForm.nextPage(); - await selectYesNoOption(HIV_TESTED, hivTestedValue); + await genericForm.selectYesNoOption(HIV_TESTED, hivTestedValue); await genericForm.nextPage(); await genericForm.submitForm(); }; @@ -192,17 +186,6 @@ module.exports = { FIRST_PREGNANCY, MISCARRIAGE, ADDITIONAL_FACTORS, - VAGINAL_BLEEDING, - FITS, - ABDOMINAL_PAIN, - HEADACHE, - VERY_PALE, - FEVER, - REDUCE_FETAL_MOV, - BREAKING_OF_WATER, - EASILY_TIRED, - SWELLING_HANDS, - BREATHLESSNESS, LLIN, IRON_FOLATE, DEWORMING_MEDICATION, @@ -211,7 +194,6 @@ module.exports = { setDeliveryDate, getConfirmationDetails, setANCVisitsPast, - selectYesNoOption, setFutureVisitDate, selectAllRiskFactors, submitPregnancy, diff --git a/tests/page-objects/default/enketo/replace-user.wdio.page.js b/tests/page-objects/default/enketo/replace-user.wdio.page.js index d7a51a5569b..d49632a35fc 100644 --- a/tests/page-objects/default/enketo/replace-user.wdio.page.js +++ b/tests/page-objects/default/enketo/replace-user.wdio.page.js @@ -8,6 +8,8 @@ const fullNameField = () => $('input[name="/replace_user/new_contact/name"]'); const dobUnknownField = () => $('input[name="/replace_user/new_contact/ephemeral_dob/dob_method"]'); const yearsField = () => $('input[name="/replace_user/new_contact/ephemeral_dob/age_years"]'); const sexField = (sex) => $(`input[name="/replace_user/new_contact/sex"][value="${sex}"]`); +const warningMsgPhone = () => $('span[data-value=" /replace_user/new_contact_phone "]'); +const warningMsgContactName = () => $('span[data-value=" /replace_user/new_contact_name "]'); const selectAdminCode = async (code) => { await (await adminCodeField()).waitForDisplayed(); @@ -34,11 +36,19 @@ const selectContactSex = async (sex) => { await (await sexField(sex)).click(); }; +const getWarningMsgDetails = async () => { + return { + phone: await warningMsgPhone().getText(), + contactName: await warningMsgContactName().getText(), + }; +}; + module.exports = { + SEX, selectAdminCode, selectContactFullName, selectContactDobUnknown, selectContactAgeYears, selectContactSex, - SEX + getWarningMsgDetails, }; diff --git a/tests/page-objects/default/enketo/undo-death-report.page.js b/tests/page-objects/default/enketo/undo-death-report.page.js index 3fa908623fe..3af7b17f7fe 100644 --- a/tests/page-objects/default/enketo/undo-death-report.page.js +++ b/tests/page-objects/default/enketo/undo-death-report.page.js @@ -1,7 +1,9 @@ const FORM = 'form[data-form-id="undo_death_report"]'; -const confirmUndoDeathOption = (value) => $(FORM + - ` input[name="/undo_death_report/undo/undo_information"][value="${value}"]`); +const confirmUndoDeathOption = (value) => { + return $(`${FORM} input[name="/undo_death_report/undo/undo_information"][value="${value}"]`); +}; +const patientName = () => $('span[data-value=" /undo_death_report/patient_display_name "]'); const setConfirmUndoDeathOption = async (value = 'yes') => { const confirmation = await confirmUndoDeathOption(value); @@ -9,6 +11,13 @@ const setConfirmUndoDeathOption = async (value = 'yes') => { await confirmation.click(); }; +const getConfirmationPatientName = async () => { + const name = await patientName(); + await name.waitForDisplayed(); + return await name.getText(); +}; + module.exports = { setConfirmUndoDeathOption, + getConfirmationPatientName, }; diff --git a/tests/page-objects/default/sms/messages.wdio.page.js b/tests/page-objects/default/sms/messages.wdio.page.js index 628ccbe0e4b..83457876598 100644 --- a/tests/page-objects/default/sms/messages.wdio.page.js +++ b/tests/page-objects/default/sms/messages.wdio.page.js @@ -41,7 +41,7 @@ const getMessageHeader = async () => { return { name: await $(`${MESSAGE_HEADER} .name`).getText(), phone: await $(`${MESSAGE_HEADER} .phone`).getText(), - lineage: await $(`${MESSAGE_HEADER} .horizontal.lineage`).getText() + lineage: await $(`${MESSAGE_HEADER} .horizontal.lineage`).getText(), }; }; diff --git a/tests/page-objects/standard/contacts/contacts.wdio.page.js b/tests/page-objects/standard/contacts/contacts.wdio.page.js index 8f092fe0ffa..a371094d89d 100644 --- a/tests/page-objects/standard/contacts/contacts.wdio.page.js +++ b/tests/page-objects/standard/contacts/contacts.wdio.page.js @@ -1,5 +1,5 @@ -const contactPageDefault = require('../../default/contacts/contacts.wdio.page'); -const commonPageDefault = require('../../default/common/common.wdio.page'); +const contactPageDefault = require('@page-objects/default/contacts/contacts.wdio.page'); +const commonPageDefault = require('@page-objects//default/common/common.wdio.page'); const HEALTH_PROGRAMS = { ANC: 'anc', PNC: 'pnc', IMM: 'imm', GPM: 'gpm'}; const healthProgram = (program) => $(`input[name="/data/health_center/use_cases"][value="${program}"]`); @@ -18,8 +18,9 @@ const pregnancyVisits = () => $(`${PREG_CARD_SELECTOR} div[test-id="contact.prof const PAST_PREG_CARD_SELECTOR = 'div[test-id="contact.profile.past_pregnancies"]'; const pastPregnancyCard = () => $(PAST_PREG_CARD_SELECTOR); const deliveryCode = () => $(`${PAST_PREG_CARD_SELECTOR} div[test-id*="contact.profile.delivery_code"] label`); -const ancVisitsCompleted = () => $(`${PAST_PREG_CARD_SELECTOR} ` + - `div[test-id="contact.profile.anc_visit"] p.card-field-value`); +const ancVisitsCompleted = () => { + return $(`${PAST_PREG_CARD_SELECTOR} div[test-id="contact.profile.anc_visit"] p.card-field-value`); +}; const addPlace = async (type, placeName, contactName, rightSideAction=true) => { if (rightSideAction) { @@ -29,12 +30,12 @@ const addPlace = async (type, placeName, contactName, rightSideAction=true) => { } await (await contactPageDefault.newPrimaryContactButton()).waitForDisplayed(); await (await contactPageDefault.newPrimaryContactButton()).click(); - await (await contactPageDefault.newPrimaryContactName()).addValue(contactName); + await (await contactPageDefault.newPrimaryContactName()).setValue(contactName); await contactPageDefault.genericForm.nextPage(); await (await contactPageDefault.writeNamePlace(type)).click(); - await (await contactPageDefault.newPlaceName()).addValue(placeName); - await (await contactPageDefault.externalIdField(type)).addValue('1234457'); - await (await contactPageDefault.notes(type)).addValue(`Some ${type} notes`); + await (await contactPageDefault.customPlaceNameField()).setValue(placeName); + await (await contactPageDefault.externalIdField(type)).setValue('1234457'); + await (await contactPageDefault.notes(type)).setValue(`Some ${type} notes`); await (await contactPageDefault.genericForm.submitButton()).click(); const dashedType = type.replace('_', '-'); await (await contactPageDefault.contactCardIcon(dashedType)).waitForDisplayed(); @@ -94,6 +95,14 @@ const getAncVisits = async () => { return (await ancVisitsCompleted()).getText(); }; +const getCurrentPlaceEditFormValues = async (type) => { + return { + name: await contactPageDefault.nameField(type).getValue(), + externalId: await contactPageDefault.externalIdField(type).getValue(), + notes: await contactPageDefault.notes(type).getValue(), + }; +}; + module.exports = { contactPageDefault, addPlace, @@ -105,4 +114,5 @@ module.exports = { pastPregnancyCard, getDeliveryCode, getAncVisits, + getCurrentPlaceEditFormValues, }; diff --git a/tests/page-objects/standard/enketo/child-health-registration.wdio.page.js b/tests/page-objects/standard/enketo/child-health-registration.wdio.page.js new file mode 100644 index 00000000000..ed9424d462d --- /dev/null +++ b/tests/page-objects/standard/enketo/child-health-registration.wdio.page.js @@ -0,0 +1,51 @@ +const enketoCommonPage = require('@page-objects/standard/enketo/enketo.wdio.page.js'); + +const DEFAULT_NOTE = + `span[data-itext-id="/child_health_registration/group_note/default_chw_sms_note:label"]${enketoCommonPage.ACTIVE}`; +const REGISTRATION_DETAILS = + `span[data-itext-id="/child_health_registration/group_summary/r_patient_info:label"]${enketoCommonPage.ACTIVE} `; +const FOLLOW_UP_SMS = enketoCommonPage.followUpSmsNote1('child_health_registration', 'summary'); + +const chwName = () => $(`${DEFAULT_NOTE} span[data-value=" /child_health_registration/chw_name "]`); +const chwPhone = () => $(`${DEFAULT_NOTE} span[data-value=" /child_health_registration/chw_phone "]`); +const defaultChwSmsText = () => { + return $(`${DEFAULT_NOTE} span[data-value=" /child_health_registration/group_note/default_chw_sms_text "]`); +}; +const personalNote = () => $('textarea[name="/child_health_registration/group_note/g_chw_sms"]'); +const sumChildName = () => $(REGISTRATION_DETAILS + enketoCommonPage.patientNameSummary('child_health_registration')); +const sumChildId = () => { + return $(REGISTRATION_DETAILS + enketoCommonPage.patientIdSummary('child_health_registration', 'summary')); +}; +const sumFollowUpMsgChwName = () => $(`${FOLLOW_UP_SMS} span[data-value=" /child_health_registration/chw_name "]`); +const sumFollowUpMsgChwPhone = () => $(`${FOLLOW_UP_SMS} span[data-value=" /child_health_registration/chw_phone "]`); +const sumFollowUpSmsContent = () => $(enketoCommonPage.followUpSmsNote2('child_health_registration', 'summary')); + +const getFormInformation = async () => { + return { + chwName: await chwName().getText(), + chwPhone: await chwPhone().getText(), + defaultSms: await defaultChwSmsText().getText(), + }; +}; + +const setPersonalNote = async (value = 'Test note') => { + const note = await personalNote(); + await note.waitForDisplayed(); + await note.setValue(value); +}; + +const getSummaryInformation = async () => { + return { + childName: await sumChildName().getText(), + childId: await sumChildId().getText(), + chwName: await sumFollowUpMsgChwName().getText(), + chwPhone: await sumFollowUpMsgChwPhone().getText(), + smsContent: await sumFollowUpSmsContent().getText(), + }; +}; + +module.exports = { + getFormInformation, + setPersonalNote, + getSummaryInformation, +}; diff --git a/tests/page-objects/standard/enketo/death-confirmation.wdio.page.js b/tests/page-objects/standard/enketo/death-confirmation.wdio.page.js new file mode 100644 index 00000000000..beb10df3a67 --- /dev/null +++ b/tests/page-objects/standard/enketo/death-confirmation.wdio.page.js @@ -0,0 +1,57 @@ +const enketoCommonPage = require('@page-objects/standard/enketo/enketo.wdio.page.js'); + +const confirmDeath = (value) => $(`input[name="/death_confirmation/death_report/death"][value="${value}"]`); +const additionalNotes = () => $('input[name="/death_confirmation/death_report/notes"]'); +const deathDate = () => $('section[name="/death_confirmation/death_report"] input.ignore.input-small'); +const deathPlace = (value) => $(`input[name="/death_confirmation/death_report/place"][value="${value}"]`); + +const getFormInformation = async () => { + const deathConfirmationNote = 'span[data-itext-id="/death_confirmation/death_report/n_2:label"]' + + enketoCommonPage.ACTIVE; + const childName = 'span[data-value=" /death_confirmation/child_name "]'; + const chwName = 'span[data-value=" /death_confirmation/chw_name "]'; + + return { + submittedReportChildName: await $('span[data-itext-id="/death_confirmation/death_report/n_1:label"]' + + enketoCommonPage.ACTIVE) + .$(childName) + .getText(), + deathConfirmationNoteChildName: await $(deathConfirmationNote).$(childName).getText(), + deathConfirmationNoteChwName: await $(deathConfirmationNote).$(chwName).getText(), + deathConfirmationNoteChwPhone: await $(deathConfirmationNote) + .$('span[data-value=" /death_confirmation/chw_phone "]') + .getText(), + }; +}; + +const selectConfirmDeathValue = async (value = 'yes') => { + const isDeath = await confirmDeath(value); + await isDeath.waitForClickable(); + await isDeath.click(); +}; + +const setDeathDate = async (value) => { + const date = await deathDate(); + await date.waitForDisplayed(); + await date.setValue(value); +}; + +const selectDeathPlace = async (value = 'home') => { + const place = await deathPlace(value); + await place.waitForClickable(); + await place.click(); +}; + +const setAdditionalNotes = async (value = 'Test note') => { + const note = await additionalNotes(); + await note.waitForDisplayed(); + await note.setValue(value); +}; + +module.exports = { + getFormInformation, + selectConfirmDeathValue, + setDeathDate, + selectDeathPlace, + setAdditionalNotes, +}; diff --git a/tests/page-objects/standard/enketo/delivery.wdio.page.js b/tests/page-objects/standard/enketo/delivery.wdio.page.js index f562f493e01..9cfd9cfeaa2 100644 --- a/tests/page-objects/standard/enketo/delivery.wdio.page.js +++ b/tests/page-objects/standard/enketo/delivery.wdio.page.js @@ -5,21 +5,38 @@ const OUTCOME = { liveBirth: 'healthy', stillBirth: 'still_birth', miscarriage: const LOCATION = { facility: 'f', homeAttendant: 's', homeNoAttendant: 'ns' }; const FORM = enketoCommonPage.form('delivery'); -const pregnancyOutcome = (value) => $(`${FORM} ` + - `input[name="/delivery/group_delivery_summary/g_pregnancy_outcome"][value="${value}"`); -const pregnancyOutcomeLabel = (value) => $(`${FORM} ` + - `span[data-itext-id="/delivery/group_delivery_summary/g_pregnancy_outcome/${value}:label"]`); -const deliveryLocation = (value) => $(`${FORM} ` + - `input[name="/delivery/group_delivery_summary/g_delivery_code"][value="${value}"`); -const deliveryLocationLabel = (value) => $(`${FORM} ` + - `span[data-itext-id="/delivery/group_delivery_summary/g_delivery_code/${value}:label"]` + - `${enketoCommonPage.ACTIVE_OPTION_LABEL}`); +const { ACTIVE, ACTIVE_OPTION_LABEL } = enketoCommonPage; +const DELIV_SUM = '/delivery/group_delivery_summary/'; +const GROUP_SUM = '/delivery/group_summary/'; + +const pregnancyOutcome = (value) => $(`${FORM} input[name="${DELIV_SUM}g_pregnancy_outcome"][value="${value}"]`); +const pregnancyOutcomeLabel = (value) => { + return $(`${FORM} span[data-itext-id="${DELIV_SUM}g_pregnancy_outcome/${value}:label"]${ACTIVE}`); +}; +const deliveryLocation = (value) => $(`${FORM} input[name="${DELIV_SUM}g_delivery_code"][value="${value}"]`); +const deliveryLocationLabel = (value) => { + return $(`${FORM} span[data-itext-id="${DELIV_SUM}g_delivery_code/${value}:label"]${ACTIVE_OPTION_LABEL}`); +}; const deliveryDate = () => $(`${FORM} div.widget.date input`); const smsNote = () => $(`${FORM} ${enketoCommonPage.smsNote('delivery')}`); -const outcomeSummary = () => $(`${FORM} ` + - `span[data-value=" /delivery/group_delivery_summary/display_delivery_outcome "]`); -const locationSummary = () => $(`${FORM} span[data-value=" /delivery/group_summary/r_delivery_location "]`); -const followUpSMS = () => $(`${FORM} ${enketoCommonPage.followUpSms('delivery')}`); +const patientNameSummary = () => { + const nameSum = enketoCommonPage.patientNameSummary('delivery'); + return $(`${FORM} span[data-itext-id="${GROUP_SUM}r_patient_info:label"]${ACTIVE} ${nameSum}`); +}; +const patientIdSummary = () => { + const idSum = enketoCommonPage.patientIdSummary('delivery', 'summary'); + return $(`${FORM} span[data-itext-id="${GROUP_SUM}r_patient_info:label"]${ACTIVE} ${idSum}`); +}; +const outcomeSummary = () => { + const pregOutcomeLabel = `span[data-itext-id="${GROUP_SUM}r_pregnancy_outcome:label"]${ACTIVE}`; + return $(`${FORM} ${pregOutcomeLabel} span[data-value=" ${DELIV_SUM}display_delivery_outcome "]`); +}; +const locationSummary = () => { + const birthDateLabel = `span[data-itext-id="${GROUP_SUM}r_birth_date:label"]${ACTIVE}`; + return $(`${FORM} ${birthDateLabel} span[data-value=" ${GROUP_SUM}r_delivery_location "]`); +}; +const followUpSmsNote1 = () => $(`${FORM} ${enketoCommonPage.followUpSmsNote1('delivery', 'summary')}`); +const followUpSmsNote2 = () => $(`${FORM} ${enketoCommonPage.followUpSmsNote2('delivery', 'summary')}`); const selectPregnancyOutcome = async (value = OUTCOME.liveBirth) => { const outcome = await pregnancyOutcome(value); @@ -32,10 +49,9 @@ const selectDeliveryLocation = async (value = LOCATION.facility) => { const location = await deliveryLocation(value); await location.waitForDisplayed(); await location.click(); - const locationLabel = utils.isMinimumChromeVersion() + return utils.isMinimumChromeVersion() ? await (await deliveryLocationLabel(value)).getAttribute('innerHTML') : await (await deliveryLocationLabel(value)).getText(); - return locationLabel; }; const setDeliveryDate = async (value) => { @@ -50,22 +66,15 @@ const setNote = async (text = 'Test note') => { await note.setValue(text); }; -const getOutcomeSummary = async () => { - const outcome = await outcomeSummary(); - await outcome.waitForDisplayed(); - return await outcome.getText(); -}; - -const getLocationSummary = async () => { - const location = await locationSummary(); - await location.waitForDisplayed(); - return await location.getText(); -}; - -const getFollowUpSMS = async () => { - const sms = await followUpSMS(); - await sms.waitForDisplayed(); - return sms.getText(); +const getSummaryDetails = async () => { + return { + patientName: await patientNameSummary().getText(), + patientId: await patientIdSummary().getText(), + outcome: await outcomeSummary().getText(), + location: await locationSummary().getText(), + followUpSmsNote1: await followUpSmsNote1().getText(), + followUpSmsNote2: await followUpSmsNote2().getText(), + }; }; module.exports = { @@ -75,7 +84,5 @@ module.exports = { selectDeliveryLocation, setDeliveryDate, setNote, - getOutcomeSummary, - getLocationSummary, - getFollowUpSMS, + getSummaryDetails, }; diff --git a/tests/page-objects/standard/enketo/enketo.wdio.page.js b/tests/page-objects/standard/enketo/enketo.wdio.page.js index 8a4d0b0010e..f1cebc9031a 100644 --- a/tests/page-objects/standard/enketo/enketo.wdio.page.js +++ b/tests/page-objects/standard/enketo/enketo.wdio.page.js @@ -3,7 +3,14 @@ const ACTIVE = '.active'; const ACTIVE_SPAN = 'span' + ACTIVE; const ACTIVE_OPTION_LABEL = '.option-label' + ACTIVE; const smsNote = (formId) => `textarea[name="/${formId}/group_note/g_chw_sms"]`; -const followUpSms = (formId) => `span[data-value=" /${formId}/chw_sms "]`; +const followUpSmsNote1 = (formId, type) => { + return `span[data-itext-id="/${formId}/group_${type}/r_followup_note1:label"]${ACTIVE}`; +}; +const followUpSmsNote2 = (formId, type) => { + return `span[data-itext-id="/${formId}/group_${type}/r_followup_note2:label"]${ACTIVE}`; +}; +const patientIdSummary = (formId, type) => `span[data-value=" /${formId}/group_${type}/r_patient_id "]`; +const patientNameSummary = (formId) => `span[data-value=" /${formId}/patient_name "]`; module.exports = { form, @@ -11,5 +18,8 @@ module.exports = { ACTIVE_SPAN, ACTIVE_OPTION_LABEL, smsNote, - followUpSms, + followUpSmsNote1, + followUpSmsNote2, + patientIdSummary, + patientNameSummary, }; diff --git a/tests/page-objects/standard/enketo/immunization-visit.wdio.page.js b/tests/page-objects/standard/enketo/immunization-visit.wdio.page.js index 25941df9ab7..acf76716dd1 100644 --- a/tests/page-objects/standard/enketo/immunization-visit.wdio.page.js +++ b/tests/page-objects/standard/enketo/immunization-visit.wdio.page.js @@ -19,15 +19,27 @@ const TYPHOID_VACCINE = 'input[name="/immunization_visit/group_typhoid/g_typhoid const VITAMIN_A_VACCINE = 'input[name="/immunization_visit/group_vitamin_a/g_vitamin_a"]'; const YELLOW_FEVER_VACCINE = 'input[name="/immunization_visit/group_yellow_fever/g_yellow_fever"]'; +const pregDetailsLabel = '"/immunization_visit/group_review/r_pregnancy_details:label"'; + const notes = () => $(`${enketoCommonPage.smsNote('immunization_visit')}`); const vaccines = () => $$('input[name="/immunization_visit/group_select_vaccines/g_vaccines"]'); -const patientNameSummary = () => $('.current span[data-value=" /immunization_visit/patient_name "]'); +const patientNameSummary = () => { + const immVisit = enketoCommonPage.patientNameSummary('immunization_visit'); + return $(`span[data-itext-id=${pregDetailsLabel}]${enketoCommonPage.ACTIVE} ${immVisit}`); +}; +const patientIdSummary = () => { + const immVisit = enketoCommonPage.patientIdSummary('immunization_visit', 'review'); + return $(`span[data-itext-id=${pregDetailsLabel}]${enketoCommonPage.ACTIVE} ${immVisit}`); +}; // Excluding the 'last-child' because it represents the follow-up message from the summary page form -const vaccinesAvailableSummary = () => - $$('label.question.readonly.or-branch.non-select.or-appearance-li:not(:last-child)'); -const vaccinesDisableSummary = () => - $$('label.question.readonly.or-branch.non-select.or-appearance-li.disabled'); -const followUpSMS = () => $(`.current ${enketoCommonPage.followUpSms('immunization_visit')}`); +const vaccinesAvailableSummary = () => { + return $$('label.question.readonly.or-branch.non-select.or-appearance-li:not(:last-child)'); +}; +const vaccinesDisableSummary = () => { + return $$('label.question.readonly.or-branch.non-select.or-appearance-li.disabled'); +}; +const followUpSmsNote1 = () => $(enketoCommonPage.followUpSmsNote1('immunization_visit', 'review')); +const followUpSmsNote2 = () => $(enketoCommonPage.followUpSmsNote2('immunization_visit', 'review')); const selectAppliedVaccines = async (selector, option = 'no') => { const vaccinesSelector = await $$(`${selector}[value*="${option}"]`); @@ -46,18 +58,18 @@ const selectAllVaccines = async () => { const addNotes = async (note = 'Test notes') => await (await notes()).setValue(note); -const getNotes = async () => await (await notes()).getText(); - -const getPatientNameSummaryPage = async () => await (await patientNameSummary()).getText(); - -const getAppliedVaccinesSummary = async () => { +const getSummaryDetails = async () => { const vaccinesAvaible = await vaccinesAvailableSummary(); const vaccinesDisabled = await vaccinesDisableSummary(); - return vaccinesAvaible.length - vaccinesDisabled.length; + return { + patientName: await patientNameSummary().getText(), + patientId: await patientIdSummary().getText(), + appliedVaccines: vaccinesAvaible.length - vaccinesDisabled.length, + followUpSmsNote1: await followUpSmsNote1().getText(), + followUpSmsNote2: await followUpSmsNote2().getText(), + }; }; -const getFollowUpSMS = async () => await (await followUpSMS()).getText(); - module.exports = { BCG_VACCINE, CHOLERA_VACCINE, @@ -80,8 +92,5 @@ module.exports = { selectAllVaccines, selectAppliedVaccines, addNotes, - getNotes, - getPatientNameSummaryPage, - getAppliedVaccinesSummary, - getFollowUpSMS, + getSummaryDetails, }; diff --git a/tests/page-objects/standard/enketo/postnatal-visit.wdio.page.js b/tests/page-objects/standard/enketo/postnatal-visit.wdio.page.js new file mode 100644 index 00000000000..c066814327c --- /dev/null +++ b/tests/page-objects/standard/enketo/postnatal-visit.wdio.page.js @@ -0,0 +1,75 @@ +const enketoCommonPage = require('@page-objects/standard/enketo/enketo.wdio.page.js'); +const { ACTIVE } = enketoCommonPage; + +const assessingTo = (value) => $(`input[name="/postnatal_visit/group_who_assessed/g_who_assessed"][value="${value}"]`); +const smsNote = () => $('textarea[name="/postnatal_visit/group_note/g_chw_sms"]'); +const dangerSigns = (person) => { + return $$(`input[name="/postnatal_visit/group_danger_signs_${person}/g_danger_signs_${person}"]`); +}; +const otherDangerSign = (person) => { + return $(`input[name="/postnatal_visit/group_danger_signs_${person}/danger_signs_${person}_other"]`); +}; +const patientNameSummary = () => { + const postNatVisit = enketoCommonPage.patientNameSummary('postnatal_visit'); + return $(`span[data-itext-id="/postnatal_visit/group_review/r_postnatal_details:label"]${ACTIVE} ${postNatVisit}`); +}; +const patientIdSummary = () => { + const postNatVisit = enketoCommonPage.patientIdSummary('postnatal_visit', 'review'); + return $(`span[data-itext-id="/postnatal_visit/group_review/r_postnatal_details:label"]${ACTIVE} ${postNatVisit}`); +}; +const visitInformation = () => { + return $(`label:not(.disabled) span[data-itext-id*="/postnatal_visit/group_review/r_visit_"]${ACTIVE}`); +}; +const dangerSignsSummary = (person) => { + return $$(`span[data-itext-id*="/postnatal_visit/group_review/r_${person}_danger_sign"]${ACTIVE}`); +}; +const followUpSmsNote1 = () => $(`${enketoCommonPage.followUpSmsNote1('postnatal_visit', 'review')}`); +const followUpSmsNote2 = () => $(`${enketoCommonPage.followUpSmsNote2('postnatal_visit', 'review')}`); + +const selectAssessingTo = async (value = 'both') => { + const whom = await assessingTo(value); + await whom.waitForClickable(); + await whom.click(); +}; + +const selectAllDangerSigns = async (person) => { + const signs = await dangerSigns(person); + for (const dangerSign of signs) { + await dangerSign.click(); + } + return signs.length; +}; + +const setOtherDangerSign = async (person, sign) => { + const otherSign = await otherDangerSign(person); + await otherSign.waitForDisplayed(); + await otherSign.setValue(sign); +}; + +const setSmsNote = async (noteValue) => { + const note = await smsNote(); + await note.waitForDisplayed(); + await note.setValue(noteValue); +}; + +const getSummaryDetails = async () => { + return { + patientName: await patientNameSummary().getText(), + patientId: await patientIdSummary().getText(), + visitInformation: await visitInformation().getText(), + // subtracting 1 element that corresponds to the title + countDangerSignsMom: await dangerSignsSummary('mom').length - 1, + // subtracting 1 element that corresponds to the title + countDangerSignsBaby: await dangerSignsSummary('baby').length - 1, + followUpSmsNote1: await followUpSmsNote1().getText(), + followUpSmsNote2: await followUpSmsNote2().getText(), + }; +}; + +module.exports = { + selectAssessingTo, + selectAllDangerSigns, + setOtherDangerSign, + setSmsNote, + getSummaryDetails, +}; diff --git a/tests/page-objects/standard/enketo/pregnancy-visit.wdio.page.js b/tests/page-objects/standard/enketo/pregnancy-visit.wdio.page.js index 38c8b981992..479c53afe2c 100644 --- a/tests/page-objects/standard/enketo/pregnancy-visit.wdio.page.js +++ b/tests/page-objects/standard/enketo/pregnancy-visit.wdio.page.js @@ -3,11 +3,23 @@ const commonPage = require('@page-objects/default/common/common.wdio.page'); const enketoCommonPage = require('@page-objects/standard/enketo/enketo.wdio.page.js'); const FORM = enketoCommonPage.form('pregnancy_visit'); +const { ACTIVE } = enketoCommonPage; + const dangerSig = () => $$(`${FORM} input[name="/pregnancy_visit/group_danger_signs/g_danger_signs"]`); const smsNote = () => $(`${FORM} ${enketoCommonPage.smsNote('pregnancy_visit')}`); -const dangerSignSummary = () => $$(`${FORM} span[data-itext-id*="/pregnancy_visit/group_review/r_danger_sign"]` + - `${enketoCommonPage.ACTIVE}`); -const followUpSMS = () => $(`${FORM} ${enketoCommonPage.followUpSms('pregnancy_visit')}`); +const dangerSignSummary = () => { + return $$(`${FORM} span[data-itext-id*="/pregnancy_visit/group_review/r_danger_sign"]${ACTIVE}`); +}; +const patientNameSummary = () => { + const nameSum = enketoCommonPage.patientNameSummary('pregnancy_visit'); + return $(`${FORM} span[data-itext-id="/pregnancy_visit/group_review/r_pregnancy_details:label"]${ACTIVE} ${nameSum}`); +}; +const patientIdSummary = () => { + const idSum = enketoCommonPage.patientIdSummary('pregnancy_visit', 'review'); + return $(`${FORM} span[data-itext-id="/pregnancy_visit/group_review/r_pregnancy_details:label"]${ACTIVE} ${idSum}`); +}; +const followUpSmsNote1 = () => $(enketoCommonPage.followUpSmsNote1('pregnancy_visit', 'review')); +const followUpSmsNote2 = () => $(enketoCommonPage.followUpSmsNote2('pregnancy_visit', 'review')); const selectAllDangerSigns = async () => { const dangerSigns = await dangerSig(); @@ -23,10 +35,14 @@ const setNote = async (text = 'Test note') => { await note.setValue(text); }; -const getFollowUpSMS = async () => { - const sms = await followUpSMS(); - await sms.waitForDisplayed(); - return sms.getText(); +const getSummaryDetails = async () => { + return { + patientName: await patientNameSummary().getText(), + patientId: await patientIdSummary().getText(), + countDangerSigns: await dangerSignSummary().length, + followUpSmsNote1: await followUpSmsNote1().getText(), + followUpSmsNote2: await followUpSmsNote2().getText(), + }; }; const submitPregnancyVisit = async () => { @@ -42,6 +58,6 @@ module.exports = { selectAllDangerSigns, setNote, dangerSignSummary, - getFollowUpSMS, + getSummaryDetails, submitPregnancyVisit, }; diff --git a/tests/page-objects/standard/enketo/pregnancy.wdio.page.js b/tests/page-objects/standard/enketo/pregnancy.wdio.page.js index 3a25bcee515..9c1ade19047 100644 --- a/tests/page-objects/standard/enketo/pregnancy.wdio.page.js +++ b/tests/page-objects/standard/enketo/pregnancy.wdio.page.js @@ -4,19 +4,34 @@ const enketoCommonPage = require('@page-objects/standard/enketo/enketo.wdio.page const APROX_LMP = { up2Months: 61, up3Months: 91, up4Months: 122, b5To6Months: 183, b7To8Months: 244 }; -const form = enketoCommonPage.form('pregnancy'); -const knowLMP = (value) => $(`${form} input[name="/pregnancy/group_lmp/g_lmp_method"][value="${value}"]`); -const aproxLMP = (value) => $(`${form} input[name="/pregnancy/group_lmp/g_lmp_approx"][value="${value}"]`); -const getEstDeliveryDate = () => $(`${form} span[data-itext-id="/pregnancy/group_lmp/g_display_edd:label"]` + - `${enketoCommonPage.ACTIVE}`); -const risksFac = () => $$(`${form} [name="/pregnancy/group_risk_factors"] label:not(:first-child) > [type="checkbox"]`); -const dangerSigns = () => $$(`${form} input[name="/pregnancy/group_danger_signs/g_danger_signs"]`); -const smsNote = () => $(`${form} ${enketoCommonPage.smsNote('pregnancy')}`); -const riskFactorsSummary = () => $$(`${form} :not(label.disabled):not(label.or-appearance-yellow) > ` + - `span[data-itext-id*="/pregnancy/group_review/r_risk_factor"]${enketoCommonPage.ACTIVE}`); -const dangerSignsSummary = () => $$(`${form} span[data-itext-id*="/pregnancy/group_review/r_danger_sign"]` + - `${enketoCommonPage.ACTIVE}`); -const followUpSMS = () => $(`${form} ${enketoCommonPage.followUpSms('pregnancy')}`); +const FORM = enketoCommonPage.form('pregnancy'); +const { ACTIVE } = enketoCommonPage; + +const knowLMP = (value) => $(`${FORM} input[name="/pregnancy/group_lmp/g_lmp_method"][value="${value}"]`); +const aproxLMP = (value) => $(`${FORM} input[name="/pregnancy/group_lmp/g_lmp_approx"][value="${value}"]`); +const getEstDeliveryDate = () => { + return $(`${FORM} span[data-itext-id="/pregnancy/group_lmp/g_display_edd:label"]${ACTIVE}`); +}; +const risksFac = () => $$(`${FORM} [name="/pregnancy/group_risk_factors"] label:not(:first-child) > [type="checkbox"]`); +const dangerSigns = () => $$(`${FORM} input[name="/pregnancy/group_danger_signs/g_danger_signs"]`); +const smsNote = () => $(`${FORM} ${enketoCommonPage.smsNote('pregnancy')}`); +const patientNameSummary = () => { + const nameSum = enketoCommonPage.patientNameSummary('pregnancy'); + return $(`${FORM} span[data-itext-id="/pregnancy/group_review/r_pregnancy_details:label"]${ACTIVE} ${nameSum}`); +}; +const patientIdSummary = () => { + const idSum = enketoCommonPage.patientIdSummary('pregnancy', 'review'); + return $(`${FORM} span[data-itext-id="/pregnancy/group_review/r_pregnancy_details:label"]${ACTIVE} ${idSum}`); +}; +const riskFactorsSummary = () => { + const parent = ':not(label.disabled):not(label.or-appearance-yellow)'; + return $$(`${FORM} ${parent} > span[data-itext-id*="/pregnancy/group_review/r_risk_factor"]${ACTIVE}`); +}; +const dangerSignsSummary = () => { + return $$(`${FORM} span[data-itext-id*="/pregnancy/group_review/r_danger_sign"]${ACTIVE}`); +}; +const followUpSmsNote1 = () => $(`${FORM} ${enketoCommonPage.followUpSmsNote1('pregnancy', 'review')}`); +const followUpSmsNote2 = () => $(`${FORM} ${enketoCommonPage.followUpSmsNote2('pregnancy', 'review')}`); const selectKnowLMP = async (value = 'approx') => { const lmpOption = await knowLMP(value); @@ -53,10 +68,15 @@ const setNote = async (text = 'Test note') => { await note.setValue(text); }; -const getFollowUpSMS = async () => { - const sms = await followUpSMS(); - await sms.waitForDisplayed(); - return sms.getText(); +const getSummaryDetails = async () => { + return { + patientName: await patientNameSummary().getText(), + patientId: await patientIdSummary().getText(), + countRiskFactors: await riskFactorsSummary().length, + countDangerSigns: await dangerSignsSummary().length, + followUpSmsNote1: await followUpSmsNote1().getText(), + followUpSmsNote2: await followUpSmsNote2().getText(), + }; }; const submitPregnancy = async () => { @@ -83,6 +103,6 @@ module.exports = { setNote, riskFactorsSummary, dangerSignsSummary, - getFollowUpSMS, + getSummaryDetails, submitPregnancy, }; diff --git a/webapp/angular.json b/webapp/angular.json index 0b98dab9e68..97281bccfe2 100644 --- a/webapp/angular.json +++ b/webapp/angular.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@angular/cli/lib/config/schema.json", "version": 1, - "newProjectRoot": "projects", + "newProjectRoot": "web-components", "projects": { "webapp": { "projectType": "application", @@ -127,6 +127,137 @@ } } } + }, + "cht-form": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "less" + } + }, + "root": "web-components/cht-form", + "sourceRoot": "web-components/cht-form/src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-builders/custom-webpack:browser", + "options": { + "outputPath": "../build/cht-form", + "index": "web-components/cht-form/src/index.html", + "main": "web-components/cht-form/src/main.ts", + "polyfills": "src/ts/polyfills.ts", + "tsConfig": "web-components/cht-form/tsconfig.app.json", + "aot": true, + "assets": [ + { + "glob": "*", + "input": "src/fonts", + "output": "fonts/" + }, + { + "glob": "*", + "input": "src/audio", + "output": "audio/" + } + ], + "styles": [ + "src/css/inbox.less" + ], + "stylePreprocessorOptions": { + "includePaths": [ + "src/css" + ] + }, + "scripts": [ + "node_modules/jquery/dist/jquery.min.js", + "node_modules/select2/dist/js/select2.min.js" + ], + + "customWebpackConfig": { + "path": "./custom-webpack.config.js" + } + }, + "configurations": { + "development": { + "deleteOutputPath": false, + "outputHashing": "none", + "sourceMap": true, + "namedChunks": true, + "extractLicenses": false, + "vendorChunk": false, + "buildOptimizer": true, + "optimization": false + }, + "production": { + "deleteOutputPath": false, + "optimization": { + "fonts": true, + "scripts": true, + "styles": { + "minify": true, + "inlineCritical": false + } + }, + "outputHashing": "none", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb", + "maximumError": "10kb" + } + ] + } + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "tests/karma/test.ts", + "polyfills": "src/ts/polyfills.ts", + "tsConfig": "web-components/cht-form/tsconfig.spec.json", + "karmaConfig": "web-components/cht-form/tests/karma/karma-unit.conf.js", + "include": [ + "../tests/karma/**/*.spec.ts" + ], + "codeCoverage": true, + "codeCoverageExclude": [ + "src/ts/polyfills.ts" + ], + "assets": [ + { + "glob": "*", + "input": "src/fonts", + "output": "fonts/" + }, + { + "glob": "*", + "input": "src/audio", + "output": "audio/" + } + ], + "styles": [], + "stylePreprocessorOptions": { + "includePaths": [ + "src/css" + ] + }, + "scripts": [ + "node_modules/jquery/dist/jquery.min.js", + "node_modules/select2/dist/js/select2.min.js" + ] + } + } + } } }, "cli": { diff --git a/webapp/package-lock.json b/webapp/package-lock.json index f7cd866ec0a..37fe2f75778 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -15,6 +15,7 @@ "@angular/common": "^16.2.3", "@angular/compiler": "^16.2.3", "@angular/core": "^16.2.3", + "@angular/elements": "^16.2.3", "@angular/forms": "^16.2.3", "@angular/material": "^16.2.2", "@angular/platform-browser": "^16.2.3", @@ -261,6 +262,21 @@ "zone.js": "~0.13.0" } }, + "node_modules/@angular/elements": { + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-16.2.7.tgz", + "integrity": "sha512-QnOwLc0qr26/0bcKv1k99/wZU/9JdTObpGbze6M/LkapgNBoAHN4uKELpDr040BfI1C75qxsh8eu8ibGNDBDeg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/core": "16.2.7", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, "node_modules/@angular/forms": { "version": "16.2.5", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.5.tgz", @@ -1750,6 +1766,14 @@ "tslib": "^2.3.0" } }, + "@angular/elements": { + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-16.2.7.tgz", + "integrity": "sha512-QnOwLc0qr26/0bcKv1k99/wZU/9JdTObpGbze6M/LkapgNBoAHN4uKELpDr040BfI1C75qxsh8eu8ibGNDBDeg==", + "requires": { + "tslib": "^2.3.0" + } + }, "@angular/forms": { "version": "16.2.5", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.5.tgz", diff --git a/webapp/package.json b/webapp/package.json index 1d029fffb73..f1037493b1f 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -19,7 +19,9 @@ "scripts": { "postinstall": "patch-package", "unit": "UNIT_TEST_ENV=1 ng test webapp", + "unit:cht-form": "ng test cht-form", "build": "ng build", + "build:cht-form": "ng build cht-form", "compile": "ngc" }, "dependencies": { @@ -28,6 +30,7 @@ "@angular/common": "^16.2.3", "@angular/compiler": "^16.2.3", "@angular/core": "^16.2.3", + "@angular/elements": "^16.2.3", "@angular/forms": "^16.2.3", "@angular/material": "^16.2.2", "@angular/platform-browser": "^16.2.3", diff --git a/webapp/src/ts/modules/contacts/contacts-edit.component.ts b/webapp/src/ts/modules/contacts/contacts-edit.component.ts index a254c92cc1d..60caab71b28 100644 --- a/webapp/src/ts/modules/contacts/contacts-edit.component.ts +++ b/webapp/src/ts/modules/contacts/contacts-edit.component.ts @@ -9,10 +9,8 @@ import { FormService } from '@mm-services/form.service'; import { EnketoFormContext } from '@mm-services/enketo.service'; import { ContactTypesService } from '@mm-services/contact-types.service'; import { DbService } from '@mm-services/db.service'; -import { ContactSaveService } from '@mm-services/contact-save.service'; import { Selectors } from '@mm-selectors/index'; import { GlobalActions } from '@mm-actions/global'; -import { ContactsActions } from '@mm-actions/contacts'; import { TelemetryService } from '@mm-services/telemetry.service'; import { TranslateService } from '@mm-services/translate.service'; @@ -29,18 +27,15 @@ export class ContactsEditComponent implements OnInit, OnDestroy, AfterViewInit { private formService:FormService, private contactTypesService:ContactTypesService, private dbService:DbService, - private contactSaveService:ContactSaveService, private telemetryService:TelemetryService, private translateService:TranslateService, ) { this.globalActions = new GlobalActions(store); - this.contactsActions = new ContactsActions(store); } subscription = new Subscription(); translationsLoadedSubscription; private globalActions; - private contactsActions; private xmlVersion; enketoStatus; @@ -304,8 +299,8 @@ export class ContactsEditComponent implements OnInit, OnDestroy, AfterViewInit { // Updating fields before save. Ref: #6670. $('form.or').trigger('beforesave'); - return this.contactSaveService - .save(form, docId, this.enketoContact.type, this.xmlVersion) + return this.formService + .saveContact(form, docId, this.enketoContact.type, this.xmlVersion) .then((result) => { console.debug('saved contact', result); diff --git a/webapp/src/ts/services/contact-save.service.ts b/webapp/src/ts/services/contact-save.service.ts index 4d3c7e1cb4a..2f55519223f 100644 --- a/webapp/src/ts/services/contact-save.service.ts +++ b/webapp/src/ts/services/contact-save.service.ts @@ -1,57 +1,30 @@ import { v4 as uuidV4 } from 'uuid'; import { Injectable, NgZone } from '@angular/core'; -import { Store } from '@ngrx/store'; -import { reduce as _reduce, isObject as _isObject, defaults as _defaults } from 'lodash-es'; +import { defaults as _defaults, isObject as _isObject } from 'lodash-es'; import { DbService } from '@mm-services/db.service'; import { EnketoTranslationService } from '@mm-services/enketo-translation.service'; import { ExtractLineageService } from '@mm-services/extract-lineage.service'; -import { ServicesActions } from '@mm-actions/services'; -import { ContactTypesService } from '@mm-services/contact-types.service'; -import { TransitionsService } from '@mm-services/transitions.service'; @Injectable({ providedIn: 'root' }) export class ContactSaveService { - private servicesActions; private readonly CONTACT_FIELD_NAMES = [ 'parent', 'contact' ]; constructor( - private store:Store, - private contactTypesService:ContactTypesService, private dbService:DbService, private enketoTranslationService:EnketoTranslationService, private extractLineageService:ExtractLineageService, - private transitionsService:TransitionsService, private ngZone:NgZone, ) { - this.servicesActions = new ServicesActions(store); } - private generateFailureMessage(bulkDocsResult) { - return _reduce(bulkDocsResult, (msg: any, result) => { - let newMsg = msg; - if (!result.ok) { - if (!newMsg) { - newMsg = 'Some documents did not save correctly: '; - } - newMsg += result.id + ' with ' + result.message + '; '; - } - return newMsg; - }, null); - } - - private prepareSubmittedDocsForSave(original, submitted, type) { + private prepareSubmittedDocsForSave(original, submitted, typeFields) { if (original) { _defaults(submitted.doc, original); - } else if (this.contactTypesService.isHardcodedType(type)) { - // default hierarchy - maintain backwards compatibility - submitted.doc.type = type; } else { - // configured hierarchy - submitted.doc.type = 'contact'; - submitted.doc.contact_type = type; + Object.assign(submitted.doc, typeFields); } const doc = this.prepare(submitted.doc); @@ -173,46 +146,17 @@ export class ContactSaveService { return promiseChain.then(() => preparedSiblings); } - save(form, docId, type, xmlVersion) { - return this.ngZone.runOutsideAngular(() => { - return (docId ? this.dbService.get().get(docId) : Promise.resolve()) - .then(original => { - const submitted = this.enketoTranslationService.contactRecordToJs(form.getDataStr({ irrelevant: false })); - return this.prepareSubmittedDocsForSave(original, submitted, type); - }) - .then((preparedDocs) => this.applyTransitions(preparedDocs)) - .then((preparedDocs) => { - if (xmlVersion) { - for (const doc of preparedDocs.preparedDocs) { - doc.form_version = xmlVersion; - } - } - - const primaryDoc = preparedDocs.preparedDocs.find(doc => doc.type === type); - this.servicesActions.setLastChangedDoc(primaryDoc || preparedDocs.preparedDocs[0]); - - return this.dbService - .get() - .bulkDocs(preparedDocs.preparedDocs) - .then((bulkDocsResult) => { - const failureMessage = this.generateFailureMessage(bulkDocsResult); - - if (failureMessage) { - throw new Error(failureMessage); - } - - return { docId: preparedDocs.docId, bulkDocsResult }; - }); - }); + async save(form, docId, typeFields, xmlVersion) { + return this.ngZone.runOutsideAngular(async () => { + const original = docId ? await this.dbService.get().get(docId) : null; + const submitted = this.enketoTranslationService.contactRecordToJs(form.getDataStr({ irrelevant: false })); + const docData = await this.prepareSubmittedDocsForSave(original, submitted, typeFields); + if (xmlVersion) { + for (const doc of docData.preparedDocs) { + doc.form_version = xmlVersion; + } + } + return docData; }); } - - private applyTransitions(preparedDocs) { - return this.transitionsService - .applyTransitions(preparedDocs.preparedDocs) - .then(updatedDocs => { - preparedDocs.preparedDocs = updatedDocs; - return preparedDocs; - }); - } } diff --git a/webapp/src/ts/services/form.service.ts b/webapp/src/ts/services/form.service.ts index fe3acf96fff..a10d09c3e68 100644 --- a/webapp/src/ts/services/form.service.ts +++ b/webapp/src/ts/services/form.service.ts @@ -20,8 +20,11 @@ import { TransitionsService } from '@mm-services/transitions.service'; import { GlobalActions } from '@mm-actions/global'; import { CHTScriptApiService } from '@mm-services/cht-script-api.service'; import { TrainingCardsService } from '@mm-services/training-cards.service'; -import { EnketoService, EnketoFormContext } from '@mm-services/enketo.service'; +import { EnketoFormContext, EnketoService } from '@mm-services/enketo.service'; import { UserSettingsService } from '@mm-services/user-settings.service'; +import { ContactSaveService } from '@mm-services/contact-save.service'; +import { reduce as _reduce } from 'lodash-es'; +import { ContactTypesService } from '@mm-services/contact-types.service'; /** * Service for interacting with forms. This is the primary entry-point for CHT code to render forms and save the @@ -35,7 +38,9 @@ import { UserSettingsService } from '@mm-services/user-settings.service'; export class FormService { constructor( private store: Store, + private contactSaveService:ContactSaveService, private contactSummaryService: ContactSummaryService, + private contactTypesService:ContactTypesService, private dbService: DbService, private fileReaderService: FileReaderService, private lineageModelGeneratorService: LineageModelGeneratorService, @@ -290,6 +295,48 @@ export class FormService { }); } + private applyTransitions(preparedDocs) { + return this.transitionsService + .applyTransitions(preparedDocs.preparedDocs) + .then(updatedDocs => { + preparedDocs.preparedDocs = updatedDocs; + return preparedDocs; + }); + } + + private generateFailureMessage(bulkDocsResult) { + return _reduce(bulkDocsResult, (msg: any, result) => { + let newMsg = msg; + if (!result.ok) { + if (!newMsg) { + newMsg = 'Some documents did not save correctly: '; + } + newMsg += result.id + ' with ' + result.message + '; '; + } + return newMsg; + }, null); + } + + async saveContact(form, docId, type, xmlVersion) { + const typeFields = this.contactTypesService.isHardcodedType(type) + ? { type } + : { type: 'contact', contact_type: type }; + + const docs = await this.contactSaveService.save(form, docId, typeFields, xmlVersion); + const preparedDocs = await this.applyTransitions(docs); + + const primaryDoc = preparedDocs.preparedDocs.find(doc => doc.type === type); + this.servicesActions.setLastChangedDoc(primaryDoc || preparedDocs.preparedDocs[0]); + const bulkDocsResult = await this.dbService.get().bulkDocs(preparedDocs.preparedDocs); + const failureMessage = this.generateFailureMessage(bulkDocsResult); + + if (failureMessage) { + throw new Error(failureMessage); + } + + return { docId: preparedDocs.docId, bulkDocsResult }; + } + unload(form) { this.enketoService.unload(form); } diff --git a/webapp/tests/.eslintrc b/webapp/tests/.eslintrc index f65afa6bc1b..9785b4f8a09 100644 --- a/webapp/tests/.eslintrc +++ b/webapp/tests/.eslintrc @@ -9,6 +9,6 @@ "angular/timeout-service": "off" }, "parserOptions": { - "ecmaVersion": 8 + "ecmaVersion": 2018 } } diff --git a/webapp/tests/karma/karma-unit.base.conf.js b/webapp/tests/karma/karma-unit.base.conf.js new file mode 100644 index 00000000000..3aef4fe92e6 --- /dev/null +++ b/webapp/tests/karma/karma-unit.base.conf.js @@ -0,0 +1,45 @@ +module.exports = { + frameworks: ['mocha', '@angular-devkit/build-angular'], + plugins: [ + require('karma-mocha'), + require('karma-chrome-launcher'), + require('karma-mocha-reporter'), + require('@angular-devkit/build-angular/plugins/karma'), + require('karma-coverage'), + ], + client: { + captureConsole: true, + }, + reporters: ['mocha', 'coverage'], + mochaReporter: { + output: 'full', + showDiff: true, + }, + port: 9876, + colors: true, + autoWatch: true, + singleRun: false, + restartOnFileChange: false, + browsers: ['ChromeHeadless'], + customLaunchers: { + ChromeHeadlessCI: { + base: 'ChromeHeadless', + flags: ['--no-sandbox'], + } + }, + browserConsoleLogOptions: { + level: 'log', + format: '%b %T: %m', + terminal: true, + }, + coverageReporter: { + reporters: [ + { type: 'html' }, + { type: 'lcovonly', file: 'lcov.info' }, + { type: 'text-summary' }, + ], + subdir: '.', + fixWebpackSourcePaths: true, + skipFilesWithNoCoverage: true, + } +}; diff --git a/webapp/tests/karma/karma-unit.conf.js b/webapp/tests/karma/karma-unit.conf.js index 3886da8d4d0..13881319c6d 100644 --- a/webapp/tests/karma/karma-unit.conf.js +++ b/webapp/tests/karma/karma-unit.conf.js @@ -1,55 +1,17 @@ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html const path = require('path'); +const baseConfig = require('./karma-unit.base.conf'); module.exports = function (config) { config.set({ + ...baseConfig, basePath: '../../', - frameworks: ['mocha', '@angular-devkit/build-angular'], - plugins: [ - require('karma-mocha'), - require('karma-chrome-launcher'), - require('karma-mocha-reporter'), - require('@angular-devkit/build-angular/plugins/karma'), - require('karma-coverage'), - ], - client: { - captureConsole: true, - }, - reporters: ['mocha', 'coverage'], - mochaReporter: { - output: 'full', - showDiff: true, - }, logLevel: config.LOG_INFO, - port: 9876, - colors: true, - autoWatch: true, - singleRun: false, - restartOnFileChange: false, - browsers: ['ChromeHeadless'], - customLaunchers: { - ChromeHeadlessCI: { - base: 'ChromeHeadless', - flags: ['--no-sandbox'], - } - }, - browserConsoleLogOptions: { - level: 'log', - format: '%b %T: %m', - terminal: true, - }, coverageReporter: { - reporters: [ - { type: 'html' }, - { type: 'lcovonly', file: 'lcov.info' }, - { type: 'text-summary' }, - ], - dir: path.join(__dirname, 'coverage'), - subdir: '.', - fixWebpackSourcePaths: true, - skipFilesWithNoCoverage: true, - }, + ...baseConfig.coverageReporter, + dir: path.join(__dirname, 'coverage') + } }); // allow to require xml files as strings diff --git a/webapp/tests/karma/ts/modules/contacts/contacts-edit.component.spec.ts b/webapp/tests/karma/ts/modules/contacts/contacts-edit.component.spec.ts index 3ade0a6cb12..2b7afa73952 100644 --- a/webapp/tests/karma/ts/modules/contacts/contacts-edit.component.spec.ts +++ b/webapp/tests/karma/ts/modules/contacts/contacts-edit.component.spec.ts @@ -17,7 +17,6 @@ import { DbService } from '@mm-services/db.service'; import { Selectors } from '@mm-selectors/index'; import { LineageModelGeneratorService } from '@mm-services/lineage-model-generator.service'; import { FormService } from '@mm-services/form.service'; -import { ContactSaveService } from '@mm-services/contact-save.service'; import { GlobalActions } from '@mm-actions/global'; @@ -32,7 +31,6 @@ describe('ContactsEdit component', () => { let component; let formService; let lineageModelGeneratorService; - let contactSaveService; let routeSnapshot; let telemetryService; @@ -55,10 +53,10 @@ describe('ContactsEdit component', () => { formService = { render: sinon.stub(), unload: sinon.stub(), + saveContact: sinon.stub() }; telemetryService = { record: sinon.stub() }; lineageModelGeneratorService = { contact: sinon.stub().resolves({ doc: { } }) }; - contactSaveService = { save: sinon.stub() }; sinon.stub(console, 'error'); @@ -86,7 +84,6 @@ describe('ContactsEdit component', () => { { provide: LineageModelGeneratorService, useValue: lineageModelGeneratorService }, { provide: FormService, useValue: formService }, { provide: ContactTypesService, useValue: contactTypesService }, - { provide: ContactSaveService, useValue: contactSaveService }, { provide: TelemetryService, useValue: telemetryService }, ], declarations: [ @@ -570,7 +567,7 @@ describe('ContactsEdit component', () => { component.enketoSaving = true; await component.save(); - expect(contactSaveService.save.callCount).to.equal(0); + expect(formService.saveContact.callCount).to.equal(0); expect(setEnketoSavingStatus.callCount).to.equal(0); expect(setEnketoError.callCount).to.equal(0); }); @@ -591,7 +588,7 @@ describe('ContactsEdit component', () => { expect(setEnketoError.callCount).to.equal(1); expect(setEnketoError.args).to.deep.equal([[null]]); expect(component.enketoContact.formInstance.validate.callCount).to.equal(1); - expect(contactSaveService.save.callCount).to.equal(0); + expect(formService.saveContact.callCount).to.equal(0); }); it('should catch save errors', async () => { @@ -604,13 +601,13 @@ describe('ContactsEdit component', () => { }, type: 'some_contact', }; - contactSaveService.save.rejects({ some: 'error' }); + formService.saveContact.rejects({ some: 'error' }); await component.save(); expect(setEnketoSavingStatus.callCount).to.equal(2); expect(setEnketoSavingStatus.args).to.deep.equal([[true], [false]]); expect(component.enketoContact.formInstance.validate.callCount).to.equal(1); - expect(contactSaveService.save.callCount).to.equal(1); + expect(formService.saveContact.callCount).to.equal(1); expect(setEnketoError.callCount).to.equal(2); }); @@ -629,7 +626,7 @@ describe('ContactsEdit component', () => { await createComponent(); await fixture.whenStable(); - contactSaveService.save.resolves({ docId: 'new_clinic_id' }); + formService.saveContact.resolves({ docId: 'new_clinic_id' }); await component.save(); expect(telemetryService.record.callCount).to.equal(3); @@ -639,8 +636,8 @@ describe('ContactsEdit component', () => { expect(setEnketoSavingStatus.callCount).to.equal(2); expect(setEnketoSavingStatus.args).to.deep.equal([[true], [false]]); expect(setEnketoError.callCount).to.equal(1); - expect(contactSaveService.save.callCount).to.equal(1); - expect(contactSaveService.save.args[0]).to.deep.equal([ form, null, 'clinic', undefined ]); + expect(formService.saveContact.callCount).to.equal(1); + expect(formService.saveContact.args[0]).to.deep.equal([ form, null, 'clinic', undefined ]); expect(router.navigate.callCount).to.equal(1); expect(router.navigate.args[0]).to.deep.equal([['/contacts', 'new_clinic_id']]); }); @@ -667,15 +664,15 @@ describe('ContactsEdit component', () => { await createComponent(); await fixture.whenStable(); - contactSaveService.save.resolves({ docId: 'the_person' }); + formService.saveContact.resolves({ docId: 'the_person' }); await component.save(); expect(setEnketoSavingStatus.callCount).to.equal(2); expect(setEnketoSavingStatus.args).to.deep.equal([[true], [false]]); expect(setEnketoError.callCount).to.equal(1); - expect(contactSaveService.save.callCount).to.equal(1); - expect(contactSaveService.save.args[0]).to.deep.equal([ form, 'the_person', 'person', undefined ]); + expect(formService.saveContact.callCount).to.equal(1); + expect(formService.saveContact.args[0]).to.deep.equal([ form, 'the_person', 'person', undefined ]); expect(router.navigate.callCount).to.equal(1); expect(router.navigate.args[0]).to.deep.equal([['/contacts', 'the_person']]); expect(telemetryService.record.callCount).to.equal(3); @@ -705,15 +702,15 @@ describe('ContactsEdit component', () => { await createComponent(); await fixture.whenStable(); - contactSaveService.save.resolves({ docId: 'the_patient' }); + formService.saveContact.resolves({ docId: 'the_patient' }); await component.save(); expect(setEnketoSavingStatus.callCount).to.equal(2); expect(setEnketoSavingStatus.args).to.deep.equal([[true], [false]]); expect(setEnketoError.callCount).to.equal(1); - expect(contactSaveService.save.callCount).to.equal(1); - expect(contactSaveService.save.args[0]).to.deep.equal([ form, 'the_patient', 'patient', undefined ]); + expect(formService.saveContact.callCount).to.equal(1); + expect(formService.saveContact.args[0]).to.deep.equal([ form, 'the_patient', 'patient', undefined ]); expect(router.navigate.callCount).to.equal(1); expect(router.navigate.args[0]).to.deep.equal([['/contacts', 'the_patient']]); expect(telemetryService.record.callCount).to.equal(3); diff --git a/webapp/tests/karma/ts/services/contact-save.service.spec.ts b/webapp/tests/karma/ts/services/contact-save.service.spec.ts index e7ae2c5655e..6317848d186 100644 --- a/webapp/tests/karma/ts/services/contact-save.service.spec.ts +++ b/webapp/tests/karma/ts/services/contact-save.service.spec.ts @@ -2,26 +2,18 @@ import { TestBed } from '@angular/core/testing'; import sinon from 'sinon'; import { assert } from 'chai'; import { provideMockStore } from '@ngrx/store/testing'; -import { cloneDeep } from 'lodash-es'; import { DbService } from '@mm-services/db.service'; -import { ContactTypesService } from '@mm-services/contact-types.service'; import { EnketoTranslationService } from '@mm-services/enketo-translation.service'; import { ExtractLineageService } from '@mm-services/extract-lineage.service'; import { ContactSaveService } from '@mm-services/contact-save.service'; -import { ServicesActions } from '@mm-actions/services'; -import { TransitionsService } from '@mm-services/transitions.service'; describe('ContactSave service', () => { let service; - let bulkDocs; let get; - let contactTypesService; let enketoTranslationService; let extractLineageService; - let transitionsService; - let setLastChangedDoc; let clock; beforeEach(() => { @@ -29,21 +21,15 @@ describe('ContactSave service', () => { contactRecordToJs: sinon.stub(), }; - contactTypesService = { isHardcodedType: sinon.stub().returns(false) }; extractLineageService = { extract: sinon.stub() }; - transitionsService = { applyTransitions: sinon.stub().resolvesArg(0) }; - bulkDocs = sinon.stub(); get = sinon.stub(); - setLastChangedDoc = sinon.stub(ServicesActions.prototype, 'setLastChangedDoc'); TestBed.configureTestingModule({ providers: [ provideMockStore(), - { provide: DbService, useValue: { get: () => ({ get, bulkDocs }) } }, - { provide: ContactTypesService, useValue: contactTypesService }, + { provide: DbService, useValue: { get: () => ({ get }) } }, { provide: EnketoTranslationService, useValue: enketoTranslationService }, { provide: ExtractLineageService, useValue: extractLineageService }, - { provide: TransitionsService, useValue: transitionsService }, ] }); @@ -63,20 +49,15 @@ describe('ContactSave service', () => { enketoTranslationService.contactRecordToJs.returns({ doc: { _id: 'main1', type: 'main', contact: 'abc' } }); - bulkDocs.resolves([]); get.resolves({ _id: 'abc', name: 'gareth', parent: { _id: 'def' } }); extractLineageService.extract.returns({ _id: 'abc', parent: { _id: 'def' } }); return service .save(form, docId, type) - .then(() => { + .then(({ preparedDocs: savedDocs }) => { assert.equal(get.callCount, 1); assert.equal(get.args[0][0], 'abc'); - assert.equal(bulkDocs.callCount, 1); - - const savedDocs = bulkDocs.args[0][0]; - assert.equal(savedDocs.length, 1); assert.deepEqual(savedDocs[0].contact, { _id: 'abc', @@ -84,8 +65,6 @@ describe('ContactSave service', () => { _id: 'def' } }); - assert.equal(setLastChangedDoc.callCount, 1); - assert.deepEqual(setLastChangedDoc.args[0], [savedDocs[0]]); }); }); @@ -97,20 +76,15 @@ describe('ContactSave service', () => { enketoTranslationService.contactRecordToJs.returns({ doc: { _id: 'main1', type: 'main', contact: { _id: 'abc', name: 'Richard' } } }); - bulkDocs.resolves([]); get.resolves({ _id: 'abc', name: 'Richard', parent: { _id: 'def' } }); extractLineageService.extract.returns({ _id: 'abc', parent: { _id: 'def' } }); return service .save(form, docId, type) - .then(() => { + .then(({ preparedDocs: savedDocs }) => { assert.equal(get.callCount, 1); assert.equal(get.args[0][0], 'abc'); - assert.equal(bulkDocs.callCount, 1); - - const savedDocs = bulkDocs.args[0][0]; - assert.equal(savedDocs.length, 1); assert.deepEqual(savedDocs[0].contact, { _id: 'abc', @@ -118,8 +92,6 @@ describe('ContactSave service', () => { _id: 'def' } }); - assert.equal(setLastChangedDoc.callCount, 1); - assert.deepEqual(setLastChangedDoc.args[0], [savedDocs[0]]); }); }); @@ -143,15 +115,9 @@ describe('ContactSave service', () => { return contact; }); - bulkDocs.resolves([]); - return service .save(form, docId, type) - .then(() => { - assert.isTrue(bulkDocs.calledOnce); - - const savedDocs = bulkDocs.args[0][0]; - + .then(({ preparedDocs: savedDocs }) => { assert.equal(savedDocs[0]._id, 'main1'); assert.equal(savedDocs[1]._id, 'kid1'); @@ -163,9 +129,6 @@ describe('ContactSave service', () => { assert.equal(savedDocs[2].parent.extracted, true); assert.equal(extractLineageService.extract.callCount, 3); - - assert.equal(setLastChangedDoc.callCount, 1); - assert.deepEqual(setLastChangedDoc.args[0], [savedDocs[0]]); }); }); @@ -189,8 +152,6 @@ describe('ContactSave service', () => { return contact; }); - bulkDocs.resolves([]); - const xmlVersion = { time: 123456, sha256: '654321' @@ -198,9 +159,7 @@ describe('ContactSave service', () => { return service .save(form, docId, type, xmlVersion) - .then(() => { - assert.isTrue(bulkDocs.calledOnce); - const savedDocs = bulkDocs.args[0][0]; + .then(({ preparedDocs: savedDocs }) => { assert.equal(savedDocs.length, 3); for (const savedDoc of savedDocs) { assert.equal(savedDoc.form_version.time, 123456); @@ -223,7 +182,6 @@ describe('ContactSave service', () => { value: undefined, } }); - bulkDocs.resolves([]); get .withArgs('main1') .resolves({ @@ -246,15 +204,11 @@ describe('ContactSave service', () => { return service .save(form, docId, type) - .then(() => { + .then(({ preparedDocs: savedDocs }) => { assert.equal(get.callCount, 2); assert.deepEqual(get.args[0], ['main1']); assert.deepEqual(get.args[1], ['contact']); - assert.equal(bulkDocs.callCount, 1); - - const savedDocs = bulkDocs.args[0][0]; - assert.equal(savedDocs.length, 1); assert.deepEqual(savedDocs[0], { _id: 'main1', @@ -268,68 +222,6 @@ describe('ContactSave service', () => { data: 'is present', reported_date: 5000, }); - - assert.equal(setLastChangedDoc.callCount, 1); - assert.deepEqual(setLastChangedDoc.args[0], [savedDocs[0]]); }); }); - - it('should pass the contacts to transitions service before saving and save modified contacts', () => { - const form = { getDataStr: () => '' }; - const docId = null; - const type = 'some-contact-type'; - - enketoTranslationService.contactRecordToJs.returns({ - doc: { _id: 'main1', type: 'main', contact: { _id: 'abc', name: 'Richard' } } - }); - bulkDocs.resolves([]); - get.resolves({ _id: 'abc', name: 'Richard', parent: { _id: 'def' } }); - extractLineageService.extract.returns({ _id: 'abc', parent: { _id: 'def' } }); - transitionsService.applyTransitions.callsFake((docs) => { - const clonedDocs = cloneDeep(docs); // don't mutate so we can assert - clonedDocs[0].transitioned = true; - clonedDocs.push({ this: 'is a new doc' }); - return Promise.resolve(clonedDocs); - }); - clock = sinon.useFakeTimers(1000); - - return service - .save(form, docId, type) - .then(() => { - assert.equal(get.callCount, 1); - assert.equal(get.args[0][0], 'abc'); - - assert.equal(transitionsService.applyTransitions.callCount, 1); - assert.deepEqual(transitionsService.applyTransitions.args[0], [[ - { - _id: 'main1', - contact: { _id: 'abc', parent: { _id: 'def' } }, - contact_type: type, - type: 'contact', - parent: undefined, - reported_date: 1000 - } - ]]); - - assert.equal(bulkDocs.callCount, 1); - const savedDocs = bulkDocs.args[0][0]; - - assert.equal(savedDocs.length, 2); - assert.deepEqual(savedDocs, [ - { - _id: 'main1', - contact: { _id: 'abc', parent: { _id: 'def' } }, - contact_type: type, - type: 'contact', - parent: undefined, - reported_date: 1000, - transitioned: true, - }, - { this: 'is a new doc' }, - ]); - assert.equal(setLastChangedDoc.callCount, 1); - assert.deepEqual(setLastChangedDoc.args[0], [savedDocs[0]]); - }); - }); - }); diff --git a/webapp/tests/karma/ts/services/form.service.spec.ts b/webapp/tests/karma/ts/services/form.service.spec.ts index 033a727c97b..10817bae33c 100644 --- a/webapp/tests/karma/ts/services/form.service.spec.ts +++ b/webapp/tests/karma/ts/services/form.service.spec.ts @@ -1,6 +1,6 @@ import { fakeAsync, flush, TestBed } from '@angular/core/testing'; import sinon from 'sinon'; -import { expect } from 'chai'; +import { assert, expect } from 'chai'; import { provideMockStore } from '@ngrx/store/testing'; import * as _ from 'lodash-es'; import { toBik_text } from 'bikram-sambat'; @@ -30,6 +30,9 @@ import * as medicXpathExtensions from '../../../../src/js/enketo/medic-xpath-ext import { CHTScriptApiService } from '@mm-services/cht-script-api.service'; import { TrainingCardsService } from '@mm-services/training-cards.service'; import { EnketoService, EnketoFormContext } from '@mm-services/enketo.service'; +import { cloneDeep } from 'lodash-es'; +import { ExtractLineageService } from '@mm-services/extract-lineage.service'; +import { EnketoTranslationService } from '@mm-services/enketo-translation.service'; describe('Form service', () => { // return a mock form ready for putting in #dbContent @@ -1278,4 +1281,292 @@ describe('Form service', () => { }); }); }); + + describe('saveContact', () => { + let clock; + let extractLineageService; + let enketoTranslationService; + + beforeEach(() => { + extractLineageService = { extract: sinon.stub() }; + enketoTranslationService = { + contactRecordToJs: sinon.stub(), + }; + + TestBed.configureTestingModule({ + providers: [ + provideMockStore(), + { + provide: DbService, + useValue: { + get: () => ({ getAttachment: dbGetAttachment, get: dbGet, bulkDocs: dbBulkDocs }) + } + }, + { provide: ContactSummaryService, useValue: { get: ContactSummary } }, + { provide: Form2smsService, useValue: { transform: Form2Sms } }, + { provide: SearchService, useValue: { search: Search } }, + { provide: SettingsService, useValue: { get: sinon.stub().resolves({}) } }, + { provide: LineageModelGeneratorService, useValue: LineageModelGenerator }, + { provide: FileReaderService, useValue: FileReader }, + { provide: UserContactService, useValue: { get: UserContact } }, + { provide: UserSettingsService, useValue: { getWithLanguage: UserSettings } }, + { provide: TranslateFromService, useValue: { get: TranslateFrom } }, + { provide: EnketoPrepopulationDataService, useValue: { get: EnketoPrepopulationData } }, + { provide: ExtractLineageService, useValue: extractLineageService }, + { provide: EnketoTranslationService, useValue: enketoTranslationService }, + { provide: AttachmentService, useValue: { add: AddAttachment, remove: removeAttachment } }, + { provide: XmlFormsService, useValue: xmlFormsService }, + { provide: ZScoreService, useValue: zScoreService }, + { provide: CHTScriptApiService, useValue: chtScriptApiService }, + { provide: TransitionsService, useValue: transitionsService }, + { provide: TranslateService, useValue: translateService }, + { provide: TrainingCardsService, useValue: trainingCardsService }, + { provide: FeedbackService, useValue: feedbackService }, + ], + }); + + service = TestBed.inject(FormService); + }); + + afterEach(() => { + clock?.restore(); + }); + + it('fetches and binds db types and minifies string contacts', () => { + const form = { getDataStr: () => '' }; + const docId = null; + const type = 'some-contact-type'; + + enketoTranslationService.contactRecordToJs.returns({ + doc: { _id: 'main1', type: 'main', contact: 'abc' } + }); + dbBulkDocs.resolves([]); + dbGet.resolves({ _id: 'abc', name: 'gareth', parent: { _id: 'def' } }); + extractLineageService.extract.returns({ _id: 'abc', parent: { _id: 'def' } }); + + return service + .saveContact(form, docId, type) + .then(() => { + assert.equal(dbGet.callCount, 1); + assert.equal(dbGet.args[0][0], 'abc'); + + assert.equal(dbBulkDocs.callCount, 1); + + const savedDocs = dbBulkDocs.args[0][0]; + + assert.equal(savedDocs.length, 1); + assert.deepEqual(savedDocs[0].contact, { + _id: 'abc', + parent: { + _id: 'def' + } + }); + assert.equal(setLastChangedDoc.callCount, 1); + assert.deepEqual(setLastChangedDoc.args[0], [savedDocs[0]]); + }); + }); + + it('fetches and binds db types and minifies object contacts', () => { + const form = { getDataStr: () => '' }; + const docId = null; + const type = 'some-contact-type'; + + enketoTranslationService.contactRecordToJs.returns({ + doc: { _id: 'main1', type: 'main', contact: { _id: 'abc', name: 'Richard' } } + }); + dbBulkDocs.resolves([]); + dbGet.resolves({ _id: 'abc', name: 'Richard', parent: { _id: 'def' } }); + extractLineageService.extract.returns({ _id: 'abc', parent: { _id: 'def' } }); + + return service + .saveContact(form, docId, type) + .then(() => { + assert.equal(dbGet.callCount, 1); + assert.equal(dbGet.args[0][0], 'abc'); + + assert.equal(dbBulkDocs.callCount, 1); + + const savedDocs = dbBulkDocs.args[0][0]; + + assert.equal(savedDocs.length, 1); + assert.deepEqual(savedDocs[0].contact, { + _id: 'abc', + parent: { + _id: 'def' + } + }); + assert.equal(setLastChangedDoc.callCount, 1); + assert.deepEqual(setLastChangedDoc.args[0], [savedDocs[0]]); + }); + }); + + it('should include parent ID in repeated children', () => { + const form = { getDataStr: () => '' }; + const docId = null; + const type = 'some-contact-type'; + + enketoTranslationService.contactRecordToJs.returns({ + doc: { _id: 'main1', type: 'main', contact: 'NEW'}, + siblings: { + contact: { _id: 'sis1', type: 'sister', parent: 'PARENT', }, + }, + repeats: { + child_data: [ { _id: 'kid1', type: 'child', parent: 'PARENT', } ], + }, + }); + + extractLineageService.extract.callsFake(contact => { + contact.extracted = true; + return contact; + }); + + dbBulkDocs.resolves([]); + + return service + .saveContact(form, docId, type) + .then(() => { + assert.isTrue(dbBulkDocs.calledOnce); + + const savedDocs = dbBulkDocs.args[0][0]; + + assert.equal(savedDocs[0]._id, 'main1'); + + assert.equal(savedDocs[1]._id, 'kid1'); + assert.equal(savedDocs[1].parent._id, 'main1'); + assert.equal(savedDocs[1].parent.extracted, true); + + assert.equal(savedDocs[2]._id, 'sis1'); + assert.equal(savedDocs[2].parent._id, 'main1'); + assert.equal(savedDocs[2].parent.extracted, true); + + assert.equal(extractLineageService.extract.callCount, 3); + + assert.equal(setLastChangedDoc.callCount, 1); + assert.deepEqual(setLastChangedDoc.args[0], [savedDocs[0]]); + }); + }); + + it('should copy old properties for existing contacts', () => { + const form = { getDataStr: () => '' }; + const docId = 'main1'; + const type = 'some-contact-type'; + + enketoTranslationService.contactRecordToJs.returns({ + doc: { + _id: 'main1', + type: 'contact', + contact_type: 'some-contact-type', + contact: { _id: 'contact', name: 'Richard' }, + value: undefined, + } + }); + dbBulkDocs.resolves([]); + dbGet + .withArgs('main1') + .resolves({ + _id: 'main1', + name: 'Richard', + parent: { _id: 'def' }, + value: 33, + some: 'additional', + data: 'is present', + }) + .withArgs('contact') + .resolves({ _id: 'contact', name: 'Richard', parent: { _id: 'def' } }); + + extractLineageService.extract + .withArgs(sinon.match({ _id: 'contact' })) + .returns({ _id: 'contact', parent: { _id: 'def' } }) + .withArgs(sinon.match({ _id: 'def' })) + .returns({ _id: 'def' }); + clock = sinon.useFakeTimers(5000); + + return service + .saveContact(form, docId, type) + .then(() => { + assert.equal(dbGet.callCount, 2); + assert.deepEqual(dbGet.args[0], ['main1']); + assert.deepEqual(dbGet.args[1], ['contact']); + + assert.equal(dbBulkDocs.callCount, 1); + + const savedDocs = dbBulkDocs.args[0][0]; + + assert.equal(savedDocs.length, 1); + assert.deepEqual(savedDocs[0], { + _id: 'main1', + type: 'contact', + name: 'Richard', + contact_type: 'some-contact-type', + contact: { _id: 'contact', parent: { _id: 'def' } }, + parent: { _id: 'def' }, + value: 33, + some: 'additional', + data: 'is present', + reported_date: 5000, + }); + + assert.equal(setLastChangedDoc.callCount, 1); + assert.deepEqual(setLastChangedDoc.args[0], [savedDocs[0]]); + }); + }); + + it('should pass the contacts to transitions service before saving and save modified contacts', () => { + const form = { getDataStr: () => '' }; + const docId = null; + const type = 'some-contact-type'; + + enketoTranslationService.contactRecordToJs.returns({ + doc: { _id: 'main1', type: 'main', contact: { _id: 'abc', name: 'Richard' } } + }); + dbBulkDocs.resolves([]); + dbGet.resolves({ _id: 'abc', name: 'Richard', parent: { _id: 'def' } }); + extractLineageService.extract.returns({ _id: 'abc', parent: { _id: 'def' } }); + transitionsService.applyTransitions.callsFake((docs) => { + const clonedDocs = cloneDeep(docs); // don't mutate so we can assert + clonedDocs[0].transitioned = true; + clonedDocs.push({ this: 'is a new doc' }); + return Promise.resolve(clonedDocs); + }); + clock = sinon.useFakeTimers(1000); + + return service + .saveContact(form, docId, type) + .then(() => { + assert.equal(dbGet.callCount, 1); + assert.equal(dbGet.args[0][0], 'abc'); + + assert.equal(transitionsService.applyTransitions.callCount, 1); + assert.deepEqual(transitionsService.applyTransitions.args[0], [[ + { + _id: 'main1', + contact: { _id: 'abc', parent: { _id: 'def' } }, + contact_type: type, + type: 'contact', + parent: undefined, + reported_date: 1000 + } + ]]); + + assert.equal(dbBulkDocs.callCount, 1); + const savedDocs = dbBulkDocs.args[0][0]; + + assert.equal(savedDocs.length, 2); + assert.deepEqual(savedDocs, [ + { + _id: 'main1', + contact: { _id: 'abc', parent: { _id: 'def' } }, + contact_type: type, + type: 'contact', + parent: undefined, + reported_date: 1000, + transitioned: true, + }, + { this: 'is a new doc' }, + ]); + assert.equal(setLastChangedDoc.callCount, 1); + assert.deepEqual(setLastChangedDoc.args[0], [savedDocs[0]]); + }); + }); + }); }); diff --git a/webapp/web-components/cht-form/README.md b/webapp/web-components/cht-form/README.md new file mode 100644 index 00000000000..20c99939ce1 --- /dev/null +++ b/webapp/web-components/cht-form/README.md @@ -0,0 +1,103 @@ +# cht-form Web Component + +This [Angular Element](https://angular.io/guide/elements) encapsulates the functionality required for displaying and interacting with an Enketo form into a custom [Web Component](https://developer.mozilla.org/en-US/docs/Web/Web_Components). The `cht-form` web component can be used in any web page to display and interact with an Enketo form. The form layout and behavior will be functionally identical to form interactions within the CHT webapp. + +## Building the web component + +To build the web component, run the following commands from the root directory of cht-core: + +```shell +npm ci +npm run build-cht-form +``` + +This will build the `cht-form` component and place the build artifacts in the `./build/cht-form` directory. + +## Using the web component + +The `cht-form` web component can be included on any page by pulling in the following files: + +- `runtime.js` +- `polyfills.js` +- `scripts.js` +- `main.js` +- `styles.css` + +With those files included on the page, you can then use the `cht-form` web component in your HTML: + +```html + +``` + +### Form inputs + +The following inputs are supported by the `cht-form` web component: + +- `formHtml` (Required) - String value containing the HTML content of the form to render. +- `formModel` (Required) - String value containing the model xml of the form to render. +- `formXml` (Required) - String value containing the ODK xform xml of the form to render. +- `contactType` (Required for contact forms) - String value containing the contact type of the form to render (e.g. `person`, `district_hospital`, etc). +- `user` - The user's settings document from the CouchDB `_users` database. +- `contactSummary` - The context data for the contact summary of the form's contact. Must not be set for `contact` + forms. +- `content` - The content data to provide when rendering the form. The `contact` field can be the hydrated contact + document for the form's contact. + +These inputs can be set directly onto the `cht-form` HTML element: + +```js +const myForm = document.getElementById('myform'); +myForm.user = { contact_id: 'test_user' }; +myForm.contactSummary = { pregnancy_uuid: 'myPregUUID' }; +myForm.content = { contact: { name: "My Test Patient" } }; + +myForm.formHtml = formData.formHtml; +myForm.formModel = formData.formModel; +myForm.formXml = formData.formXml; +``` + +### Form outputs + +Outputs from the web component are emitted as events. The following events are emitted: + +- `onRender` - Emitted when the form is finished rendering. +- `onSubmit` - Emitted when the form is submitted (the submit button is pressed). The event detail contains an array of + docs created by the form. +- `onCancel` - Emitted when the form is cancelled (the cancel button is pressed). + +```js +myForm.addEventListener('onSubmit', async (e) => { + console.log('form submitted', e.detail); +}); +``` + +### Customizing the page layout + +Currently, the position of the form on a page needs to be absolute (for all the magical "reactiveness" to work properly). To adjust _where_ on the page the content is displayed, you can override the value for: + +```css +.content .page { + top: ???px !important; +} +``` + +Also, for more complex layouts, you should use bootstrap containers to manage the position of the various elements on the page: + +```html +
+
+

Hello, world!

+
+
+ +
+
+``` + +## Example Page + +The included `index.html` provides an example of a simple web page using the `cht-form` web component to display a basic form. Once you have built the web component (as described above), you can deploy the example page using the following command: + +```shell +npx -y http-server build/cht-form +``` diff --git a/webapp/web-components/cht-form/package.json b/webapp/web-components/cht-form/package.json new file mode 100644 index 00000000000..570238f21ac --- /dev/null +++ b/webapp/web-components/cht-form/package.json @@ -0,0 +1,15 @@ +{ + "name": "cht-form", + "version": "1.0.0", + "description": "Web Component for CHT forms", + "author": "Medic", + "license": "AGPL-3.0-only", + "repository": { + "type": "git", + "url": "https://github.com/medic/cht-core.git" + }, + "bugs": { + "url": "https://github.com/medic/cht-core/issues" + }, + "homepage": "https://github.com/medic/cht-core/blob/master/webapp/web-components/cht-form/README.md" +} diff --git a/webapp/web-components/cht-form/src/app.component.html b/webapp/web-components/cht-form/src/app.component.html new file mode 100644 index 00000000000..cb575955fa3 --- /dev/null +++ b/webapp/web-components/cht-form/src/app.component.html @@ -0,0 +1,29 @@ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
diff --git a/webapp/web-components/cht-form/src/app.component.ts b/webapp/web-components/cht-form/src/app.component.ts new file mode 100644 index 00000000000..f58eaeb460c --- /dev/null +++ b/webapp/web-components/cht-form/src/app.component.ts @@ -0,0 +1,246 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { EnketoFormContext, EnketoService } from '@mm-services/enketo.service'; +import * as medicXpathExtensions from '../../../src/js/enketo/medic-xpath-extensions'; +import moment from 'moment'; +import { toBik_text } from 'bikram-sambat'; +import { TranslateService } from '@mm-services/translate.service'; +import { ContactSaveService } from '@mm-services/contact-save.service'; + +@Component({ + selector: 'cht-form', + templateUrl: './app.component.html', +}) +export class AppComponent { + private readonly DEFAULT_FORM_ID = 'cht-form-id'; + private readonly DEFAULT_USER = { contact_id: 'default_user', language: 'en' } as const; + private readonly DEFAULT_STATUS = { + saving: false as boolean, + error: null as string | null, + } as const; + + private readonly HARDCODED_TYPES = [ + 'district_hospital', + 'health_center', + 'clinic', + 'person' + ]; + + private _formId = this.DEFAULT_FORM_ID; + private _formXml?: string; + private _formModel?: string; + private _formHtml?: string; + private _contactSummary?: Record; + private _contactType?: string; + private _content: Record | null = null; + private _user: typeof this.DEFAULT_USER & Record = this.DEFAULT_USER; + + private currentRender?: Promise; + private reRenderForm = false; + + @Input() editing = false; + @Input() status = { ...this.DEFAULT_STATUS }; + + @Output() onRender: EventEmitter = new EventEmitter(); + @Output() onCancel: EventEmitter = new EventEmitter(); + @Output() onSubmit: EventEmitter = new EventEmitter(); + + constructor( + private contactSaveService: ContactSaveService, + private enketoService: EnketoService, + private translateService: TranslateService, + ) { + const zscoreUtil = {}; + const api = {}; + medicXpathExtensions.init(zscoreUtil, toBik_text, moment, api); + } + + @Input() set formId(value: string) { + if (!value?.trim().length) { + throw new Error('The Form Id must be populated.'); + } + this._formId = value; + this.queueRenderForm(); + } + + @Input() set formHtml(value: string) { + if (!value?.trim().length) { + throw new Error('The Form HTML must be populated.'); + } + this._formHtml = value; + this.queueRenderForm(); + } + + @Input() set formModel(value: string) { + if (!value?.trim().length) { + throw new Error('The Form Model must be populated.'); + } + this._formModel = value; + this.queueRenderForm(); + } + + @Input() set formXml(value: string) { + if (!value?.trim().length) { + throw new Error('The Form XML must be populated.'); + } + this._formXml = value; + this.queueRenderForm(); + } + + @Input() set contactSummary(value: Record | undefined) { + this._contactSummary = value ? { context: value } : undefined; + this.queueRenderForm(); + } + + @Input() set contactType(value: string | undefined) { + this._contactType = value; + this.queueRenderForm(); + } + + @Input() set content(value: Record | null) { + this._content = value; + if (this._content?.contact && !this._content.source) { + this._content.source = 'contact'; + } + this.queueRenderForm(); + } + + @Input() set user(user: typeof this.DEFAULT_USER & Record) { + if (!user) { + throw new Error('The user must be populated.'); + } + this._user = { ...this.DEFAULT_USER, ...user }; + this.queueRenderForm(); + } + + get formId(): string { + return this._formId; + } + + cancelForm(): void { + this.tearDownForm(); + this.onCancel.emit(); + } + + async submitForm(): Promise { + this.status.saving = true; + + try { + const submittedDocs = await this.getDocsFromForm(); + this.onSubmit.emit(submittedDocs); + } catch (e) { + console.error('Error submitting form data: ', e); + this.status.error = await this.translateService.get('error.report.save'); + } finally { + this.status.saving = false; + } + } + + private async getDocsFromForm() { + const currentForm = this.enketoService.getCurrentForm(); + if (this._contactType) { + const typeFields = this.HARDCODED_TYPES.includes(this._contactType) + ? { type: this._contactType } + : { type: 'contact', contact_type: this._contactType }; + const { preparedDocs } = await this.contactSaveService.save(currentForm, null, typeFields, null); + return preparedDocs; + } + + const formDoc = { + xml: this._formXml, + doc: {} + }; + return this.enketoService.completeNewReport(this._formId, currentForm, formDoc, this._content?.contact); + } + + private queueRenderForm() { + if (this.currentRender) { + this.reRenderForm = true; + return; + } + this.currentRender = this + .renderForm() + .catch(e => console.error('Error rendering form: ', e)) + .finally(() => { + this.currentRender = undefined; + if (this.reRenderForm) { + this.reRenderForm = false; + this.queueRenderForm(); + } + }); + } + + private async renderForm() { + this.unloadForm(); + if (!this._formHtml || !this._formModel || !this._formXml) { + return; + } + + const selector = `#${this._formId}`; + // Can have a race condition where the formId is set here but the Angular attribute data binding has not updated + // the DOM yet (the formId is used as the id of the .enketo element) + if (!$(selector).length) { + return this.waitForSelector(selector) + .then(() => this.renderForm()); + } + + const editedListener = () => this.editing = true; + const valuechangeListener = () => this.status.error = null; + const formDoc = { _id: this._formId }; + const type = this._contactType ? 'contact': 'report'; + const formContext = new EnketoFormContext(selector, type, formDoc, this._content); + formContext.editedListener = editedListener; + formContext.valuechangeListener = valuechangeListener; + formContext.contactSummary = this._contactSummary; + const formDetails = this.getFormDetails(); + await this.enketoService.renderForm(formContext, formDetails, this._user); + this.onRender.emit(); + } + + private unloadForm() { + const currentForm = this.enketoService.getCurrentForm(); + if (currentForm) { + this.enketoService.unload(currentForm); + } + } + + private async waitForSelector(selector: string) { + return new Promise(resolve => { + const observer = new MutationObserver(() => { + if ($(selector).length) { + observer.disconnect(); + return resolve(true); + } + }); + observer.observe($('.body').get(0)!, { + subtree: true, + attributeFilter: ['id'], + }); + }); + } + + private getFormDetails() { + const $html = $(this._formHtml!); + const hasContactSummary = $(this._formModel!) + .find('> instance[id="contact-summary"]') + .length === 1; + return { + html: $html, + model: this._formModel, + hasContactSummary: hasContactSummary + }; + } + + private tearDownForm() { + this.unloadForm(); + this._formXml = undefined; + this._formHtml = undefined; + this._formModel = undefined; + this._contactSummary = undefined; + this._contactType = undefined; + this._content = null; + this._formId = this.DEFAULT_FORM_ID; + this._user = this.DEFAULT_USER; + this.editing = false; + this.status = { ...this.DEFAULT_STATUS }; + } +} diff --git a/webapp/web-components/cht-form/src/app.module.ts b/webapp/web-components/cht-form/src/app.module.ts new file mode 100644 index 00000000000..09307c0f3f2 --- /dev/null +++ b/webapp/web-components/cht-form/src/app.module.ts @@ -0,0 +1,51 @@ +import { TranslateCompiler, TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { DbService } from '@mm-services/db.service'; +import { TranslationLoaderProvider } from '@mm-providers/translation-loader.provider'; +import { TranslateMessageFormatCompilerProvider } from '@mm-providers/translate-messageformat-compiler.provider'; +import { BrowserModule } from '@angular/platform-browser'; +import { DoBootstrap, Injector, NgModule } from '@angular/core'; +import { createCustomElement } from '@angular/elements'; +import { AppComponent } from './app.component'; +import { TranslateService } from '@mm-services/translate.service'; + +@NgModule({ + declarations: [AppComponent], + imports: [ + BrowserModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: (db: DbService) => new TranslationLoaderProvider(db), + deps: [DbService], + }, + compiler: { + provide: TranslateCompiler, + useClass: TranslateMessageFormatCompilerProvider, + }, + }), + ] +}) +export class AppModule implements DoBootstrap { + constructor( + injector: Injector, + private dbService: DbService, + private translateService: TranslateService + ) { + const chtForm = createCustomElement(AppComponent, { injector }); + customElements.define('cht-form', chtForm); + } + + ngDoBootstrap() { + window.CHTCore = { + AndroidAppLauncher: { isEnabled: () => false }, + Language: { get: async () => 'en' }, + MRDT: { enabled: () => false }, + Select2Search: { + init: async () => {} + }, + Settings: { get: async () => ({ default_country_code: '1' }) }, + Translate: this.translateService, + DB: this.dbService, + }; + } +} diff --git a/webapp/web-components/cht-form/src/index.html b/webapp/web-components/cht-form/src/index.html new file mode 100644 index 00000000000..dec84314fce --- /dev/null +++ b/webapp/web-components/cht-form/src/index.html @@ -0,0 +1,98 @@ + + + + + Example Website with Embedded cht-form Web Component + + +
+ +
+ +
+ +
+ + + + diff --git a/webapp/web-components/cht-form/src/main.ts b/webapp/web-components/cht-form/src/main.ts new file mode 100644 index 00000000000..7cd17ff5321 --- /dev/null +++ b/webapp/web-components/cht-form/src/main.ts @@ -0,0 +1,32 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { AppModule } from './app.module'; +import * as $ from 'jquery'; + +window.$ = window.jQuery = require('jquery'); + +// Moment additional locales +require('../../../src/js/moment-locales/tl'); +require('../../../src/js/moment-locales/hil'); +require('../../../src/js/moment-locales/ceb'); +require('../../../src/js/moment-locales/lg'); +require('moment/locale/fr'); +require('moment/locale/es'); +require('moment/locale/bm'); +require('moment/locale/hi'); +require('moment/locale/id'); +require('moment/locale/ne'); +require('moment/locale/sw'); +require('select2'); +require(`../../../src/js/enketo/main`); + +// Enable jQuery support for self-closing xml tags +// https://jquery.com/upgrade-guide/3.5/ +const rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi; +// eslint-disable-next-line no-import-assign +Object.defineProperties($, { // NOSONAR + htmlPrefilter: { value: (html) => html.replace(rxhtmlTag, '<$1>') } +}); + +platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/webapp/web-components/cht-form/src/stubs/db.service.ts b/webapp/web-components/cht-form/src/stubs/db.service.ts new file mode 100644 index 00000000000..c7c632896f3 --- /dev/null +++ b/webapp/web-components/cht-form/src/stubs/db.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class DbService { + public get(): any { + return { + get: () => Promise.resolve(), + query: async (selector, options) => { + if (selector === 'medic-client/contacts_by_phone') { + // Used by phone-widget to look for contacts with same phone number + return { rows: [] }; + } + throw new Error(`Unsupported selector: DbService.get.query(${selector}, ${JSON.stringify(options)})`); + }, + getAttachment: () => { + // assume media attachment + return Promise.resolve(new Blob()); + }, + }; + } +} diff --git a/webapp/web-components/cht-form/tests/.eslintrc b/webapp/web-components/cht-form/tests/.eslintrc new file mode 100644 index 00000000000..9785b4f8a09 --- /dev/null +++ b/webapp/web-components/cht-form/tests/.eslintrc @@ -0,0 +1,14 @@ +{ + "env": { + "mocha": true, + "browser": true + }, + "rules": { + "no-console": "off", + "angular/window-service": "off", + "angular/timeout-service": "off" + }, + "parserOptions": { + "ecmaVersion": 2018 + } +} diff --git a/webapp/web-components/cht-form/tests/karma/app.component.spec.ts b/webapp/web-components/cht-form/tests/karma/app.component.spec.ts new file mode 100644 index 00000000000..6f4e27eedce --- /dev/null +++ b/webapp/web-components/cht-form/tests/karma/app.component.spec.ts @@ -0,0 +1,786 @@ +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import moment from 'moment'; +import { toBik_text } from 'bikram-sambat'; +import sinon from 'sinon'; +import { expect } from 'chai'; + +import { AppComponent } from '../../src/app.component'; +import * as medicXpathExtensions from '../../../../src/js/enketo/medic-xpath-extensions'; +import { EnketoService } from '@mm-services/enketo.service'; +import { ContactSaveService } from '@mm-services/contact-save.service'; + +describe('AppComponent', () => { + const FORM_ID = 'cht-form-id'; + const FORM_HTML = '
my form
'; + const FORM_MODEL = ''; + const FORM_XML = '
'; + const USER = { + contact_id: 'default_user', + language: 'en', + } as const; + + let contactSaveService; + let enketoService; + let fixture; + + const getComponent = () => { + fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + return fixture.componentInstance; + }; + + beforeEach(async () => { + contactSaveService = { save: sinon.stub() }; + enketoService = { + renderForm: sinon + .stub() + .resolves(), + getCurrentForm: sinon.stub(), + completeNewReport: sinon.stub(), + unload: sinon.stub(), + }; + await TestBed + .configureTestingModule({ + declarations: [AppComponent], + imports: [TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } })], + providers: [ + { provide: ContactSaveService, useValue: contactSaveService }, + { provide: EnketoService, useValue: enketoService }, + ] + }) + .compileComponents(); + }); + + afterEach(sinon.restore); + + it('creates component and init XPath extensions', async () => { + const medicXpathExtensionsInit = sinon.spy(medicXpathExtensions, 'init'); + const component = await getComponent(); + + expect(component).to.exist; + expect(medicXpathExtensionsInit.callCount).to.equal(1); + expect(medicXpathExtensionsInit.args[0]).to.deep.equal([ + {}, + toBik_text, + moment, + {} + ]); + expect(component.formId).to.eq(FORM_ID); + expect(component.editing).to.be.false; + expect(component.status).to.deep.equal({ + saving: false, + error: null + }); + }); + + it('renders form when required fields are set', fakeAsync(async () => { + const component = await getComponent(); + const onRender = sinon.stub(); + component.onRender.subscribe(onRender); + + expect(enketoService.renderForm.called).to.be.false; + expect(onRender.called).to.be.false; + component.formXml = FORM_XML; + tick(); + expect(enketoService.renderForm.called).to.be.false; + expect(onRender.called).to.be.false; + component.formModel = FORM_MODEL; + tick(); + expect(enketoService.renderForm.called).to.be.false; + expect(onRender.called).to.be.false; + component.formHtml = FORM_HTML; + tick(); + + expect(enketoService.getCurrentForm.callCount).to.equal(3); + expect(enketoService.renderForm.callCount).to.equal(1); + expect(onRender.callCount).to.equal(1); + const actualContext = enketoService.renderForm.args[0][0]; + expect(actualContext).to.deep.include({ + selector: `#${FORM_ID}`, + type: 'report', + formDoc: { _id: FORM_ID }, + instanceData: null, + contactSummary: undefined + }); + expect(actualContext.editedListener).to.exist; + expect(actualContext.valuechangeListener).to.exist; + expect(enketoService.renderForm.args[0][1]).to.deep.equal({ + html: $(FORM_HTML), + model: FORM_MODEL, + hasContactSummary: false + }); + expect(enketoService.renderForm.args[0][2]).to.deep.equal(USER); + expect(enketoService.unload.called).to.be.false; + })); + + it('renders form with optional field values', fakeAsync(async () => { + const formId = 'test-form-id'; + const formModel = ''; + const user = { + contact_id: 'spanish_user', + language: 'es', + custom: 'user field' + }; + const contactSummary = { hello: 'world' }; + const content = { my: 'content' }; + + const component = await getComponent(); + const onRender = sinon.stub(); + component.onRender.subscribe(onRender); + component.formId = formId; + // Trigger change detection to update the bound id attribute + fixture.detectChanges(); + tick(); + component.user = user; + tick(); + component.contactSummary = contactSummary; + tick(); + component.contactType = 'person'; + tick(); + component.content = content; + tick(); + component.formXml = FORM_XML; + tick(); + component.formModel = formModel; + tick(); + component.formHtml = FORM_HTML; + tick(); + + expect(enketoService.renderForm.callCount).to.equal(1); + expect(onRender.callCount).to.equal(1); + const actualContext = enketoService.renderForm.args[0][0]; + expect(actualContext).to.deep.include({ + selector: `#${formId}`, + type: 'contact', + formDoc: { _id: formId }, + instanceData: content, + contactSummary: { context: contactSummary } + }); + expect(actualContext.editedListener).to.exist; + expect(actualContext.valuechangeListener).to.exist; + expect(enketoService.renderForm.args[0][1]).to.deep.equal({ + html: $(FORM_HTML), + model: formModel, + hasContactSummary: true + }); + expect(enketoService.renderForm.args[0][2]).to.deep.equal(user); + expect(enketoService.unload.called).to.be.false; + + // Null out the optional fields and render again + component.contactSummary = undefined; + component.contactType = undefined; + component.content = null; + tick(); + + // Form is rendered again, but without instanceData or contactSummary + expect(enketoService.renderForm.callCount).to.equal(3); + expect(onRender.callCount).to.equal(3); + const actualContext1 = enketoService.renderForm.args[2][0]; + expect(actualContext1).to.deep.include({ + selector: `#${formId}`, + type: 'report', + formDoc: { _id: formId }, + instanceData: null, + contactSummary: undefined + }); + expect(enketoService.renderForm.args[2][1]).to.deep.equal({ + html: $(FORM_HTML), + model: formModel, + hasContactSummary: true + }); + expect(enketoService.renderForm.args[2][2]).to.deep.equal(user); + expect(enketoService.unload.called).to.be.false; + })); + + it('renders form with default fields missing from the provided user', fakeAsync(async () => { + const component = await getComponent(); + component.user = { hello: 'world' }; + tick(); + component.formXml = FORM_XML; + tick(); + component.formModel = FORM_MODEL; + tick(); + component.formHtml = FORM_HTML; + tick(); + expect(enketoService.renderForm.callCount).to.equal(1); + expect(enketoService.renderForm.args[0][2]).to.deep.equal({ + contact_id: 'default_user', + language: 'en', + hello: 'world' + }); + })); + + it('waits for form id to be set on DOM when rendering', fakeAsync(async () => { + const MutationObserver = global.MutationObserver; + const mutationObserverMock = { + observe: sinon.stub(), + disconnect: sinon.stub(), + takeRecords: sinon.stub(), + }; + const mutationObserverConst = sinon.stub().returns(mutationObserverMock); + global.MutationObserver = mutationObserverConst as any; + + try { + const formId = 'test-form-id'; + const component = await getComponent(); + const onRender = sinon.stub(); + component.onRender.subscribe(onRender); + + component.formId = formId; + component.formXml = FORM_XML; + component.formModel = FORM_MODEL; + component.formHtml = FORM_HTML; + tick(); + // Trigger change detection to update the bound id attribute + fixture.detectChanges(); + expect(mutationObserverConst.callCount).to.equal(1); + expect(mutationObserverMock.observe.callCount).to.equal(1); + expect(mutationObserverMock.observe.args[0]).to.deep.equal([ + $('.body').get(0)!, + { + subtree: true, + attributeFilter: ['id'], + } + ]); + // Manually trigger the observer callback + mutationObserverConst.args[0][0](); + expect(mutationObserverMock.disconnect.callCount).to.equal(1); + tick(); + + expect(enketoService.getCurrentForm.callCount).to.equal(3); + expect(enketoService.renderForm.callCount).to.equal(1); + expect(onRender.callCount).to.equal(1); + const actualContext = enketoService.renderForm.args[0][0]; + expect(actualContext).to.deep.include({ + selector: `#${formId}`, + type: 'report', + formDoc: { _id: formId }, + instanceData: null, + contactSummary: undefined + }); + } finally { + global.MutationObserver = MutationObserver; + } + })); + + it('renders form with proper source value when contact is provided', fakeAsync(async () => { + const content = { contact: { name: 'my contact' } }; + + const component = await getComponent(); + component.content = content; + tick(); + component.formXml = FORM_XML; + tick(); + component.formModel = FORM_MODEL; + tick(); + component.formHtml = FORM_HTML; + tick(); + expect(enketoService.renderForm.callCount).to.equal(1); + const actualContext = enketoService.renderForm.args[0][0]; + expect(actualContext).to.deep.include({ + selector: `#${FORM_ID}`, + type: 'report', + formDoc: { _id: FORM_ID }, + instanceData: { ...content, source: 'contact' }, + contactSummary: undefined + }); + })); + + it('renders form with given source value even if contact is provided', fakeAsync(async () => { + const content = { + contact: { name: 'my contact' }, + source: 'task' + }; + + const component = await getComponent(); + component.content = content; + tick(); + component.formXml = FORM_XML; + tick(); + component.formModel = FORM_MODEL; + tick(); + component.formHtml = FORM_HTML; + tick(); + expect(enketoService.renderForm.callCount).to.equal(1); + const actualContext = enketoService.renderForm.args[0][0]; + expect(actualContext).to.deep.include({ + selector: `#${FORM_ID}`, + type: 'report', + formDoc: { _id: FORM_ID }, + instanceData: content, + contactSummary: undefined + }); + })); + + it('re-renders form when any field is set', fakeAsync(async () => { + const firstFormId = 'first-form-id'; + const formId = 'test-form-id'; + const formModel = ''; + const user = { + contact_id: 'spanish_user', + language: 'es', + }; + const contactSummary = { hello: 'world' }; + const content = { my: 'content' }; + const currentForm = { _id: 'current-form' }; + enketoService.getCurrentForm.returns(currentForm); + + const component = await getComponent(); + const onRender = sinon.stub(); + component.onRender.subscribe(onRender); + component.formXml = FORM_XML; + component.formModel = formModel; + component.formHtml = FORM_HTML; + component.formId = firstFormId; + // Trigger change detection to update the bound id attribute + fixture.detectChanges(); + component.user = user; + component.contactSummary = contactSummary; + component.contactType = 'person'; + component.content = content; + // Set the form ID again + component.formId = formId; + // Trigger change detection to update the bound id attribute + fixture.detectChanges(); + tick(); + // The queue of renderForm calls gets processed in the tick + expect(enketoService.renderForm.callCount).to.equal(1); + expect(onRender.callCount).to.equal(1); + // The last render call contains the latest values + const actualContext = enketoService.renderForm.args[0][0]; + expect(actualContext).to.deep.include({ + selector: `#${formId}`, + type: 'contact', + formDoc: { _id: formId }, + instanceData: content, + contactSummary: { context: contactSummary } + }); + expect(actualContext.editedListener).to.exist; + expect(actualContext.valuechangeListener).to.exist; + expect(enketoService.renderForm.args[0][1]).to.deep.equal({ + html: $(FORM_HTML), + model: formModel, + hasContactSummary: true + }); + expect(enketoService.renderForm.args[0][2]).to.deep.equal(user); + expect(enketoService.unload.callCount).to.equal(2); + enketoService.unload.args.forEach((args) => expect(args).to.deep.equal([currentForm])); + })); + + [ + null, + undefined, + '', + ' ', + ].forEach((value) => { + it(`throws error when setting [${value}] Form Id`, fakeAsync(async () => { + const component = await getComponent(); + expect(() => component.formId = value as unknown as string).to.throw('The Form Id must be populated.'); + })); + + it(`throws error when setting [${value}] Form HTML`, fakeAsync(async () => { + const component = await getComponent(); + expect(() => component.formHtml = value as unknown as string).to.throw('The Form HTML must be populated.'); + })); + + it(`throws error when setting [${value}] Form Model`, fakeAsync(async () => { + const component = await getComponent(); + expect(() => component.formModel = value as unknown as string).to.throw('The Form Model must be populated.'); + })); + + it(`throws error when setting [${value}] Form XML`, fakeAsync(async () => { + const component = await getComponent(); + expect(() => component.formXml = value as unknown as string).to.throw('The Form XML must be populated.'); + })); + }); + + it(`throws error when setting null user`, fakeAsync(async () => { + const component = await getComponent(); + expect(() => component.user = null as unknown as Record).to.throw('The user must be populated.'); + })); + + it('sets the editing value to true when the edited listener is triggered', fakeAsync(async () => { + const component = await getComponent(); + component.formXml = FORM_XML; + tick(); + component.formModel = FORM_MODEL; + tick(); + component.formHtml = FORM_HTML; + tick(); + expect(enketoService.renderForm.callCount).to.equal(1); + const actualContext = enketoService.renderForm.args[0][0]; + expect(actualContext.editedListener).to.exist; + expect(component.editing).to.be.false; + actualContext.editedListener(); + expect(component.editing).to.be.true; + })); + + it('clears the error status value when the value change listener is triggered', fakeAsync(async () => { + const component = await getComponent(); + component.status.error = 'some error'; + component.formXml = FORM_XML; + tick(); + component.formModel = FORM_MODEL; + tick(); + component.formHtml = FORM_HTML; + tick(); + expect(enketoService.renderForm.callCount).to.equal(1); + const actualContext = enketoService.renderForm.args[0][0]; + expect(actualContext.editedListener).to.exist; + expect(component.status.error).to.equal('some error'); + actualContext.valuechangeListener(); + expect(component.status.error).to.be.null; + })); + + it('submits form by completing the report and emitting the docs', fakeAsync(async () => { + const expectedDocs = [ + { _id: 'doc1' }, + { _id: 'doc2' }, + ]; + enketoService.completeNewReport.resolves(expectedDocs); + const currentForm = { _id: 'current-form' }; + const formId = 'test-form-id'; + const formXml = '
custom
'; + const formModel = ''; + const formHtml = '
custom
'; + const user = { + contact_id: 'spanish_user', + language: 'es', + }; + const contactSummary = { hello: 'world' }; + const contact = { phone: '12345' }; + const content = { my: 'content', contact }; + + const component = getComponent(); + component.formId = formId; + // Trigger change detection to update the bound id attribute + fixture.detectChanges(); + tick(); + component.user = user; + tick(); + component.contactSummary = contactSummary; + tick(); + component.content = content; + tick(); + component.formXml = formXml; + tick(); + component.formModel = formModel; + tick(); + component.formHtml = formHtml; + tick(); + component.editing = true; + + expect(enketoService.getCurrentForm.callCount).to.equal(7); + expect(enketoService.renderForm.callCount).to.equal(1); + enketoService.getCurrentForm + .onCall(7) + .returns(currentForm); + enketoService.getCurrentForm + .onCall(8) + .returns(currentForm); + + let actualSubmittedDocs; + component.onSubmit.subscribe((submittedDocs) => { + actualSubmittedDocs = submittedDocs; + }); + + const submitPromise = component.submitForm(); + expect(component.status.saving).to.be.true; + await submitPromise; + tick(); + expect(enketoService.getCurrentForm.callCount).to.equal(8); + expect(contactSaveService.save.called).to.be.false; + expect(enketoService.completeNewReport.callCount).to.equal(1); + const expectedFormDoc = { + xml: formXml, + doc: {} + }; + expect(enketoService.completeNewReport.args[0]).to.deep.equal([ + formId, + currentForm, + expectedFormDoc, + contact + ]); + expect(actualSubmittedDocs).to.deep.equal(expectedDocs); + })); + + it('submits contact form with default type', fakeAsync(async () => { + const expectedDocs = [ + { _id: 'doc1' }, + { _id: 'doc2' }, + ]; + contactSaveService.save.resolves({ preparedDocs: expectedDocs }); + const currentForm = { _id: 'current-form' }; + const formId = 'test-form-id'; + const formXml = '
custom
'; + const formModel = ''; + const formHtml = '
custom
'; + const user = { + contact_id: 'spanish_user', + language: 'es', + }; + const contactSummary = { hello: 'world' }; + const content = { my: 'content' }; + + const component = getComponent(); + component.formId = formId; + // Trigger change detection to update the bound id attribute + fixture.detectChanges(); + tick(); + component.user = user; + tick(); + component.contactSummary = contactSummary; + tick(); + component.contactType = 'person'; + tick(); + component.content = content; + tick(); + component.formXml = formXml; + tick(); + component.formModel = formModel; + tick(); + component.formHtml = formHtml; + tick(); + component.editing = true; + + expect(enketoService.getCurrentForm.callCount).to.equal(8); + expect(enketoService.renderForm.callCount).to.equal(1); + enketoService.getCurrentForm + .onCall(8) + .returns(currentForm); + enketoService.getCurrentForm + .onCall(9) + .returns(currentForm); + + let actualSubmittedDocs; + component.onSubmit.subscribe((submittedDocs) => { + actualSubmittedDocs = submittedDocs; + }); + + const submitPromise = component.submitForm(); + expect(component.status.saving).to.be.true; + await submitPromise; + tick(); + expect(enketoService.getCurrentForm.callCount).to.equal(9); + expect(enketoService.completeNewReport.called).to.be.false; + expect(contactSaveService.save.callCount).to.equal(1); + expect(contactSaveService.save.args[0]).to.deep.equal([ + currentForm, + null, + { type: 'person' }, + null + ]); + expect(actualSubmittedDocs).to.deep.equal(expectedDocs); + })); + + it('submits contact form with custom type', fakeAsync(async () => { + const expectedDocs = [ + { _id: 'doc1' }, + { _id: 'doc2' }, + ]; + contactSaveService.save.resolves({ preparedDocs: expectedDocs }); + const currentForm = { _id: 'current-form' }; + const formId = 'test-form-id'; + const formXml = '
custom
'; + const formModel = ''; + const formHtml = '
custom
'; + const user = { + contact_id: 'spanish_user', + language: 'es', + }; + const contactSummary = { hello: 'world' }; + const content = { my: 'content' }; + + const component = getComponent(); + component.formId = formId; + // Trigger change detection to update the bound id attribute + fixture.detectChanges(); + tick(); + component.user = user; + tick(); + component.contactSummary = contactSummary; + tick(); + component.contactType = 'custom_contact'; + tick(); + component.content = content; + tick(); + component.formXml = formXml; + tick(); + component.formModel = formModel; + tick(); + component.formHtml = formHtml; + tick(); + component.editing = true; + + expect(enketoService.getCurrentForm.callCount).to.equal(8); + expect(enketoService.renderForm.callCount).to.equal(1); + enketoService.getCurrentForm + .onCall(8) + .returns(currentForm); + enketoService.getCurrentForm + .onCall(9) + .returns(currentForm); + + let actualSubmittedDocs; + component.onSubmit.subscribe((submittedDocs) => { + actualSubmittedDocs = submittedDocs; + }); + + const submitPromise = component.submitForm(); + expect(component.status.saving).to.be.true; + await submitPromise; + tick(); + expect(enketoService.getCurrentForm.callCount).to.equal(9); + expect(enketoService.completeNewReport.called).to.be.false; + expect(contactSaveService.save.callCount).to.equal(1); + expect(contactSaveService.save.args[0]).to.deep.equal([ + currentForm, + null, + { type: 'contact', contact_type: 'custom_contact' }, + null + ]); + expect(actualSubmittedDocs).to.deep.equal(expectedDocs); + })); + + it('submits form when error thrown completing the report', fakeAsync(async () => { + const expectedError = new Error('Validation Error'); + enketoService.completeNewReport.rejects(expectedError); + const currentForm = { _id: 'current-form' }; + const consoleErrorStub = sinon.stub(console, 'error'); + + const component = getComponent(); + component.formXml = FORM_XML; + tick(); + + enketoService.getCurrentForm + .onCall(1) + .returns(currentForm); + enketoService.getCurrentForm + .onCall(2) + .returns(currentForm); + + component.onSubmit.subscribe(() => { + expect.fail('Should not have emitted docs'); + }); + + const submitPromise = component.submitForm(); + expect(component.status.saving).to.be.true; + await submitPromise; + tick(); + expect(component.status.saving).to.be.false; + expect(component.status.error).to.equal('error.report.save'); + expect(consoleErrorStub.callCount).to.equal(1); + expect(consoleErrorStub.args[0]).to.deep.equal(['Error submitting form data: ', expectedError]); + expect(enketoService.getCurrentForm.callCount).to.equal(2); + expect(enketoService.completeNewReport.callCount).to.equal(1); + const expectedFormDoc = { + xml: FORM_XML, + doc: {} + }; + expect(enketoService.completeNewReport.args[0]).to.deep.equal([ + FORM_ID, + currentForm, + expectedFormDoc, + undefined + ]); + expect(enketoService.unload.called).to.be.false; + })); + + it('cancels form and unloads data', fakeAsync(async () => { + const expectedDocs = [ + { _id: 'doc1' }, + { _id: 'doc2' }, + ]; + enketoService.completeNewReport.resolves(expectedDocs); + const currentForm = { _id: 'current-form' }; + const formId = 'test-form-id'; + const formXml = '
custom
'; + const formModel = ''; + const formHtml = '
custom
'; + const user = { + contact_id: 'spanish_user', + language: 'es', + }; + const contactSummary = { hello: 'world' }; + const content = { my: 'content' }; + + const component = getComponent(); + component.formId = formId; + // Trigger change detection to update the bound id attribute + fixture.detectChanges(); + tick(); + component.user = user; + tick(); + component.contactSummary = contactSummary; + tick(); + component.contactType = 'person'; + tick(); + component.content = content; + tick(); + component.formXml = formXml; + tick(); + component.formModel = formModel; + tick(); + component.formHtml = formHtml; + tick(); + component.editing = true; + component.status = { saving: true, error: 'some error' }; + + expect(enketoService.getCurrentForm.callCount).to.equal(8); + expect(enketoService.renderForm.callCount).to.equal(1); + enketoService.getCurrentForm + .onCall(8) + .returns(currentForm); + + let cancelEmitted = false; + component.onCancel.subscribe(() => { + cancelEmitted = true; + }); + + component.cancelForm(); + tick(); + expect(cancelEmitted).to.be.true; + expect(enketoService.getCurrentForm.callCount).to.equal(9); + + expect(enketoService.unload.callCount).to.equal(1); + expect(enketoService.unload.args[0]).to.deep.equal([currentForm]); + expect(component.formId).to.equal(FORM_ID); + expect(component.editing).to.be.false; + expect(component.status).to.deep.equal({ + saving: false, + error: null + }); + // Trigger change detection to update the bound id attribute + fixture.detectChanges(); + + // Render another form to show that internal data has been reset to defaults + component.formXml = FORM_XML; + tick(); + expect(enketoService.renderForm.callCount).to.equal(1); + component.formModel = FORM_MODEL; + tick(); + expect(enketoService.renderForm.callCount).to.equal(1); + component.formHtml = FORM_HTML; + tick(); + expect(enketoService.getCurrentForm.callCount).to.equal(12); + expect(enketoService.renderForm.callCount).to.equal(2); + // New form has been rendered with default values (internal data was reset) + const actualContext = enketoService.renderForm.args[1][0]; + expect(actualContext).to.deep.include({ + selector: `#${FORM_ID}`, + type: 'report', + formDoc: { _id: FORM_ID }, + instanceData: null, + contactSummary: undefined + }); + expect(actualContext.editedListener).to.exist; + expect(actualContext.valuechangeListener).to.exist; + expect(enketoService.renderForm.args[1][1]).to.deep.equal({ + html: $(FORM_HTML), + model: FORM_MODEL, + hasContactSummary: false + }); + expect(enketoService.renderForm.args[1][2]).to.deep.equal(USER); + })); +}); diff --git a/webapp/web-components/cht-form/tests/karma/karma-unit.conf.js b/webapp/web-components/cht-form/tests/karma/karma-unit.conf.js new file mode 100644 index 00000000000..d4c57fc8d4c --- /dev/null +++ b/webapp/web-components/cht-form/tests/karma/karma-unit.conf.js @@ -0,0 +1,16 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html +const path = require('path'); +const baseConfig = require('../../../../tests/karma/karma-unit.base.conf'); + +module.exports = function (config) { + config.set({ + ...baseConfig, + basePath: '../../', + logLevel: config.LOG_INFO, + coverageReporter: { + ...baseConfig.coverageReporter, + dir: path.join(__dirname, 'coverage') + } + }); +}; diff --git a/webapp/web-components/cht-form/tsconfig.app.json b/webapp/web-components/cht-form/tsconfig.app.json new file mode 100644 index 00000000000..7a4bee96419 --- /dev/null +++ b/webapp/web-components/cht-form/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node", "jquery"], + "paths": { + "@mm-actions/*": ["src/ts/actions/*"], + "@mm-providers/*": ["src/ts/providers/*"], + "@mm-reducers/*": ["src/ts/reducers/*"], + "@mm-selectors/*": ["src/ts/selectors/*"], + "@mm-services/*": ["src/ts/services/*"], + "@mm-services/db.service": ["web-components/cht-form/src/stubs/db.service.ts"], + } + }, + "files": [ + "src/main.ts", + "../../src/ts/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/webapp/web-components/cht-form/tsconfig.spec.json b/webapp/web-components/cht-form/tsconfig.spec.json new file mode 100755 index 00000000000..cd46b18b4b6 --- /dev/null +++ b/webapp/web-components/cht-form/tsconfig.spec.json @@ -0,0 +1,28 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "paths": { + "@mm-actions/*": ["src/ts/actions/*"], + "@mm-providers/*": ["src/ts/providers/*"], + "@mm-reducers/*": ["src/ts/reducers/*"], + "@mm-selectors/*": ["src/ts/selectors/*"], + "@mm-services/*": ["src/ts/services/*"], + "@mm-services/db.service": ["web-components/cht-form/src/stubs/db.service.ts"], + }, + "outDir": "../out-tsc/spec", + "types": [ + "mocha", + "sinon", + "node", + "jquery" + ] + }, + "files": [ + "../../tests/karma/test.ts", + "../../src/ts/polyfills.ts" + ], + "include": [ + "tests/karma/**/*.spec.ts" + ] +}