diff --git a/tests/e2e/default/db/initial-replication.wdio-spec.js b/tests/e2e/default/db/initial-replication.wdio-spec.js index 4acb8bfd82..befeaa50c8 100644 --- a/tests/e2e/default/db/initial-replication.wdio-spec.js +++ b/tests/e2e/default/db/initial-replication.wdio-spec.js @@ -155,6 +155,7 @@ describe('initial-replication', () => { setTimeout(() => browser.refresh(), 3000); setTimeout(() => browser.refresh(), 5000); + await browser.pause(5000); await commonPage.waitForPageLoaded(); await validateReplication(); }); diff --git a/tests/e2e/default/transitions/client-side-muting.wdio-spec.js b/tests/e2e/default/transitions/client-side-muting.wdio-spec.js index 18c5304f32..af7d40be76 100644 --- a/tests/e2e/default/transitions/client-side-muting.wdio-spec.js +++ b/tests/e2e/default/transitions/client-side-muting.wdio-spec.js @@ -11,7 +11,7 @@ const personFactory = require('@factories/cht/contacts/person'); /* global window */ -describe.skip('Muting', () => { +describe('Muting', () => { const places = placeFactory.generateHierarchy(); const district = places.get('district_hospital'); const healthCenter = places.get('health_center'); @@ -155,22 +155,6 @@ describe.skip('Muting', () => { expect(doc.muted).to.be.ok; expect(doc.muting_history).to.be.undefined; }; - const setBrowserOffline = async () => { - await browser.throttle({ - offline: true, - downloadThroughput: 0, - uploadThroughput: 0, - latency: 0 - }); - }; - const setBrowserOnline = async () => { - await browser.throttle({ - offline: false, - downloadThroughput: 1000 * 1000, - uploadThroughput: 1000 * 1000, - latency: 0 - }); - }; before(async () => { await utils.saveDocs([district, healthCenter]); @@ -213,17 +197,7 @@ describe.skip('Muting', () => { describe('for an offline user', () => { const updateClientSideMutingSettings = async (settings) => { - await setBrowserOffline(); - await utils.updateSettings(settings); - await setBrowserOnline(); - try { - await commonPage.sync(); - } catch (err) { - // sometimes sync happens by itself, on timeout - console.error('Error when trying to sync', err); - await commonPage.closeReloadModal(true); - await commonPage.sync(); - } + await utils.updateSettings(settings, { sync: true, refresh: true, ignoreReload: true }); }; const unmuteContacts = () => { @@ -264,10 +238,8 @@ describe.skip('Muting', () => { afterEach(async () => { await commonPage.sync(); - await setBrowserOffline(); await utils.revertSettings(true); await unmuteContacts(); - await setBrowserOnline(); }); it( 'should not process muting client-side if not enabled', async () => { diff --git a/tests/e2e/default/transitions/create-user-for-contacts.replace-user.wdio-spec.js b/tests/e2e/default/transitions/create-user-for-contacts.replace-user.wdio-spec.js index 19cc4bb102..9c1ac6c62a 100644 --- a/tests/e2e/default/transitions/create-user-for-contacts.replace-user.wdio-spec.js +++ b/tests/e2e/default/transitions/create-user-for-contacts.replace-user.wdio-spec.js @@ -87,30 +87,6 @@ const loginAsUser = async (user) => { await commonPage.waitForPageLoaded(); }; -/** - * Ongoing replication can be interrupted by the user being edited on the server side. - * A 401 for a replication request will create a feedback doc, which will fail the test. - */ -const assertFeedbackDocs = async () => { - const feedbackDocs = await chtDbUtils.getFeedbackDocs(); - if (!feedbackDocs.length) { - return; - } - - const feedbackDocsToIgnore = [ - 'Http failure response', - 'Server error' - ]; - - const unknownMessages = feedbackDocs - .map(doc => doc.info.message) - .filter(message => !feedbackDocsToIgnore.find(toIgnore => message.includes(toIgnore))); - - if (!unknownMessages.length) { - await chtDbUtils.clearFeedbackDocs(); - } -}; - const loginAsOfflineUser = () => loginAsUser(ORIGINAL_USER); const loginAsOnlineUser = () => loginAsUser(ONLINE_USER); @@ -206,6 +182,30 @@ const waitForConflicts = async (getDoc) => { return utils.delayPromise(waitForConflicts(getDoc), 100); }; +/** + * Ongoing replication can be interrupted by the user being edited on the server side. + * A 401 for a replication request will create a feedback doc, which will fail the test. + */ +const assertFeedbackDocs = async () => { + const feedbackDocs = await chtDbUtils.getFeedbackDocs(); + if (!feedbackDocs.length) { + return; + } + + const feedbackDocsToIgnore = [ + 'Http failure response', + 'Server error' + ]; + + const unknownMessages = feedbackDocs + .map(doc => doc.info.message) + .filter(message => !feedbackDocsToIgnore.find(toIgnore => message.includes(toIgnore))); + + if (!unknownMessages.length) { + await chtDbUtils.clearFeedbackDocs(); + } +}; + describe('Create user for contacts', () => { before(async () => { await utils.saveDocIfNotExists(BASIC_FORM_DOC); diff --git a/tests/page-objects/default/common/common.wdio.page.js b/tests/page-objects/default/common/common.wdio.page.js index 8b784d17ef..afe520677a 100644 --- a/tests/page-objects/default/common/common.wdio.page.js +++ b/tests/page-objects/default/common/common.wdio.page.js @@ -51,6 +51,8 @@ const FEEDBACK = '#feedback'; //About menu const ABOUT_MENU = 'aria/About'; //Configuration App +const ELEMENT_DISPLAY_PAUSE = 500; // 500ms + const configurationAppMenuOption = () => $('aria/App Management'); const errorLog = () => $(`error-log`); const sideBarMenuTitle = () => $('aria/Menu'); @@ -72,7 +74,7 @@ const waitForSnackbarToClose = async () => { const clickFastActionById = async (id) => { // Wait for the Angular Material's animation to complete. - await browser.pause(500); + await browser.pause(ELEMENT_DISPLAY_PAUSE); await (await fastActionListContainer()).waitForDisplayed(); await (await fastActionById(id)).waitForClickable(); await (await fastActionById(id)).click(); @@ -107,7 +109,7 @@ const getFastActionItemsLabels = async () => { await fab.waitForClickable(); await fab.click(); - await browser.pause(500); + await browser.pause(ELEMENT_DISPLAY_PAUSE); await (await fastActionListContainer()).waitForDisplayed(); const items = await fastActionItems(); @@ -345,16 +347,25 @@ const syncAndNotWaitForSuccess = async () => { await (await syncButton()).click(); }; -const syncAndWaitForSuccess = async (timeout = 20000) => { - await openHamburgerMenu(); - await (await syncButton()).waitForClickable(); - await (await syncButton()).click(); - await closeReloadModal(false); - await openHamburgerMenu(); - if (await (await syncInProgress()).isExisting()) { - await (await syncInProgress()).waitForDisplayed({ reverse: true, timeout }); +const syncAndWaitForSuccess = async (timeout = 20000, retry = 10) => { + if (retry < 0) { + throw new Error('Failed to sync after 10 retries'); + } + await closeReloadModal(false, 0); + + try { + await openHamburgerMenu(); + if (!await (await syncInProgress()).isExisting()) { + await (await syncButton()).click(); + await openHamburgerMenu(); + } + + await (await syncInProgress()).waitForDisplayed({ timeout, reverse: true }); + await (await syncSuccess()).waitForDisplayed({ timeout: ELEMENT_DISPLAY_PAUSE }); + } catch (err) { + console.error(err); + await syncAndWaitForSuccess(timeout, retry - 1); } - await (await syncSuccess()).waitForDisplayed({ timeout }); }; const hideModalOverlay = () => { @@ -393,12 +404,11 @@ const syncAndWaitForFailure = async () => { const closeReloadModal = async (shouldUpdate = false, timeout = 5000) => { try { - await browser.waitUntil( async () => await modalPage.modal().isDisplayed(), { timeout: 10000, interval: 500 } ); shouldUpdate ? await modalPage.submit(timeout) : await modalPage.cancel(timeout); shouldUpdate && await waitForAngularLoaded(); return true; } catch (err) { - console.error('Reload modal not showed up'); + timeout && console.error('Reload modal has not showed up'); return false; } }; diff --git a/webapp/src/ts/app.component.ts b/webapp/src/ts/app.component.ts index 6716a0e5b3..6ee6738633 100644 --- a/webapp/src/ts/app.component.ts +++ b/webapp/src/ts/app.component.ts @@ -377,6 +377,8 @@ export class AppComponent implements OnInit, AfterViewInit { } private watchDDocChanges() { + this.updateServiceWorker.update(() => this.ngZone.run(() => this.showUpdateReady())); + this.changesService.subscribe({ key: 'ddoc', filter: (change) => { diff --git a/webapp/src/ts/services/form.service.ts b/webapp/src/ts/services/form.service.ts index 8e4f99deb3..2329c82040 100644 --- a/webapp/src/ts/services/form.service.ts +++ b/webapp/src/ts/services/form.service.ts @@ -8,7 +8,6 @@ import * as medicXpathExtensions from '../../js/enketo/medic-xpath-extensions'; import { DbService } from '@mm-services/db.service'; import { FileReaderService } from '@mm-services/file-reader.service'; import { LineageModelGeneratorService } from '@mm-services/lineage-model-generator.service'; -import { SearchService } from '@mm-services/search.service'; import { SubmitFormBySmsService } from '@mm-services/submit-form-by-sms.service'; import { UserContactService } from '@mm-services/user-contact.service'; import { XmlFormsService } from '@mm-services/xml-forms.service'; @@ -46,7 +45,6 @@ export class FormService { private dbService: DbService, private fileReaderService: FileReaderService, private lineageModelGeneratorService: LineageModelGeneratorService, - private searchService: SearchService, private submitFormBySmsService: SubmitFormBySmsService, private userContactService: UserContactService, private userSettingsService:UserSettingsService, diff --git a/webapp/tests/karma/ts/app.component.spec.ts b/webapp/tests/karma/ts/app.component.spec.ts index 1744063b60..f19119185e 100644 --- a/webapp/tests/karma/ts/app.component.spec.ts +++ b/webapp/tests/karma/ts/app.component.spec.ts @@ -49,6 +49,7 @@ import { UserSettingsService } from '@mm-services/user-settings.service'; import { FormService } from '@mm-services/form.service'; import { OLD_NAV_PERMISSION } from '@mm-components/header/header.component'; import { SidebarMenuComponent } from '@mm-components/sidebar-menu/sidebar-menu.component'; +import { ReloadingComponent } from '@mm-modals/reloading/reloading.component'; describe('AppComponent', () => { let component: AppComponent; @@ -89,6 +90,7 @@ describe('AppComponent', () => { let trainingCardsService; let userSettingsService; let formService; + let updateServiceWorkerService; // End Services let globalActions; @@ -123,7 +125,11 @@ describe('AppComponent', () => { unreadRecordsService = { init: sinon.stub() }; setLanguageService = { set: sinon.stub() }; translateService = { instant: sinon.stub().returnsArg(0) }; - modalService = { show: sinon.stub().resolves() }; + modalService = { + show: sinon.stub().returns({ + afterClosed: sinon.stub().returns(of()) + }) + }; browserDetectorService = { isUsingOutdatedBrowser: sinon.stub().returns(false) }; chtDatasourceService = { isInitialized: sinon.stub() }; analyticsModulesService = { get: sinon.stub() }; @@ -175,6 +181,7 @@ describe('AppComponent', () => { trainingCardsService = { initTrainingCards: sinon.stub() }; userSettingsService = { get: sinon.stub().resolves({ facility_id: ['facility'], contact_id: 'contact' }) }; formService = { setUserContext: sinon.stub() }; + updateServiceWorkerService = { update: sinon.stub() }; consoleErrorStub = sinon.stub(console, 'error'); const mockedSelectors = [ @@ -203,7 +210,7 @@ describe('AppComponent', () => { { provide: AuthService, useValue: authService }, { provide: ResourceIconsService, useValue: resourceIconsService }, { provide: ChangesService, useValue: changesService }, - { provide: UpdateServiceWorkerService, useValue: {} }, + { provide: UpdateServiceWorkerService, useValue: updateServiceWorkerService }, { provide: LocationService, useValue: locationService }, { provide: ModalService, useValue: modalService }, { provide: BrowserDetectorService, useValue: browserDetectorService}, @@ -282,6 +289,19 @@ describe('AppComponent', () => { expect(userSettingsService.get.calledOnce).to.equal(true); expect(globalActions.setUserFacilityIds.calledOnceWith(['facility'])).to.equal(true); expect(globalActions.setUserContactId.calledOnceWith('contact')).to.equal(true); + expect(updateServiceWorkerService.update.callCount).to.equal(1); + }); + + it('should show reload popup when service worker is updated', async () => { + await getComponent(); + await component.setupPromise; + + expect(updateServiceWorkerService.update.callCount).to.equal(1); + const callback = updateServiceWorkerService.update.args[0][0]; + callback(); + expect(modalService.show.calledOnce).to.be.true; + expect(modalService.show.args[0]).to.have.deep.members([ReloadingComponent]); + }); it('should display browser compatibility modal if using outdated chrome browser', async () => {