diff --git a/buildConfig/data/staging/form/form-config_webview_version_get.json b/buildConfig/data/staging/form/form-config_webview_version_get.json
new file mode 100644
index 0000000000..7d27f91257
--- /dev/null
+++ b/buildConfig/data/staging/form/form-config_webview_version_get.json
@@ -0,0 +1,32 @@
+{
+ "id": "api.form.read",
+ "params": {
+ "resmsgid": "cc557f06-5fcf-4feb-919d-627944163857",
+ "msgid": "f0dcdf53-4949-431c-b804-2dbf21886153",
+ "status": "successful"
+ },
+ "responseCode": "OK",
+ "result": {
+ "form": {
+ "type": "config",
+ "subtype": "webview_version",
+ "action": "get",
+ "component": "*",
+ "framework": "*",
+ "data": {
+ "templateName": "webview_version",
+ "action": "get",
+ "fields": [
+ {
+ "version": "54"
+ }
+ ]
+ },
+ "created_on": "2020-01-07T10:51:43.203Z",
+ "last_modified_on": null,
+ "rootOrgId": "*"
+ }
+ },
+ "ts": "2020-01-07T10:52:04.924Z",
+ "ver": "1.0"
+}
\ No newline at end of file
diff --git a/build_config b/build_config
index d17a61c312..5708c0d3ae 100644
--- a/build_config
+++ b/build_config
@@ -10,6 +10,7 @@ cordova-plugin=cordova-plugin-file-transfer
cordova-plugin=cordova-plugin-inappbrowser
cordova-plugin=cordova-plugin-network-information
cordova-plugin=cordova-plugin-statusbar
+cordova-plugin=cordova-plugin-webview-checker
cordova-plugin=https://github.com/adriano-di-giovanni/cordova-plugin-shared-preferences.git
cordova-plugin=https://github.com/katzer/cordova-plugin-local-notifications.git
cordova-plugin=https://github.com/swayangjit/cordova-plugin-fcm-with-dependecy-updated.git#release-2.6.0
@@ -17,7 +18,7 @@ cordova-plugin=cordova-plugin-advanced-http
cordova-plugin=cordova-plugin-android-permissions
cordova-plugin=cordova-plugin-media
cordova-plugin=cordova.plugins.diagnostic
-cordova-plugin=https://github.com/swayangjit/cordova-plugin-buildconfig-reader.git
+cordova-plugin=https://github.com/swayangjit/cordova-plugin-buildconfig-reader.git#release-2.6.5
cordova-plugin=https://github.com/swayangjit/cordova-plugin-android-downloadmanager.git
cordova-plugin=https://github.com/swayangjit/sb-cordova-plugin-db.git
cordova-plugin=cordova-android-support-gradle-release --variable ANDROID_SUPPORT_VERSION=28.0.0
@@ -25,4 +26,4 @@ cordova-plugin=https://github.com/Sunbird-Ed/sb-cordova-plugin-customtabs.git --
sunbird-cordova-plugin=https://github.com/project-sunbird/cordova-plugin-openrap.git
sunbird-cordova-plugin=https://github.com/project-sunbird/cordova-plugin-qr-scanner.git
sunbird-cordova-plugin=https://github.com/project-sunbird/cordova-plugin-sunbirdsplash.git#release-2.6.5
-sunbird-cordova-plugin=https://github.com/project-sunbird/cordova-plugin-file-support.git
+sunbird-cordova-plugin=https://github.com/project-sunbird/cordova-plugin-file-support.git#release-2.6.5
diff --git a/config.xml b/config.xml
index 85ce7ab29e..f8afe9a3ae 100644
--- a/config.xml
+++ b/config.xml
@@ -140,5 +140,6 @@
+
diff --git a/package.json b/package.json
index bc161a820c..70f97aa30d 100644
--- a/package.json
+++ b/package.json
@@ -93,7 +93,7 @@
"rxjs": "^6.5.3",
"sb-cordova-plugin-customtabs": "git+https://github.com/Sunbird-Ed/sb-cordova-plugin-customtabs.git",
"sb-cordova-plugin-db": "git+https://github.com/swayangjit/sb-cordova-plugin-db.git",
- "sunbird-sdk": "git+https://github.com/Sunbird-Ed/sunbird-mobile-sdk.git#v2.7.5",
+ "sunbird-sdk": "git+https://github.com/Sunbird-Ed/sunbird-mobile-sdk.git#v2.7.7",
"tslib": "^1.10.0",
"x2js": "^3.3.0",
"zone.js": "~0.8.29"
@@ -180,7 +180,8 @@
"cordova-android-support-gradle-release": {
"ANDROID_SUPPORT_VERSION": "28.0.0"
},
- "cordova-plugin-openrap": {}
+ "cordova-plugin-openrap": {},
+ "cordova-plugin-webview-checker": {}
},
"platforms": [
"android"
@@ -231,4 +232,4 @@
},
"transformIgnorePatterns": []
}
-}
+}
\ No newline at end of file
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 04cae011d2..2ab9fcd3bb 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -39,4 +39,26 @@
+
+
+
+
+
+
+ {{'UPDATE_REQUIRED' | translate}}
+
+
+
+
{{'WEBVIEW_UPDATE_TEXT' | translate}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 7d91032116..27c816ae18 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -132,9 +132,43 @@ export class AppComponent implements OnInit, AfterViewInit {
this.appRatingService.checkInitialDate();
this.getUtmParameter();
this.checkForCodeUpdates();
+ this.checkAndroidWebViewVersion();
});
}
+ checkAndroidWebViewVersion() {
+ var that = this;
+ plugins['webViewChecker'].getCurrentWebViewPackageInfo()
+ .then(function(packageInfo) {
+ that.formAndFrameworkUtilService.getWebviewConfig().then(function(webviewVersion) {
+ if (parseInt(packageInfo.versionName.split('.')[0], 10) <= webviewVersion) {
+ document.getElementById('update-webview-container').style.display = 'block';
+ this.telemetryGeneratorService.generateImpressionTelemetry(
+ ImpressionType.VIEW, '',
+ PageId.UPDATE_WEBVIEW_POPUP,
+ Environment.HOME);
+ }
+ }).catch(function(err) {
+ if (parseInt(packageInfo.versionName.split('.')[0], 10) <= 54) {
+ document.getElementById('update-webview-container').style.display = 'block';
+ }
+ });
+ })
+ .catch(function(error) { });
+ }
+
+ openPlaystore() {
+ plugins['webViewChecker'].openGooglePlayPage()
+ .then(function() { })
+ .catch(function(error) { });
+
+ this.telemetryGeneratorService.generateInteractTelemetry(
+ InteractType.TOUCH,
+ InteractSubtype.UPDATE_WEBVIEW_CLICKED,
+ Environment.HOME,
+ PageId.UPDATE_WEBVIEW_POPUP);
+ }
+
getSystemConfig() {
const getSystemSettingsRequest: GetSystemSettingsRequest = {
id: SystemSettingsIds.COURSE_FRAMEWORK_ID
@@ -739,10 +773,7 @@ export class AppComponent implements OnInit, AfterViewInit {
&& !(await this.commonUtilService.isIpLocationAvailable())) {
this.deviceRegisterService.getDeviceProfile().toPromise().then(async (response) => {
if (response.userDeclaredLocation) {
- const locationMap = new Map();
- locationMap['state'] = response.userDeclaredLocation.state;
- locationMap['district'] = response.userDeclaredLocation.district;
- await this.preferences.putString(PreferenceKey.DEVICE_LOCATION, JSON.stringify(locationMap)).toPromise();
+ await this.preferences.putString(PreferenceKey.DEVICE_LOCATION, JSON.stringify(response.userDeclaredLocation)).toPromise();
} else if (response.ipLocation) {
const ipLocationMap = new Map();
if (response.ipLocation.state) {
diff --git a/src/app/components/popups/account-recovery-id/account-recovery-id-popup.component.spec.ts b/src/app/components/popups/account-recovery-id/account-recovery-id-popup.component.spec.ts
new file mode 100644
index 0000000000..5f614847a3
--- /dev/null
+++ b/src/app/components/popups/account-recovery-id/account-recovery-id-popup.component.spec.ts
@@ -0,0 +1,211 @@
+import { AccountRecoveryInfoComponent } from './account-recovery-id-popup.component';
+import { CommonUtilService, TelemetryGeneratorService, AppGlobalService } from '../../../../services';
+import { PopoverController, Platform, MenuController } from '@ionic/angular';
+import { ImpressionType, Environment, PageId } from '@app/services/telemetry-constants';
+import { of, throwError } from 'rxjs';
+import { ProfileService } from 'sunbird-sdk';
+
+describe('AccountRecoveryInfoComponent', () => {
+ let accountRecoveryInfoComponent: AccountRecoveryInfoComponent;
+
+ const serverProfileResponse = { response: 'SUCCESS' } as any;
+ const mockProfileService: Partial = {
+ updateServerProfile: jest.fn(() => of(serverProfileResponse)),
+ };
+
+ const mockTelemetryGeneratorService: Partial = {
+ generateImpressionTelemetry: jest.fn(),
+ generateInteractTelemetry: jest.fn()
+ };
+
+ const profile = { uid: '0123456789' } as any;
+ const mockAppGlobalService: Partial = {
+ getCurrentUser: jest.fn(() => profile)
+ };
+
+ const mockPlatform: Partial = {
+ };
+
+ const mockCommonUtilService: Partial = {
+ showToast: jest.fn(() => { })
+ };
+ const dismissFn = jest.fn(() => Promise.resolve());
+ const presentFn = jest.fn(() => Promise.resolve());
+ mockCommonUtilService.getLoader = jest.fn(() => ({
+ present: presentFn,
+ dismiss: dismissFn,
+ }));
+
+ const mockPopoverCtrl: Partial = {
+ dismiss: jest.fn()
+ };
+
+ const mockMenuController: Partial = {
+ enable: jest.fn()
+ };
+
+ beforeAll(() => {
+ accountRecoveryInfoComponent = new AccountRecoveryInfoComponent(
+ mockProfileService as ProfileService,
+ mockTelemetryGeneratorService as TelemetryGeneratorService,
+ mockAppGlobalService as AppGlobalService,
+ mockCommonUtilService as CommonUtilService,
+ mockPopoverCtrl as PopoverController,
+ mockPlatform as Platform,
+ mockMenuController as MenuController,
+ );
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should be create a instance of EditContactDetailsPopupComponent', () => {
+ expect(accountRecoveryInfoComponent).toBeTruthy();
+ });
+
+ it('should generate IMPRESSION telemtry on ngOnInit', () => {
+ // arrange
+ accountRecoveryInfoComponent.recoveryPhone = 'phone';
+ // act
+ accountRecoveryInfoComponent.ngOnInit();
+ // assert
+ expect(accountRecoveryInfoComponent.profile).toEqual({ uid: '0123456789' });
+ expect(mockTelemetryGeneratorService.generateImpressionTelemetry).toHaveBeenCalledWith(
+ ImpressionType.VIEW, '',
+ PageId.RECOVERY_ACCOUNT_ID_POPUP,
+ Environment.USER
+ );
+ expect(mockMenuController.enable).toHaveBeenCalledWith(false);
+ });
+
+ it('should handle the back button in ionViewWillEnter ', () => {
+ // arrange
+ const subscribeWithPriorityData = jest.fn((_, fn) => fn());
+ mockPlatform.backButton = {
+ subscribeWithPriority: subscribeWithPriorityData,
+
+ } as any;
+
+ accountRecoveryInfoComponent.unregisterBackButton = {
+ unsubscribe: jest.fn(),
+ } as any;
+
+ // act
+ accountRecoveryInfoComponent.ionViewWillEnter();
+ // assert
+ expect(mockPopoverCtrl.dismiss).toHaveBeenCalled();
+ });
+
+ it('should enable MenuDrawer and unsubscribe back function', () => {
+ // arrange
+ accountRecoveryInfoComponent.unregisterBackButton = {
+ unsubscribe: jest.fn(),
+
+ } as any;
+ // act
+ accountRecoveryInfoComponent.ionViewWillLeave();
+ // assert
+ expect(mockMenuController.enable).toHaveBeenCalledWith(true);
+ expect(accountRecoveryInfoComponent.unregisterBackButton.unsubscribe).toHaveBeenCalled();
+ });
+
+ it('should update the server profile successfully', (done) => {
+ // arrange
+ accountRecoveryInfoComponent.recoveryPhone = '';
+ mockCommonUtilService.networkInfo = { isNetworkAvailable: true };
+ accountRecoveryInfoComponent.recoveryEmailForm = { value: { email: 'abc@email.com' } } as any;
+ // act
+ accountRecoveryInfoComponent.submitRecoveryId(accountRecoveryInfoComponent.RecoveryType.EMAIL);
+ // assert
+ setTimeout(() => {
+ expect(mockPopoverCtrl.dismiss).toHaveBeenCalledWith({ isEdited: true });
+ expect(mockTelemetryGeneratorService.generateInteractTelemetry).toHaveBeenCalled();
+ done();
+ }, 1);
+ });
+
+ it('should handle the error scenario incase of API failure while updating email', (done) => {
+ // arrange
+ accountRecoveryInfoComponent.recoveryPhone = '';
+ mockCommonUtilService.networkInfo = { isNetworkAvailable: true };
+ accountRecoveryInfoComponent.recoveryEmailForm = { value: { email: 'abc@email.com' } } as any;
+ mockProfileService.updateServerProfile = jest.fn(() => throwError(
+ { response: { body: { params: { err: 'RECOVERY_PARAM_MATCH_EXCEPTION' } } } }));
+ // act
+ accountRecoveryInfoComponent.submitRecoveryId(accountRecoveryInfoComponent.RecoveryType.EMAIL);
+ // assert
+ setTimeout(() => {
+ expect(accountRecoveryInfoComponent.sameEmailErr).toBeTruthy();
+ done();
+ }, 1);
+ });
+
+
+ it('should handle the error scenario incase of API failure while updating phone', (done) => {
+ // arrange
+ accountRecoveryInfoComponent.recoveryPhone = 'phone';
+ mockCommonUtilService.networkInfo = { isNetworkAvailable: true };
+ accountRecoveryInfoComponent.recoveryEmailForm = { value: { phone: '0123456789' } } as any;
+ mockProfileService.updateServerProfile = jest.fn(() => throwError(
+ { response: { body: { params: { err: 'RECOVERY_PARAM_MATCH_EXCEPTION' } } } }));
+ // act
+ accountRecoveryInfoComponent.submitRecoveryId(accountRecoveryInfoComponent.RecoveryType.PHONE);
+ // assert
+ setTimeout(() => {
+ expect(accountRecoveryInfoComponent.samePhoneErr).toBeTruthy();
+ done();
+ }, 1);
+ });
+
+ it('should show TOAST in case of unexpected error scenario incase of API failure while updating phone', (done) => {
+ // arrange
+ accountRecoveryInfoComponent.recoveryPhone = 'phone';
+ mockCommonUtilService.networkInfo = { isNetworkAvailable: true };
+ accountRecoveryInfoComponent.recoveryEmailForm = { value: { phone: '0123456789' } } as any;
+ mockProfileService.updateServerProfile = jest.fn(() => throwError(
+ { response: { body: { params: { err: 'ANY_OTHER_ERROR' } } } }));
+ // act
+ accountRecoveryInfoComponent.submitRecoveryId(accountRecoveryInfoComponent.RecoveryType.PHONE);
+ // assert
+ setTimeout(() => {
+ expect(mockCommonUtilService.showToast).toHaveBeenCalledWith('SOMETHING_WENT_WRONG');
+ done();
+ }, 1);
+ });
+
+ it('should show NETWORK ERROR popup in case of no network connection', () => {
+ // arrange
+ mockCommonUtilService.networkInfo = { isNetworkAvailable: false };
+ // act
+ accountRecoveryInfoComponent.submitRecoveryId(accountRecoveryInfoComponent.RecoveryType.PHONE);
+ // assert
+ expect(mockCommonUtilService.showToast).toHaveBeenCalledWith('INTERNET_CONNECTIVITY_NEEDED');
+ });
+
+ it('should dismiss the popup when cancel is clicked', () => {
+ // arrange
+ // act
+ accountRecoveryInfoComponent.cancel();
+ // assert
+ expect(mockPopoverCtrl.dismiss).toHaveBeenCalledWith({ isEdited: false });
+ });
+
+ it('should reset the error properties', () => {
+ // arrange
+ accountRecoveryInfoComponent.sameEmailErr = true;
+ // act
+ accountRecoveryInfoComponent.removeSameRecoveryIdErr(accountRecoveryInfoComponent.RecoveryType.EMAIL);
+ // assert
+ expect(accountRecoveryInfoComponent.sameEmailErr).toBeFalsy();
+
+ // arrange
+ accountRecoveryInfoComponent.samePhoneErr = true;
+ // act
+ accountRecoveryInfoComponent.removeSameRecoveryIdErr(accountRecoveryInfoComponent.RecoveryType.PHONE);
+ // assert
+ expect(accountRecoveryInfoComponent.samePhoneErr).toBeFalsy();
+ });
+
+
+});
diff --git a/src/app/components/popups/account-recovery-id/account-recovery-id-popup.component.ts b/src/app/components/popups/account-recovery-id/account-recovery-id-popup.component.ts
index 55d96dc591..6b0d3fc8a1 100644
--- a/src/app/components/popups/account-recovery-id/account-recovery-id-popup.component.ts
+++ b/src/app/components/popups/account-recovery-id/account-recovery-id-popup.component.ts
@@ -120,9 +120,7 @@ export class AccountRecoveryInfoComponent implements OnInit {
this.telemetryGeneratorService.generateImpressionTelemetry(
ImpressionType.VIEW, '',
PageId.RECOVERY_ACCOUNT_ID_POPUP,
- Environment.USER, '', '', '',
- undefined,
- undefined
+ Environment.USER
);
}
diff --git a/src/app/components/popups/sb-generic-popover/sb-generic-popover.component.spec.ts b/src/app/components/popups/sb-generic-popover/sb-generic-popover.component.spec.ts
new file mode 100644
index 0000000000..5fe54695e0
--- /dev/null
+++ b/src/app/components/popups/sb-generic-popover/sb-generic-popover.component.spec.ts
@@ -0,0 +1,85 @@
+import { Events, PopoverController, Platform } from '@ionic/angular';
+import { SbGenericPopoverComponent } from '../sb-generic-popover/sb-generic-popover.component';
+
+describe('SbGenericPopoverComponent', () => {
+ let sbGenericPopoverComponent: SbGenericPopoverComponent;
+
+ const mockEventsResponse = { selectedContents: ['do_1234'] };
+ const mockEvents: Partial = {
+ subscribe: jest.fn(() => mockEventsResponse),
+ unsubscribe: jest.fn()
+ };
+
+ const mockPopOverController: Partial = {
+ dismiss: jest.fn()
+ };
+
+ const mockPlatform: Partial = {
+ };
+
+ beforeAll(() => {
+ sbGenericPopoverComponent = new SbGenericPopoverComponent(
+ mockPopOverController as PopoverController,
+ mockPlatform as Platform,
+ mockEvents as Events
+ );
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create a instance of SbGenericPopoverComponent', () => {
+ expect(sbGenericPopoverComponent).toBeTruthy();
+ });
+
+ it('should subscribe to back button and events subscription', () => {
+ // arrange
+ const subscribeWithPriorityData = jest.fn((_, fn) => fn());
+ mockPlatform.backButton = {
+ subscribeWithPriority: subscribeWithPriorityData,
+ } as any;
+
+ const unsubscribeFn = jest.fn();
+ sbGenericPopoverComponent.backButtonFunc = {
+ unsubscribe: unsubscribeFn,
+ } as any;
+
+ // act
+ sbGenericPopoverComponent.ngOnInit();
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalledWith({ isLeftButtonClicked: null });
+ // expect(sbGenericPopoverComponent.selectedContents).toEqual(mockEventsResponse);
+ expect(unsubscribeFn).toHaveBeenCalled();
+ });
+
+ it('should unsubscribe to back button and events on ngOnDestroy', () => {
+ // arrange
+ sbGenericPopoverComponent.backButtonFunc = {
+ unsubscribe: jest.fn(),
+ } as any;
+ // act
+ sbGenericPopoverComponent.ngOnDestroy();
+ // assert
+ expect(mockEvents.unsubscribe).toHaveBeenCalledWith('selectedContents:changed');
+ // expect(sbGenericPopoverComponent.selectedContents).toEqual(mockEventsResponse);
+ expect(sbGenericPopoverComponent.backButtonFunc.unsubscribe).toHaveBeenCalled();
+ });
+
+ it('should dismiss the popup on closePopOver', () => {
+ // arrange
+ // act
+ sbGenericPopoverComponent.closePopover();
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalledWith({ isLeftButtonClicked: null });
+ });
+
+ it('should dismiss the popup on deleteContent', () => {
+ // arrange
+ // act
+ sbGenericPopoverComponent.deleteContent(1);
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalledWith({ isLeftButtonClicked: false });
+ });
+
+});
diff --git a/src/app/components/popups/sb-generic-popover/sb-generic-popover.component.ts b/src/app/components/popups/sb-generic-popover/sb-generic-popover.component.ts
index e711eec829..cbf0c25b76 100644
--- a/src/app/components/popups/sb-generic-popover/sb-generic-popover.component.ts
+++ b/src/app/components/popups/sb-generic-popover/sb-generic-popover.component.ts
@@ -19,7 +19,10 @@ export class SbGenericPopoverComponent implements OnInit, OnDestroy {
@Input() showHeader = true;
backButtonFunc: Subscription;
- constructor(public popoverCtrl: PopoverController, private platform: Platform, private events: Events) { }
+ constructor(
+ public popoverCtrl: PopoverController,
+ private platform: Platform,
+ private events: Events) { }
ngOnInit() {
this.backButtonFunc = this.platform.backButton.subscribeWithPriority(11, () => {
diff --git a/src/app/components/popups/sb-insufficient-storage-popup/sb-insufficient-storage-popup.ts b/src/app/components/popups/sb-insufficient-storage-popup/sb-insufficient-storage-popup.ts
index 25a0a202ae..309a346758 100644
--- a/src/app/components/popups/sb-insufficient-storage-popup/sb-insufficient-storage-popup.ts
+++ b/src/app/components/popups/sb-insufficient-storage-popup/sb-insufficient-storage-popup.ts
@@ -12,12 +12,12 @@ export class SbInsufficientStoragePopupComponent {
sbPopoverMessage = '';
constructor(private navParams: NavParams,
- private popoverCtrl: PopoverController,
- private router: Router) {
+ private popoverCtrl: PopoverController,
+ private router: Router) {
this.initParams();
}
- private initParams() {
+ initParams() {
this.sbPopoverHeading = this.navParams.get('sbPopoverHeading');
this.sbPopoverMessage = this.navParams.get('sbPopoverMessage');
}
@@ -28,7 +28,6 @@ export class SbInsufficientStoragePopupComponent {
navigateToStorageSettings() {
this.popoverCtrl.dismiss();
- // this.app.getActiveNav().push(StorageSettingsPage);
this.router.navigate([RouterLinks.STORAGE_SETTINGS]);
}
diff --git a/src/app/components/popups/sb-insufficient-storage-popup/sb-insufficient-storage.popup.spec.ts b/src/app/components/popups/sb-insufficient-storage-popup/sb-insufficient-storage.popup.spec.ts
new file mode 100644
index 0000000000..e4cbdf3659
--- /dev/null
+++ b/src/app/components/popups/sb-insufficient-storage-popup/sb-insufficient-storage.popup.spec.ts
@@ -0,0 +1,72 @@
+import { SbInsufficientStoragePopupComponent } from './sb-insufficient-storage-popup';
+import { Events, PopoverController, Platform, NavParams } from '@ionic/angular';
+import { Router } from '@angular/router';
+
+describe('SbInsufficientStoragePopupComponent', () => {
+ let sbInsufficientStoragePopupComponent: SbInsufficientStoragePopupComponent;
+
+ const mockNavParams: Partial = {
+ get: jest.fn((arg) => {
+ let value;
+ switch (arg) {
+ case 'sbPopoverHeading':
+ value = 'sample_heading';
+ break;
+ case 'sbPopoverMessage':
+ value = 'sample_message';
+ break;
+ }
+ return value;
+ })
+ };
+
+ const mockPopOverController: Partial = {
+ dismiss: jest.fn()
+ };
+
+ const mockRouter: Partial = {
+ navigate: jest.fn()
+ };
+
+ beforeAll(() => {
+ sbInsufficientStoragePopupComponent = new SbInsufficientStoragePopupComponent(
+ mockNavParams as NavParams,
+ mockPopOverController as PopoverController,
+ mockRouter as Router
+ );
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create a instance of SbInsufficientStoragePopupComponent', () => {
+ expect(sbInsufficientStoragePopupComponent).toBeTruthy();
+ });
+
+ it('should initialize the sbPopoverHeading and sbPopoverMessage', () => {
+ // arrange
+ // act
+ sbInsufficientStoragePopupComponent.initParams();
+ // assert
+ expect(sbInsufficientStoragePopupComponent.sbPopoverMessage).toBeDefined();
+ expect(sbInsufficientStoragePopupComponent.sbPopoverHeading).toBeDefined();
+ });
+
+ it('should navigate to StorageSettings page', () => {
+ // arrange
+ // act
+ sbInsufficientStoragePopupComponent.navigateToStorageSettings();
+ // assert
+ expect(mockRouter.navigate).toHaveBeenCalledWith(['storage-settings']);
+ });
+
+ it('should close popover closePopover', () => {
+ // arrange
+ // act
+ sbInsufficientStoragePopupComponent.closePopover();
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalled();
+ });
+
+});
diff --git a/src/app/components/popups/sb-no-network-popup/sb-no-network.popup.spec.ts b/src/app/components/popups/sb-no-network-popup/sb-no-network.popup.spec.ts
new file mode 100644
index 0000000000..1112b44756
--- /dev/null
+++ b/src/app/components/popups/sb-no-network-popup/sb-no-network.popup.spec.ts
@@ -0,0 +1,34 @@
+import { SbNoNetworkPopupComponent } from './sb-no-network-popup.component';
+import { Events, PopoverController, Platform, NavParams } from '@ionic/angular';
+import { Router } from '@angular/router';
+
+describe('SbNoNetworkPopupComponent', () => {
+ let sbNoNetworkPopupComponent: SbNoNetworkPopupComponent;
+ const mockPopOverController: Partial = {
+ dismiss: jest.fn()
+ };
+
+ beforeAll(() => {
+ sbNoNetworkPopupComponent = new SbNoNetworkPopupComponent(
+ mockPopOverController as PopoverController,
+ );
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create a instance of SbNoNetworkPopupComponent', () => {
+ expect(sbNoNetworkPopupComponent).toBeTruthy();
+ });
+
+ it('should close popover closePopover', () => {
+ // arrange
+ // act
+ sbNoNetworkPopupComponent.closePopover();
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalled();
+ });
+
+});
+
diff --git a/src/app/components/popups/sb-popover/sb-popover.component.spec.ts b/src/app/components/popups/sb-popover/sb-popover.component.spec.ts
new file mode 100644
index 0000000000..477d118338
--- /dev/null
+++ b/src/app/components/popups/sb-popover/sb-popover.component.spec.ts
@@ -0,0 +1,151 @@
+import { SbPopoverComponent } from './sb-popover.component';
+import { PopoverController, Platform, NavParams } from '@ionic/angular';
+import { NgZone } from '@angular/core';
+import { of } from 'rxjs';
+
+describe('SbPopoverComponent', () => {
+ let sbPopoverComponent: SbPopoverComponent;
+ const mockNavParams: Partial = {
+ get: jest.fn((arg) => {
+ let value;
+ switch (arg) {
+ case 'actionsButtons':
+ value = [
+ {
+ btntext: 'OKAY',
+ btnClass: 'popover-color',
+ btnDisabled$: of([])
+ }
+ ];
+ break;
+ case 'sbPopoverDynamicContent':
+ value = of([]);
+ break;
+ case 'sbPopoverDynamicMainTitle':
+ value = of([]);
+ break;
+ case 'btnDisabled':
+ value = of([]);
+ break;
+ case 'isChild':
+ value = true;
+ break;
+ case 'handler':
+ value = () => {
+ };
+ break;
+ }
+ return value;
+ })
+ };
+
+ const mockPlatform: Partial = {
+ };
+
+ const mockNgZone: Partial = {
+ run: jest.fn((fn) => fn())
+ };
+
+ const mockPopOverController: Partial = {
+ dismiss: jest.fn()
+ };
+
+ beforeAll(() => {
+ sbPopoverComponent = new SbPopoverComponent(
+ mockNavParams as NavParams,
+ mockPlatform as Platform,
+ mockNgZone as NgZone,
+ mockPopOverController as PopoverController
+ );
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create a instance of SbInsufficientStoragePopupComponent', () => {
+ expect(sbPopoverComponent).toBeTruthy();
+ });
+
+ it('should poulate all properties provided in navparams', () => {
+ expect(sbPopoverComponent.actionsButtons).toBeDefined();
+ expect(sbPopoverComponent.sbPopoverDynamicContent$).toBeDefined();
+ expect(sbPopoverComponent.sbPopoverDynamicMainTitle$).toBeDefined();
+ });
+
+ describe('closePopOver()', () => {
+ it('should close popover', () => {
+ // arrange
+ // act
+ sbPopoverComponent.closePopover(true);
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalledWith({ closeDeletePopOver: true });
+ });
+ });
+
+ describe('deleteContent()', () => {
+ it('should close popover', () => {
+ // arrange
+ // act
+ sbPopoverComponent.deleteContent(true);
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalledWith({ canDelete: true });
+ });
+
+ it('should invoke handler method passed by navparams', () => {
+ // arrange
+ // act
+ sbPopoverComponent.deleteContent(true, 'clickedButtonText');
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalledWith({ canDelete: true });
+ });
+ });
+
+ describe('ionViewWillEnter()', () => {
+ it('should dismiss the popup when backButton is clicked', () => {
+ // arrange
+ mockPlatform.backButton = {
+ subscribeWithPriority: jest.fn((_, fn) => fn()),
+ } as any;
+
+ const unsubscribeFn = jest.fn();
+ sbPopoverComponent.backButtonFunc = {
+ unsubscribe: unsubscribeFn
+ } as any;
+ // act
+ sbPopoverComponent.ionViewWillEnter();
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalled();
+ expect(unsubscribeFn).toHaveBeenCalled();
+ });
+ });
+
+ describe('ngOnDestroy()', () => {
+ it('should unsubscribe all subscription', () => {
+ // arrange
+ const unsubscribeFn = jest.fn();
+
+ sbPopoverComponent.sbPopoverDynamicMainTitleSubscription = {
+ unsubscribe: unsubscribeFn
+ } as any;
+
+ sbPopoverComponent.sbPopoverDynamicButtonDisabledSubscription = {
+ unsubscribe: unsubscribeFn
+ } as any;
+
+ sbPopoverComponent.sbPopoverDynamicContentSubscription = {
+ unsubscribe: unsubscribeFn
+ } as any;
+
+ sbPopoverComponent.backButtonFunc = {
+ unsubscribe: unsubscribeFn
+ } as any;
+
+ // act
+ sbPopoverComponent.ngOnDestroy();
+ // assert
+ expect(unsubscribeFn).toHaveBeenCalledTimes(4);
+ });
+ });
+
+});
diff --git a/src/app/components/popups/sb-popover/sb-popover.component.ts b/src/app/components/popups/sb-popover/sb-popover.component.ts
index 50356ffde2..9e85142249 100644
--- a/src/app/components/popups/sb-popover/sb-popover.component.ts
+++ b/src/app/components/popups/sb-popover/sb-popover.component.ts
@@ -30,12 +30,12 @@ export class SbPopoverComponent implements OnDestroy {
public objRollup: Rollup;
public commonUtilService: CommonUtilService;
- private corRelationList: Array;
- private sbPopoverDynamicMainTitle$?: Observable;
- private sbPopoverDynamicMainTitleSubscription?: Subscription;
- private sbPopoverDynamicContent$?: Observable;
- private sbPopoverDynamicContentSubscription?: Subscription;
- private sbPopoverDynamicButtonDisabledSubscription?: Subscription;
+ public corRelationList: Array;
+ public sbPopoverDynamicMainTitle$?: Observable;
+ public sbPopoverDynamicMainTitleSubscription?: Subscription;
+ public sbPopoverDynamicContent$?: Observable;
+ public sbPopoverDynamicContentSubscription?: Subscription;
+ public sbPopoverDynamicButtonDisabledSubscription?: Subscription;
constructor(
public navParams: NavParams,
private platform: Platform,
diff --git a/src/app/components/popups/teacher-id-verification-popup/teacher-id-verification-popup.component.ts b/src/app/components/popups/teacher-id-verification-popup/teacher-id-verification-popup.component.ts
index 2dc7137cb7..3333698d6a 100644
--- a/src/app/components/popups/teacher-id-verification-popup/teacher-id-verification-popup.component.ts
+++ b/src/app/components/popups/teacher-id-verification-popup/teacher-id-verification-popup.component.ts
@@ -7,14 +7,12 @@ import { TelemetryGeneratorService } from '@app/services/telemetry-generator.ser
import { featureIdMap } from '@app/feature-id-map';
import {
Environment,
- ImpressionSubtype,
ImpressionType,
InteractSubtype,
InteractType,
PageId,
ID
} from '@app/services/telemetry-constants';
-import { map } from 'rxjs/operators';
export enum TeacherIdPopupFlags {
STATE_CONFIRMATION = 'stateConfirmation',
@@ -46,11 +44,11 @@ export class TeacherIdVerificationComponent implements OnInit {
tenantMessages: any;
constructor(
+ @Inject('PROFILE_SERVICE') private profileService: ProfileService,
private popOverCtrl: PopoverController,
private navParams: NavParams,
private telemetryGeneratorService: TelemetryGeneratorService,
- private commonUtilService: CommonUtilService,
- @Inject('PROFILE_SERVICE') private profileService: ProfileService) {
+ private commonUtilService: CommonUtilService) {
if (this.navParams.data) {
this.userFeed = this.navParams.data.userFeed;
this.stateList = this.userFeed.data.prospectChannels;
@@ -86,7 +84,6 @@ export class TeacherIdVerificationComponent implements OnInit {
};
this.profileService.userMigrate(req).toPromise()
.then(async (response) => {
- console.log('UserMigrateResponse', response);
this.count = 0;
if ((response.responseCode).toLowerCase() === TeacherIdPopupFlags.OK) {
this.teacherIdFlag = TeacherIdPopupFlags.VERIFIED_STATE_ID;
@@ -99,17 +96,15 @@ export class TeacherIdVerificationComponent implements OnInit {
}
}
- private initializeFormFields() {
+ initializeFormFields() {
this.teacherIdFlag = TeacherIdPopupFlags.STATE_ID_INPUT;
this.teacherIdForm = new FormGroup({
teacherId: new FormControl('', Validators.requiredTrue)
});
- console.log('teacherId initialise', this.teacherIdForm.value.teacherId);
}
submitTeacherId() {
this.count++;
- // this.disableButton = true;
this.telemetryGeneratorService.generateInteractTelemetry(
InteractType.TOUCH,
'',
@@ -130,14 +125,12 @@ export class TeacherIdVerificationComponent implements OnInit {
feedId: this.userFeed.id
};
this.externalUserVerfication(req);
- console.log('UserMigrateRequest', req, this.count);
}
}
externalUserVerfication(req) {
this.profileService.userMigrate(req).toPromise()
.then(async (response) => {
- console.log('UserMigrateResponse', response);
this.count = 0;
if ((response.responseCode).toLowerCase() === TeacherIdPopupFlags.INVALIDEXTERNALID) {
this.showTeacherIdIncorrectErr = true;
@@ -158,7 +151,6 @@ export class TeacherIdVerificationComponent implements OnInit {
}
})
.catch((error) => {
- console.log('error', error);
if (error instanceof HttpClientError) {
if (error.response.responseCode === 400) {
this.generateTelemetryForFailedVerification();
@@ -168,13 +160,13 @@ export class TeacherIdVerificationComponent implements OnInit {
this.teacherIdFlag = TeacherIdPopupFlags.FAILED_STATE_ID;
} else if (error.response.responseCode === 429) {
this.closePopup();
- this.commonUtilService.showToast(this.commonUtilService.translateMessage('USER_IS_NOT_VERIFIED'));
+ this.commonUtilService.showToast('USER_IS_NOT_VERIFIED');
} else if (error.response.responseCode === 401) {
this.closePopup();
- this.commonUtilService.showToast(this.commonUtilService.translateMessage('USER_IS_NOT_VERIFIED'));
+ this.commonUtilService.showToast('USER_IS_NOT_VERIFIED');
} else {
this.closePopup();
- this.commonUtilService.showToast(this.commonUtilService.translateMessage('USER_IS_NOT_VERIFIED'));
+ this.commonUtilService.showToast('USER_IS_NOT_VERIFIED');
}
}
});
@@ -194,7 +186,7 @@ export class TeacherIdVerificationComponent implements OnInit {
undefined,
undefined,
undefined,
- featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION,
+ featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION
);
}
generateTelemetryForFailedVerification() {
diff --git a/src/app/components/popups/teacher-id-verification-popup/teacher-id-verification.component.spec.ts b/src/app/components/popups/teacher-id-verification-popup/teacher-id-verification.component.spec.ts
new file mode 100644
index 0000000000..63a8dd9872
--- /dev/null
+++ b/src/app/components/popups/teacher-id-verification-popup/teacher-id-verification.component.spec.ts
@@ -0,0 +1,362 @@
+import { TeacherIdVerificationComponent } from './teacher-id-verification-popup.component';
+import { PopoverController, NavParams } from '@ionic/angular';
+import { TelemetryGeneratorService, CommonUtilService } from '../../../../services';
+import { of, throwError } from 'rxjs';
+import { ProfileService, HttpClientError, Response } from 'sunbird-sdk';
+import { featureIdMap } from '@app/feature-id-map';
+import {
+ Environment,
+ ImpressionType,
+ InteractSubtype,
+ InteractType,
+ PageId,
+ ID
+} from '@app/services/telemetry-constants';
+
+describe('TeacherIdVerificationComponent', () => {
+ let teacherIdVerificationComponent: TeacherIdVerificationComponent;
+
+ const userMigrationResponse = { responseCode: 'ok' } as any;
+ const mockProfileService: Partial = {
+ userMigrate: jest.fn(() => of(userMigrationResponse))
+ };
+ const mockPopOverController: Partial = {
+ dismiss: jest.fn()
+ };
+ const mockNavParams: Partial = {
+ data: {
+ userFeed: { id: '0123456789', userId: '0123456789', data: { prospectChannels: ['tn'] } },
+ tenantMessages: {}
+ }
+ };
+
+ const mockTelemetryGeneratorService: Partial = {
+ generateImpressionTelemetry: jest.fn(),
+ generateInteractTelemetry: jest.fn()
+ };
+
+ const mockCommonUtilService: Partial = {
+ showToast: jest.fn()
+ };
+
+
+ beforeAll(() => {
+ teacherIdVerificationComponent = new TeacherIdVerificationComponent(
+ mockProfileService as ProfileService,
+ mockPopOverController as PopoverController,
+ mockNavParams as NavParams,
+ mockTelemetryGeneratorService as TelemetryGeneratorService,
+ mockCommonUtilService as CommonUtilService,
+ );
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create a instance of TeacherIdVerificationComponent', () => {
+ expect(teacherIdVerificationComponent).toBeTruthy();
+ });
+
+ it('should generate IMPRESSION telemetry on ngOnit', () => {
+ // arrange
+ // act
+ teacherIdVerificationComponent.ngOnInit();
+ // assert
+ expect(mockTelemetryGeneratorService.generateImpressionTelemetry).toHaveBeenCalledWith(ImpressionType.VIEW,
+ '',
+ PageId.EXTERNAL_USER_VERIFICATION_POPUP,
+ Environment.HOME, '', '', '', undefined, featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION);
+ });
+
+ it('should close popover and generate INTERACT telemetry', () => {
+ // arrange
+ // act
+ teacherIdVerificationComponent.cancelPopup('SAMPLE_MESSAGE');
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalled();
+ const reason = new Map();
+ reason['popup_close'] = 'SAMPLE_MESSAGE';
+ expect(mockTelemetryGeneratorService.generateInteractTelemetry).toHaveBeenCalledWith( InteractType.TOUCH,
+ InteractSubtype.POPUP_DISMISSED,
+ Environment.HOME,
+ PageId.EXTERNAL_USER_VERIFICATION_POPUP,
+ undefined,
+ reason,
+ undefined,
+ featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION);
+ });
+
+ it('should close popover and generate INTERACT telemetry', () => {
+ // arrange
+ // act
+ teacherIdVerificationComponent.selectState('SAMPLE_STATE');
+ // assert
+ expect(teacherIdVerificationComponent.stateName).toEqual('SAMPLE_STATE');
+ expect(teacherIdVerificationComponent.showStates).toBeFalsy();
+ });
+
+ it('should close the popOver on closePopup', () => {
+ // arrange
+ // act
+ teacherIdVerificationComponent.closePopup();
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalled();
+ expect(mockTelemetryGeneratorService.generateInteractTelemetry).toHaveBeenCalledWith(InteractType.TOUCH,
+ InteractSubtype.POPUP_DISMISSED,
+ Environment.HOME,
+ PageId.EXTERNAL_USER_VERIFICATION_POPUP,
+ undefined,
+ undefined,
+ undefined,
+ featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION);
+ });
+
+ describe('teacherConfirmation()', () => {
+ it('should generate INTERACT telemetry if user confirms verification', () => {
+ // arrange
+ jest.spyOn(teacherIdVerificationComponent, 'initializeFormFields');
+ // act
+ teacherIdVerificationComponent.teacherConfirmation(true);
+ // assert
+ expect(teacherIdVerificationComponent.initializeFormFields).toHaveBeenCalled();
+ expect(mockTelemetryGeneratorService.generateInteractTelemetry).toHaveBeenCalledWith(InteractType.TOUCH,
+ '',
+ Environment.HOME,
+ PageId.EXTERNAL_USER_VERIFICATION_POPUP,
+ undefined,
+ undefined,
+ undefined,
+ featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION,
+ ID.USER_VERIFICATION_CONFIRMED);
+ });
+
+ it('should generate INTERACT telemetry if user rejects verification', () => {
+ // arrange
+ // act
+ teacherIdVerificationComponent.teacherConfirmation(false);
+ // assert
+ expect(mockTelemetryGeneratorService.generateInteractTelemetry).toHaveBeenCalledWith(InteractType.TOUCH,
+ '',
+ Environment.HOME,
+ PageId.EXTERNAL_USER_VERIFICATION_POPUP,
+ undefined,
+ undefined,
+ undefined,
+ featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION,
+ ID.USER_VERIFICATION_REJECTED);
+ });
+
+ it('should invoke userMigrate API with reject status if user rejects verification', (done) => {
+ // arrange
+ jest.spyOn(teacherIdVerificationComponent, 'closePopup');
+ teacherIdVerificationComponent.stateName = '';
+ // act
+ teacherIdVerificationComponent.teacherConfirmation(false);
+ // assert
+ expect(mockProfileService.userMigrate).toHaveBeenCalledWith({
+ userId: '0123456789',
+ action: 'reject',
+ channel: 'tn',
+ feedId: '0123456789'
+ });
+
+ setTimeout(() => {
+ expect(teacherIdVerificationComponent.closePopup).toHaveBeenCalled();
+ expect(teacherIdVerificationComponent.count).toEqual(0);
+ expect(teacherIdVerificationComponent.teacherIdFlag).toEqual('verifiedStateId');
+ done();
+ }, 0);
+ });
+
+ it('should close the popup in vase of API failure', (done) => {
+ // arrange
+ jest.spyOn(teacherIdVerificationComponent, 'closePopup');
+ mockProfileService.userMigrate = jest.fn(() => throwError({ error: 'API_ERROR' }));
+ // act
+ teacherIdVerificationComponent.teacherConfirmation(false);
+ // assert
+ setTimeout(() => {
+ expect(teacherIdVerificationComponent.closePopup).toHaveBeenCalled();
+ done();
+ }, 0);
+ });
+ });
+
+ describe('submitTeacherId()', () => {
+ it('should generate INTERACT telemetry if user confirms verification', () => {
+ // arrange
+ teacherIdVerificationComponent.teacherIdForm = { value: { teacherId: 'SAMPLE_TEACHERID' } } as any;
+ teacherIdVerificationComponent.stateName = '';
+ jest.spyOn(teacherIdVerificationComponent, 'externalUserVerfication');
+ // act
+ teacherIdVerificationComponent.submitTeacherId();
+ // assert
+ expect(teacherIdVerificationComponent.count).toEqual(1);
+ expect(teacherIdVerificationComponent.externalUserVerfication).toHaveBeenCalledWith({
+ userId: '0123456789',
+ userExtId: 'SAMPLE_TEACHERID',
+ channel: 'tn',
+ action: 'accept',
+ feedId: '0123456789'
+ });
+ expect(mockTelemetryGeneratorService.generateInteractTelemetry).toHaveBeenCalledWith(InteractType.TOUCH,
+ '',
+ Environment.HOME,
+ PageId.EXTERNAL_USER_VERIFICATION_POPUP,
+ undefined,
+ undefined,
+ undefined,
+ featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION,
+ ID.USER_VERIFICATION_SUBMITED);
+ });
+ });
+
+ describe('externalUserVerfication()', () => {
+ it('should generate INTERACT telemetry for successsfull validation', (done) => {
+ // arrange
+ mockProfileService.userMigrate = jest.fn(() => of(userMigrationResponse));
+ // act
+ teacherIdVerificationComponent.externalUserVerfication({
+ userId: '0123456789',
+ userExtId: 'SAMPLE_TEACHERID',
+ channel: 'tn',
+ action: 'accept',
+ feedId: '0123456789'
+ });
+ // assert
+ setTimeout(() => {
+ expect(teacherIdVerificationComponent.teacherIdFlag).toEqual('verifiedStateId');
+ expect(mockTelemetryGeneratorService.generateInteractTelemetry).toHaveBeenCalledWith(InteractType.OTHER,
+ '',
+ Environment.USER,
+ PageId.EXTERNAL_USER_VERIFICATION_POPUP,
+ undefined,
+ undefined,
+ undefined,
+ featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION,
+ ID.USER_VERIFICATION_SUCCESS);
+ done();
+ }, 0);
+ });
+
+ it('should set the error flag in case of invalid id', (done) => {
+ // arrange
+ const userMigrationInvalidIdResponse = { responseCode: 'invaliduserexternalid' } as any;
+ mockProfileService.userMigrate = jest.fn(() => of(userMigrationInvalidIdResponse));
+ // act
+ teacherIdVerificationComponent.externalUserVerfication({
+ userId: '0123456789',
+ userExtId: 'SAMPLE_TEACHERID',
+ channel: 'tn',
+ action: 'accept',
+ feedId: '0123456789'
+ });
+ // assert
+ setTimeout(() => {
+ expect(teacherIdVerificationComponent.teacherModelId).toEqual('');
+ expect(teacherIdVerificationComponent.showTeacherIdIncorrectErr).toBeTruthy();
+ done();
+ }, 0);
+ });
+
+ it('should generate failed verification INTERACT telemetry and set teacherIdFlag for 400 error code', (done) => {
+ // arrange
+ const sunbirdResponse = new Response();
+ sunbirdResponse.responseCode = 400;
+ sunbirdResponse.body = {};
+ mockProfileService.userMigrate = jest.fn(() => throwError(new HttpClientError('', sunbirdResponse)));
+ // act
+ teacherIdVerificationComponent.externalUserVerfication({});
+ // assert
+ setTimeout(() => {
+ expect(teacherIdVerificationComponent.teacherIdFlag).toEqual('failedStateId');
+ expect(mockTelemetryGeneratorService.generateInteractTelemetry).toHaveBeenCalledWith(InteractType.OTHER,
+ '',
+ Environment.HOME,
+ PageId.EXTERNAL_USER_VERIFICATION_POPUP,
+ undefined,
+ undefined,
+ undefined,
+ featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION,
+ ID.USER_VERIFICATION_FAILED);
+ done();
+ }, 0);
+ });
+
+ it('should generate failed verification INTERACT telemetry and set teacherIdFlag for 404 error code', (done) => {
+ // arrange
+ const sunbirdResponse = new Response();
+ sunbirdResponse.responseCode = 404;
+ sunbirdResponse.body = {};
+ mockProfileService.userMigrate = jest.fn(() => throwError(new HttpClientError('', sunbirdResponse)));
+ // act
+ teacherIdVerificationComponent.externalUserVerfication({});
+ // assert
+ setTimeout(() => {
+ expect(teacherIdVerificationComponent.teacherIdFlag).toEqual('failedStateId');
+ expect(mockTelemetryGeneratorService.generateInteractTelemetry).toHaveBeenCalledWith(InteractType.OTHER,
+ '',
+ Environment.HOME,
+ PageId.EXTERNAL_USER_VERIFICATION_POPUP,
+ undefined,
+ undefined,
+ undefined,
+ featureIdMap.userVerification.EXTERNAL_USER_VERIFICATION,
+ ID.USER_VERIFICATION_FAILED);
+ done();
+ }, 0);
+ });
+
+ it('should cloe popup and show USER_IS_NOT_VERIFIED toast for 429 error code', (done) => {
+ // arrange
+ const sunbirdResponse = new Response();
+ sunbirdResponse.responseCode = 429;
+ sunbirdResponse.body = {};
+ mockProfileService.userMigrate = jest.fn(() => throwError(new HttpClientError('', sunbirdResponse)));
+ jest.spyOn(teacherIdVerificationComponent, 'closePopup');
+ // act
+ teacherIdVerificationComponent.externalUserVerfication({});
+ // assert
+ setTimeout(() => {
+ expect(mockCommonUtilService.showToast).toHaveBeenCalledWith('USER_IS_NOT_VERIFIED');
+ expect(teacherIdVerificationComponent.closePopup).toHaveBeenCalled();
+ done();
+ }, 0);
+ });
+
+ it('should cloe popup and show USER_IS_NOT_VERIFIED toast for 401 error code', (done) => {
+ // arrange
+ const sunbirdResponse = new Response();
+ sunbirdResponse.responseCode = 401;
+ sunbirdResponse.body = {};
+ mockProfileService.userMigrate = jest.fn(() => throwError(new HttpClientError('', sunbirdResponse)));
+ jest.spyOn(teacherIdVerificationComponent, 'closePopup');
+ // act
+ teacherIdVerificationComponent.externalUserVerfication({});
+ // assert
+ setTimeout(() => {
+ expect(mockCommonUtilService.showToast).toHaveBeenCalledWith('USER_IS_NOT_VERIFIED');
+ expect(teacherIdVerificationComponent.closePopup).toHaveBeenCalled();
+ done();
+ }, 0);
+ });
+
+ it('should cloe popup and show USER_IS_NOT_VERIFIED toast for othe error code', (done) => {
+ // arrange
+ const sunbirdResponse = new Response();
+ sunbirdResponse.responseCode = 500;
+ sunbirdResponse.body = {};
+ mockProfileService.userMigrate = jest.fn(() => throwError(new HttpClientError('', sunbirdResponse)));
+ jest.spyOn(teacherIdVerificationComponent, 'closePopup');
+ // act
+ teacherIdVerificationComponent.externalUserVerfication({});
+ // assert
+ setTimeout(() => {
+ expect(mockCommonUtilService.showToast).toHaveBeenCalledWith('USER_IS_NOT_VERIFIED');
+ expect(teacherIdVerificationComponent.closePopup).toHaveBeenCalled();
+ done();
+ }, 0);
+ });
+ });
+});
diff --git a/src/app/components/popups/upgrade-popover/upgrade-popover.component.spec.ts b/src/app/components/popups/upgrade-popover/upgrade-popover.component.spec.ts
new file mode 100644
index 0000000000..0fe6d0194a
--- /dev/null
+++ b/src/app/components/popups/upgrade-popover/upgrade-popover.component.spec.ts
@@ -0,0 +1,75 @@
+import { UpgradePopoverComponent } from './upgrade-popover.component';
+import { PopoverController, Platform, NavParams } from '@ionic/angular';
+import { UtilityService } from '../../../../services/utility-service';
+import { NgZone } from '@angular/core';
+import { of } from 'rxjs';
+
+describe('UpgradePopoverComponent', () => {
+ let upgradePopoverComponent: UpgradePopoverComponent;
+ const mockUtilityService: Partial = {
+ openPlayStore: jest.fn()
+ };
+
+ const mockPopOverController: Partial = {
+ dismiss: jest.fn()
+ };
+
+ const mockNavParams: Partial = {
+ get: jest.fn((arg) => {
+ let value;
+ switch (arg) {
+ case 'type':
+ value = {
+ type: 'force',
+ optional: 'forceful',
+ title: 'We recommend that you upgrade to the latest version of Sunbird.',
+ desc: '',
+ actionButtons: [
+ {
+ action: 'yes',
+ label: 'Update Now',
+ link: 'https://play.google.com/store/apps/details?id=org.sunbird.app&hl=en'
+ }
+ ]
+ };
+ break;
+ }
+ return value;
+ })
+ };
+
+ beforeAll(() => {
+ upgradePopoverComponent = new UpgradePopoverComponent(
+ mockUtilityService as UtilityService,
+ mockPopOverController as PopoverController,
+ mockNavParams as NavParams
+ );
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create a instance of UpgradePopoverComponent', () => {
+ expect(upgradePopoverComponent).toBeTruthy();
+ });
+
+ it('should close popover', () => {
+ // arrange
+ // act
+ upgradePopoverComponent.cancel();
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalled();
+ });
+
+ it('should invoke openPlayStore', () => {
+ // arrange
+ jest.spyOn(upgradePopoverComponent, 'cancel');
+ // act
+ upgradePopoverComponent.upgrade('https://play.google.com/store/apps/details?id=org.sunbird.app');
+ // assert
+ expect(mockUtilityService.openPlayStore).toHaveBeenCalledWith('org.sunbird.app');
+ expect(upgradePopoverComponent.cancel).toHaveBeenCalled();
+ });
+
+});
diff --git a/src/app/components/popups/upgrade-popover/upgrade-popover.component.ts b/src/app/components/popups/upgrade-popover/upgrade-popover.component.ts
index d3c0d5ed6c..c0a10a00f9 100644
--- a/src/app/components/popups/upgrade-popover/upgrade-popover.component.ts
+++ b/src/app/components/popups/upgrade-popover/upgrade-popover.component.ts
@@ -40,5 +40,4 @@ export class UpgradePopoverComponent {
this.utilityService.openPlayStore(appId);
this.cancel();
}
-
-}
\ No newline at end of file
+}
diff --git a/src/app/components/popups/view-credits/view-credits.component.html b/src/app/components/popups/view-credits/view-credits.component.html
index 826703b00a..7128fe5744 100644
--- a/src/app/components/popups/view-credits/view-credits.component.html
+++ b/src/app/components/popups/view-credits/view-credits.component.html
@@ -16,7 +16,7 @@
{{ 'CREATORS' | translate }}
- {{mergeProperties('creators', 'creator')}}
+ {{mergeProperties(['creators', 'creator'])}}
@@ -24,7 +24,7 @@
{{ 'CONTRIBUTORS' | translate }}
- {{mergeProperties('contributors', 'owner')}}
+ {{mergeProperties(['contributors', 'owner'])}}
diff --git a/src/app/components/popups/view-credits/view-credits.component.spec.ts b/src/app/components/popups/view-credits/view-credits.component.spec.ts
new file mode 100644
index 0000000000..3545292e8c
--- /dev/null
+++ b/src/app/components/popups/view-credits/view-credits.component.spec.ts
@@ -0,0 +1,114 @@
+import { ViewCreditsComponent } from './view-credits.component';
+import { PopoverController, Platform, NavParams } from '@ionic/angular';
+import { TelemetryGeneratorService } from '../../../../services/telemetry-generator.service';
+import { PageId, InteractSubtype, Environment, InteractType } from '@app/services/telemetry-constants';
+
+describe('UpgradePopoverComponent', () => {
+ let viewCreditsComponent: ViewCreditsComponent;
+
+ const mockNavParams: Partial
= {
+ get: jest.fn((arg) => {
+ let value;
+ switch (arg) {
+ case 'content':
+ value = {
+ identifier: 'do_123',
+ pkgVersion: '1',
+ contentType: 'Resource',
+ creator: 'SAMPLE_CREATOR',
+ creators: 'SAMPLE_CREATORS'
+ };
+ break;
+ case 'pageId':
+ value = PageId.CONTENT_DETAIL;
+ break;
+ case 'rollUp':
+ value = { l1: 'do_1', l2: 'do_2'};
+ break;
+ }
+ return value;
+ })
+ };
+
+ const mockPlatform: Partial = {
+ };
+
+ const subscribeWithPriorityData = jest.fn((_, fn) => fn());
+ mockPlatform.backButton = {
+ subscribeWithPriority: subscribeWithPriorityData
+ } as any;
+
+ const mockPopOverController: Partial = {
+ dismiss: jest.fn()
+ };
+
+ const mockTelemetryGeneratorService: Partial = {
+ generateInteractTelemetry: jest.fn()
+ };
+
+
+ beforeAll(() => {
+ viewCreditsComponent = new ViewCreditsComponent(
+ mockNavParams as NavParams,
+ mockPlatform as Platform,
+ mockPopOverController as PopoverController,
+ mockTelemetryGeneratorService as TelemetryGeneratorService
+ );
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create a instance of ViewCreditsComponent', () => {
+ expect(viewCreditsComponent).toBeTruthy();
+ });
+
+ it('should dismiss the popup and unsubscribe back Function', () => {
+ // arrange
+ viewCreditsComponent.backButtonFunc = {
+ unsubscribe: jest.fn()
+ };
+ // act
+ viewCreditsComponent.ngOnInit();
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalled();
+ // expect(viewCreditsComponent.backButtonFunc.unsubscribe).toHaveBeenCalled();
+
+ });
+
+ it('should generate INTERACT telemetry on ionViewDidload', () => {
+ // arrange
+ // act
+ viewCreditsComponent.ionViewDidLoad();
+ // assert
+ expect(mockTelemetryGeneratorService.generateInteractTelemetry).toHaveBeenCalledWith(InteractType.TOUCH,
+ InteractSubtype.CREDITS_CLICKED,
+ Environment.HOME,
+ PageId.CONTENT_DETAIL,
+ { id: 'do_123', type: 'Resource', version: '1'},
+ undefined,
+ {l1: 'do_1', l2: 'do_2'},
+ undefined);
+ });
+
+ it('should dissmiss popup on cancel', () => {
+ // arrange
+ viewCreditsComponent.backButtonFunc = {
+ unsubscribe: jest.fn()
+ };
+ // act
+ viewCreditsComponent.cancel();
+ // assert
+ expect(mockPopOverController.dismiss).toHaveBeenCalled();
+ expect(viewCreditsComponent.backButtonFunc.unsubscribe).toHaveBeenCalled();
+ });
+
+ it('should return merged properties ', () => {
+ // arrange
+ // act
+ // assert
+ expect(viewCreditsComponent.mergeProperties(['creator', 'creators'])).toEqual('SAMPLE_CREATOR, SAMPLE_CREATORS');
+ });
+
+});
diff --git a/src/app/components/popups/view-credits/view-credits.component.ts b/src/app/components/popups/view-credits/view-credits.component.ts
index a05b08ca8d..568b008625 100644
--- a/src/app/components/popups/view-credits/view-credits.component.ts
+++ b/src/app/components/popups/view-credits/view-credits.component.ts
@@ -1,67 +1,50 @@
-import { Component, NgZone } from '@angular/core';
+import { Component, NgZone, OnInit } from '@angular/core';
import { NavParams, Platform, PopoverController } from '@ionic/angular';
import { TelemetryObject } from 'sunbird-sdk';
import { ProfileConstants } from '@app/app/app.constant';
import { AppGlobalService } from '@app/services/app-global-service.service';
import { TelemetryGeneratorService } from '@app/services/telemetry-generator.service';
-import { Environment, InteractType } from '@app/services/telemetry-constants';
+import { Environment, InteractType, InteractSubtype } from '@app/services/telemetry-constants';
+import { ContentUtil } from '@app/util/content-util';
@Component({
selector: 'app-view-credits',
templateUrl: './view-credits.component.html',
styleUrls: ['./view-credits.component.scss'],
})
-export class ViewCreditsComponent {
+export class ViewCreditsComponent implements OnInit {
userId = '';
backButtonFunc = undefined;
content: any;
rollUp: any;
correlation: any;
- private pageId = '';
- private popupType: string;
+ pageId = '';
- /**
- * Default function of class ViewCreditsComponent
- *
- * @param navParams
- * @param viewCtrl
- * @param platform
- * @param ngZone
- * @param telemetrygeneratorService
- * @param appGlobalService
- */
constructor(
private navParams: NavParams,
private platform: Platform,
- private ngZone: NgZone,
- private telemetrygeneratorService: TelemetryGeneratorService,
- private appGlobalService: AppGlobalService,
- private popOverCtrl: PopoverController
- ) {
- this.getUserId();
+ private popOverCtrl: PopoverController,
+ private telemetrygeneratorService: TelemetryGeneratorService
+ ) {}
+
+ ngOnInit(): void {
this.backButtonFunc = this.platform.backButton.subscribeWithPriority(11, () => {
this.popOverCtrl.dismiss();
this.backButtonFunc.unsubscribe();
});
- this.ngZone.run(() => {
- this.popupType = this.navParams.get('popupType');
- });
}
- /**
- * Ionic life cycle hook
- */
ionViewDidLoad(): void {
this.content = this.navParams.get('content');
this.pageId = this.navParams.get('pageId');
this.rollUp = this.navParams.get('rollUp');
this.correlation = this.navParams.get('correlation');
- const telemetryObject = new TelemetryObject(this.content.identifier, this.content.contentType, this.content.pkgVersion);
+ const telemetryObject = ContentUtil.getTelemetryObject(this.content);
this.telemetrygeneratorService.generateInteractTelemetry(InteractType.TOUCH,
- 'credits-clicked',
+ InteractSubtype.CREDITS_CLICKED,
Environment.HOME,
this.pageId,
telemetryObject,
@@ -71,43 +54,13 @@ export class ViewCreditsComponent {
);
}
- /* SUDO
- if firstprperty is there and secondprperty is not there, then return firstprperty value
- else if firstprperty is not there and secondprperty is there, then return secondprperty value
- else do the merger of firstprperty and secondprperty value and return merged value
- */
- mergeProperties(firstProp, secondProp) {
- if (this.content[firstProp] && !this.content[secondProp]) {
- return this.content[firstProp];
- } else if (!this.content[firstProp] && this.content[secondProp]) {
- return this.content[secondProp];
- } else {
- let first: any;
- let second: any;
- first = this.content[firstProp].split(', ');
- second = this.content[secondProp].split(', ');
- first = second.concat(first);
- first = Array.from(new Set(first));
- return first.join(', ');
- }
- }
-
- /**
- * Get user id
- */
- getUserId() {
- if (this.appGlobalService.getSessionData()) {
- this.userId = this.appGlobalService.getSessionData()[
- ProfileConstants.USER_TOKEN
- ];
- } else {
- this.userId = '';
- }
+ mergeProperties(mergeProp) {
+ return ContentUtil.mergeProperties(this.content, mergeProp);
}
cancel() {
this.popOverCtrl.dismiss();
- if(this.backButtonFunc) {
+ if (this.backButtonFunc) {
this.backButtonFunc.unsubscribe();
}
}
diff --git a/src/app/components/sign-in-card/sign-in-card.component.ts b/src/app/components/sign-in-card/sign-in-card.component.ts
index e7bab23535..507156d805 100644
--- a/src/app/components/sign-in-card/sign-in-card.component.ts
+++ b/src/app/components/sign-in-card/sign-in-card.component.ts
@@ -126,6 +126,10 @@ export class SignInCardComponent implements OnInit {
})
.then(async () => {
await loader.dismiss();
+ if (!this.appGlobalService.signinOnboardingLoader) {
+ this.appGlobalService.signinOnboardingLoader = await this.commonUtilService.getLoader();
+ await this.appGlobalService.signinOnboardingLoader.present();
+ }
that.ngZone.run(() => {
that.preferences.putString('SHOW_WELCOME_TOAST', 'true').toPromise().then();
diff --git a/src/app/district-mapping/district-mapping.page.spec.ts b/src/app/district-mapping/district-mapping.page.spec.ts
index e1730beb50..122314241b 100644
--- a/src/app/district-mapping/district-mapping.page.spec.ts
+++ b/src/app/district-mapping/district-mapping.page.spec.ts
@@ -15,6 +15,7 @@ import { SharedPreferences } from '../../../../sunbird-mobile-sdk/src/util/share
import { EMPTY, of, throwError } from 'rxjs';
import { LocationSearchResult } from '../../../../sunbird-mobile-sdk/src/profile/def/location-search-result';
import { ProfileService, Profile, ProfileType, ProfileSource, DeviceRegisterResponse } from 'sunbird-sdk';
+import { PreferenceKey } from '@app/app/app.constant';
describe('DistrictMappingPage', () => {
let districtMappingPage: DistrictMappingPage;
@@ -53,7 +54,8 @@ describe('DistrictMappingPage', () => {
};
const mockAppGlobalService: Partial = {
isUserLoggedIn: jest.fn(() => true),
- getCurrentUser: jest.fn(() => profile)
+ getCurrentUser: jest.fn(() => profile),
+ closeSigninOnboardingLoader: jest.fn()
};
const mockEvents: Partial = {
publish: jest.fn()
@@ -291,11 +293,41 @@ describe('DistrictMappingPage', () => {
expect(mockCommonUtilService.showToast).toHaveBeenCalledWith('INTERNET_CONNECTIVITY_NEEDED');
});
+ it('should invoke device register API and save it in the preference', (done) => {
+ // arrange
+ districtMappingPage.stateList = [{ type: 'state', name: 'Odisha', id: 'od_123' }];
+ districtMappingPage.districtList = [{ type: 'district', name: 'Cuttack', id: 'ct_123' }];
+ districtMappingPage.stateName = 'Odisha';
+ districtMappingPage.districtName = 'Cuttack';
+ mockCommonUtilService.networkInfo = { isNetworkAvailable: true };
+ const req = {
+ userDeclaredLocation: {
+ state: 'Odisha',
+ stateId: 'od_123',
+ district: 'Cuttack',
+ districtId: 'ct_123',
+ declaredOffline: false
+ }
+ };
+
+ // act
+ districtMappingPage.saveDeviceLocation();
+ // assert
+ setTimeout( () => {
+ expect(mockDeviceRegisterService.registerDevice).toHaveBeenCalledWith(req);
+ expect(mockPreferences.putString).toHaveBeenCalledWith(PreferenceKey.DEVICE_LOCATION, JSON.stringify(req.userDeclaredLocation));
+ expect(mockCommonUtilService.getLoader().dismiss).toHaveBeenCalled();
+ done();
+ }, 1);
+ });
+
it('should invoke updateServerProfile when submit clicked', (done) => {
// arrange
window.history.replaceState({ profile }, 'MOCK');
mockCommonUtilService.networkInfo = { isNetworkAvailable: true };
districtMappingPage.name = 'sample_name';
+ mockCommonUtilService.isDeviceLocationAvailable = jest.fn(() => Promise.resolve(false));
+ jest.spyOn(districtMappingPage, 'saveDeviceLocation').mockImplementation();
// act
districtMappingPage.submit();
// assert
@@ -381,11 +413,12 @@ describe('DistrictMappingPage', () => {
// arrange
window.history.replaceState({ source: 'guest-profile' }, 'MOCK');
mockAppGlobalService.isUserLoggedIn = jest.fn(() => false);
+ jest.spyOn(districtMappingPage, 'saveDeviceLocation').mockImplementation();
// act
districtMappingPage.submit();
// assert
setTimeout(() => {
- expect(mockDeviceRegisterService.registerDevice).toHaveBeenCalled();
+ expect(districtMappingPage.saveDeviceLocation).toHaveBeenCalled();
expect(mockLocation.back).toHaveBeenCalled();
expect(mockEvents.publish).toHaveBeenCalledWith('refresh:profile');
done();
@@ -398,11 +431,13 @@ describe('DistrictMappingPage', () => {
// arrange
window.history.replaceState({ source: 'profile-setting' }, 'MOCK');
mockAppGlobalService.isUserLoggedIn = jest.fn(() => false);
+ mockCommonUtilService.isDeviceLocationAvailable = jest.fn(() => Promise.resolve(false));
+ jest.spyOn(districtMappingPage, 'saveDeviceLocation').mockImplementation();
// act
districtMappingPage.submit();
// assert
setTimeout(() => {
- expect(mockDeviceRegisterService.registerDevice).toHaveBeenCalled();
+ expect(districtMappingPage.saveDeviceLocation).toHaveBeenCalled();
expect(mockRouter.navigate).toHaveBeenCalledWith(['/tabs'], {
state: {
loginMode: 'guest'
diff --git a/src/app/district-mapping/district-mapping.page.ts b/src/app/district-mapping/district-mapping.page.ts
index e624f0f363..49890920be 100644
--- a/src/app/district-mapping/district-mapping.page.ts
+++ b/src/app/district-mapping/district-mapping.page.ts
@@ -1,7 +1,7 @@
import {Component, OnInit, Inject, ChangeDetectorRef, NgZone, ViewChild} from '@angular/core';
import {
LocationSearchCriteria, ProfileService,
- SharedPreferences, Profile, DeviceRegisterRequest, DeviceRegisterService, DeviceInfo
+ SharedPreferences, Profile, DeviceRegisterRequest, DeviceRegisterService, DeviceInfo, LocationSearchResult
} from 'sunbird-sdk';
import { Location as loc, PreferenceKey, RouterLinks, LocationConfig } from '../../app/app.constant';
import { AppHeaderService, CommonUtilService, AppGlobalService, FormAndFrameworkUtilService } from '@app/services';
@@ -104,8 +104,8 @@ export class DistrictMappingPage {
}
name;
- stateList = [];
- districtList = [];
+ stateList: LocationSearchResult[] = [];
+ districtList: LocationSearchResult[] = [];
stateCode;
districtCode;
backButtonFunc: Subscription;
@@ -137,6 +137,7 @@ export class DistrictMappingPage {
private ngZone: NgZone,
private externalIdVerificationService: ExternalIdVerificationService
) {
+ this.appGlobalService.closeSigninOnboardingLoader();
this.isKeyboardShown$ = deviceInfo.isKeyboardShown().pipe(
tap(() => this.changeDetectionRef.detectChanges())
);
@@ -435,15 +436,14 @@ export class DistrictMappingPage {
const req: DeviceRegisterRequest = {
userDeclaredLocation: {
state: this.stateName,
+ stateId: this.stateList.find((s) => s.name === this.stateName).id,
district: this.districtName,
+ districtId: this.districtList.find((d) => d.name === this.districtName).id,
+ declaredOffline: !this.commonUtilService.networkInfo.isNetworkAvailable
}
};
this.deviceRegisterService.registerDevice(req).toPromise();
-
- const locationMap = new Map();
- locationMap['state'] = this.stateName ? this.stateName : this.availableLocationState;
- locationMap['district'] = this.districtName ? this.districtName : this.availableLocationDistrict;
- await this.preferences.putString(PreferenceKey.DEVICE_LOCATION, JSON.stringify(locationMap)).toPromise();
+ this.preferences.putString(PreferenceKey.DEVICE_LOCATION, JSON.stringify(req.userDeclaredLocation)).toPromise();
await loader.dismiss();
}
diff --git a/src/app/profile/categories-edit/categories-edit.page.ts b/src/app/profile/categories-edit/categories-edit.page.ts
index 3a023a0eb4..fe5e3a174e 100644
--- a/src/app/profile/categories-edit/categories-edit.page.ts
+++ b/src/app/profile/categories-edit/categories-edit.page.ts
@@ -143,6 +143,7 @@ export class CategoriesEditPage {
private tncUpdateHandlerService: TncUpdateHandlerService,
) {
+ this.appGlobalService.closeSigninOnboardingLoader();
this.profile = this.appGlobalService.getCurrentUser();
const extrasState = this.router.getCurrentNavigation().extras.state;
if (extrasState && extrasState.showOnlyMandatoryFields) {
diff --git a/src/app/tabs/tabs.page.html b/src/app/tabs/tabs.page.html
index 6e19dc0bdd..6dfc76726a 100644
--- a/src/app/tabs/tabs.page.html
+++ b/src/app/tabs/tabs.page.html
@@ -1,5 +1,5 @@
-
+
diff --git a/src/app/tabs/tabs.page.scss b/src/app/tabs/tabs.page.scss
index a9ea93c1d2..7d13e72d6f 100644
--- a/src/app/tabs/tabs.page.scss
+++ b/src/app/tabs/tabs.page.scss
@@ -4,7 +4,11 @@
ion-tabs {
ion-tab-button {
}
-
+ .rm-before{
+ &::before {
+ position: relative !important;
+ }
+ }
ion-tab-bar {
align-items: flex-end;
contain: none;
diff --git a/src/app/tabs/tabs.page.ts b/src/app/tabs/tabs.page.ts
index bf36f2bde6..c6b136cd35 100644
--- a/src/app/tabs/tabs.page.ts
+++ b/src/app/tabs/tabs.page.ts
@@ -31,6 +31,7 @@ export class TabsPage implements OnInit, AfterViewInit {
};
selectedLanguage: string;
appLabel: any;
+ olderWebView = false;
constructor(
private container: ContainerService,
@@ -49,7 +50,7 @@ export class TabsPage implements OnInit, AfterViewInit {
async ngOnInit() {
console.log('Inside tabsPage');
-
+ this.checkAndroidWebViewVersion();
const session = await this.appGlobalService.authService.getSession().toPromise();
if (!session) {
console.log(`Success Platform Session`, session);
@@ -111,6 +112,17 @@ export class TabsPage implements OnInit, AfterViewInit {
}
+ checkAndroidWebViewVersion() {
+ var that = this;
+ plugins['webViewChecker'].getCurrentWebViewPackageInfo()
+ .then(function(packageInfo) {
+ if (parseInt(packageInfo.versionName.split('.')[0], 10) <= 68) {
+ that.olderWebView = true;
+ }
+ })
+ .catch(function(error) { });
+ }
+
ionViewWillEnter() {
this.tabs = this.container.getAllTabs();
this.events.publish('update_header');
diff --git a/src/app/terms-and-conditions/terms-and-conditions.page.ts b/src/app/terms-and-conditions/terms-and-conditions.page.ts
index 8a21737420..3d06df7b26 100644
--- a/src/app/terms-and-conditions/terms-and-conditions.page.ts
+++ b/src/app/terms-and-conditions/terms-and-conditions.page.ts
@@ -9,9 +9,8 @@ import { LogoutHandlerService } from '@app/services/handlers/logout-handler.serv
import { TncUpdateHandlerService } from '@app/services/handlers/tnc-update-handler.service';
import { CommonUtilService } from '@app/services/common-util.service';
import { TelemetryGeneratorService } from '@app/services/telemetry-generator.service';
-import { AppHeaderService } from '@app/services/app-header.service';
import { ProfileConstants, RouterLinks } from '../app.constant';
-import { FormAndFrameworkUtilService } from '@app/services';
+import { FormAndFrameworkUtilService, AppGlobalService } from '@app/services';
import { Router, NavigationExtras } from '@angular/router';
import { SplashScreenService } from '@app/services/splash-screen.service';
import { ExternalIdVerificationService } from '@app/services/externalid-verification.service';
@@ -37,19 +36,19 @@ export class TermsAndConditionsPage implements OnInit {
private sanitizer: DomSanitizer,
private commonUtilService: CommonUtilService,
private telemetryGeneratorService: TelemetryGeneratorService,
- private headerService: AppHeaderService,
private appVersion: AppVersion,
private injector: Injector,
private formAndFrameworkUtilService: FormAndFrameworkUtilService,
private router: Router,
private splashScreenService: SplashScreenService,
- private externalIdVerificationService: ExternalIdVerificationService
+ private externalIdVerificationService: ExternalIdVerificationService,
+ private appGlobalService: AppGlobalService
) {
}
public async ngOnInit() {
+ this.appGlobalService.closeSigninOnboardingLoader();
this.appName = await this.appVersion.getAppName();
- this.headerService.hideHeader();
this.userProfileDetails = (await this.profileService.getActiveSessionProfile(
{ requiredFields: ProfileConstants.REQUIRED_FIELDS }).toPromise()).serverProfile;
@@ -134,15 +133,21 @@ export class TermsAndConditionsPage implements OnInit {
await loader.dismiss();
loader = undefined;
}
+ if (!this.appGlobalService.signinOnboardingLoader) {
+ this.appGlobalService.signinOnboardingLoader = await this.commonUtilService.getLoader();
+ await this.appGlobalService.signinOnboardingLoader.present();
+ }
this.disableSubmitButton = false;
if (value['status']) {
if (this.commonUtilService.isUserLocationAvalable(serverProfile)
- && await tncUpdateHandlerService.isSSOUser(profile)) {
+ || await tncUpdateHandlerService.isSSOUser(profile)) {
await tncUpdateHandlerService.dismissTncPage();
+ this.appGlobalService.closeSigninOnboardingLoader();
this.router.navigate(['/', RouterLinks.TABS]);
this.externalIdVerificationService.showExternalIdVerificationPopup();
this.splashScreenService.handleSunbirdSplashScreenActions();
} else {
+ // closeSigninOnboardingLoader() is called in District-Mapping page
const navigationExtras: NavigationExtras = {
state: {
isShowBackButton: false
@@ -151,6 +156,7 @@ export class TermsAndConditionsPage implements OnInit {
this.router.navigate(['/', RouterLinks.DISTRICT_MAPPING] , navigationExtras);
}
} else {
+ // closeSigninOnboardingLoader() is called in CategoryEdit page
await tncUpdateHandlerService.dismissTncPage();
this.router.navigate([`/${RouterLinks.PROFILE}/${RouterLinks.CATEGORIES_EDIT}`], {
state: {
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index a4405e3ca0..2b0f35cdb6 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -649,5 +649,8 @@
"DOWNLOAD_FILE_INFO": "Download the file to enable the disabled options",
"FILE_SAVED": "File saved in Downloads",
"DOWNLOAD_PATH": "Internal storage/Download",
- "DOWNLOAD_FILE_SIZE": "File Size {{%s}}"
+ "DOWNLOAD_FILE_SIZE": "File Size {{%s}}",
+ "UPDATE_REQUIRED": "Update Required",
+ "DOWNLOAD_NOW": "Download Now",
+ "WEBVIEW_UPDATE_TEXT": "For an effective experience, download the latest version of webview from Google Play store."
}
diff --git a/src/pipes/mime-type/mime-type.ts b/src/pipes/mime-type/mime-type.ts
index 97b673742a..03eef6bdae 100644
--- a/src/pipes/mime-type/mime-type.ts
+++ b/src/pipes/mime-type/mime-type.ts
@@ -1,11 +1,9 @@
import { MimeType } from './../../app/app.constant';
-import { Content, ContentData } from 'sunbird-sdk';
+import { Content } from 'sunbird-sdk';
import { Pipe, PipeTransform } from '@angular/core';
-
/*
Contents are filtered based on given mimetype
*/
-
@Pipe({
name: 'hasMimeType',
})
@@ -29,14 +27,13 @@ export class MimeTypePipe implements PipeTransform {
return this.getFilteredItems(item.children, mimeTypes);
}
}
-
-
+
getFilteredItems(contents: Content[] = [], mimeTypes: string[]): boolean {
const t = this.flattenDeep(contents)
.some((c) => !!mimeTypes.find(m => m === c.contentData.mimeType));
return t;
}
-
+
private flattenDeep(contents: Content[]): Content[] {
return contents.reduce((acc, val) => {
if (val.children) {
diff --git a/src/services/app-global-service.service.spec.ts b/src/services/app-global-service.service.spec.ts
index dd6286ab00..fe57cf9719 100644
--- a/src/services/app-global-service.service.spec.ts
+++ b/src/services/app-global-service.service.spec.ts
@@ -7,13 +7,17 @@ import { of } from 'rxjs';
import { PreferenceKey } from '../app/app.constant';
describe('AppGlobalService', () => {
let appGlobalService: AppGlobalService;
- const mockProfile: Partial = {};
+ const profile = { syllabus: 'tn'} as any;
+ const mockProfile: Partial = {
+ getActiveSessionProfile: jest.fn(() => of(profile))
+ };
const mockAuthService: Partial = {
getSession: jest.fn(() => of())
};
const mockFrameworkService: Partial = {};
const mockEvent: Partial = {
- subscribe: jest.fn()
+ subscribe: jest.fn(),
+ publish: jest.fn()
};
const mockPopoverCtrl: Partial = {};
const mockTelemetryGeneratorService: Partial = {};
@@ -118,4 +122,30 @@ describe('AppGlobalService', () => {
// assert
expect(appGlobalService.limitedShareQuizContent).toBeNull();
});
+
+ it('should set the signin Onboarding loader', () => {
+ // arrange
+ appGlobalService.signinOnboardingLoader = {
+ dismiss: jest.fn((fn) => fn())
+ };
+ // act
+ // assert
+ expect(appGlobalService.signinOnboardingLoader).toBeDefined();
+ });
+
+ it('should dismiss the signin Onboarding loader', (done) => {
+ // arrange
+ appGlobalService.signinOnboardingLoader = {
+ dismiss: jest.fn(() => Promise.resolve())
+ };
+ // act
+ appGlobalService.closeSigninOnboardingLoader();
+ // assert
+ expect(appGlobalService.signinOnboardingLoader.dismiss).toHaveBeenCalled();
+
+ setTimeout(() => {
+ expect(appGlobalService.signinOnboardingLoader).toBeNull();
+ done();
+ }, 1);
+ });
});
diff --git a/src/services/app-global-service.service.ts b/src/services/app-global-service.service.ts
index fce42cbffc..2bf273b35d 100644
--- a/src/services/app-global-service.service.ts
+++ b/src/services/app-global-service.service.ts
@@ -71,6 +71,7 @@ export class AppGlobalService implements OnDestroy {
private _limitedShareQuizContent: any;
private _isSignInOnboardingCompleted: any;
private isJoinTraningOnboarding: any;
+ private _signinOnboardingLoader: any;
constructor(
@@ -677,10 +678,24 @@ export class AppGlobalService implements OnDestroy {
this.isJoinTraningOnboarding = value;
}
+ get signinOnboardingLoader() {
+ return this._signinOnboardingLoader;
+ }
+ set signinOnboardingLoader(value) {
+ this._signinOnboardingLoader = value;
+ }
+
// This method is used to reset if any quiz content data is previously saved before Joining a Training
// So it wont affect in the exterId verification page
resetSavedQuizContent() {
this.limitedShareQuizContent = null;
}
+ async closeSigninOnboardingLoader() {
+ if (this.signinOnboardingLoader) {
+ await this.signinOnboardingLoader.dismiss();
+ this.signinOnboardingLoader = null;
+ }
+ }
+
}
diff --git a/src/services/content/content-info.ts b/src/services/content/content-info.ts
index 5b5ec88cbb..423220b123 100644
--- a/src/services/content/content-info.ts
+++ b/src/services/content/content-info.ts
@@ -5,5 +5,5 @@ export interface ContentInfo {
rollUp: Rollup;
correlationList: CorrelationData[];
hierachyInfo: HierarchyInfo[];
- course?:Course
+ course?: Course;
}
diff --git a/src/services/externalid-verification.service.ts b/src/services/externalid-verification.service.ts
index 8317c09e96..e03ea247c5 100644
--- a/src/services/externalid-verification.service.ts
+++ b/src/services/externalid-verification.service.ts
@@ -28,6 +28,7 @@ export class ExternalIdVerificationService {
}
async showExternalIdVerificationPopup() {
+ this.appGlobalService.closeSigninOnboardingLoader();
if (await this.checkQuizContent()) {
return;
}
diff --git a/src/services/formandframeworkutil.service.spec.ts b/src/services/formandframeworkutil.service.spec.ts
new file mode 100644
index 0000000000..62e8dd6a6c
--- /dev/null
+++ b/src/services/formandframeworkutil.service.spec.ts
@@ -0,0 +1,93 @@
+import { FormAndFrameworkUtilService } from './formandframeworkutil.service';
+import { ProfileService, SystemSettingsService, FrameworkUtilService, FormService, FrameworkService, SharedPreferences } from 'sunbird-sdk';
+import { AppGlobalService } from './app-global-service.service';
+import { AppVersion } from '@ionic-native/app-version/ngx';
+import { TranslateService } from '@ngx-translate/core';
+import { Events } from '@ionic/angular';
+import { of, throwError } from 'rxjs';
+
+
+describe('FormAndFrameworkUtilService', () => {
+ let formAndFrameworkUtilService: FormAndFrameworkUtilService;
+
+ const mockProfileService: Partial = {};
+ const mockSystemSettingsService: Partial = {};
+ const mockFrameworkUtilService: Partial = {};
+ const mockFormAndFrameworkUtilService: Partial = {};
+ const mockFormService: Partial = {};
+ const mockFrameworkService: Partial = {};
+ const mockSharedPreferences: Partial = {};
+ const mockAppGlobalService: Partial = {};
+ const mockAppVersion: Partial = {};
+ const mockTranslateService: Partial = {};
+ const mockEvents: Partial = {};
+
+ beforeAll(() => {
+ formAndFrameworkUtilService = new FormAndFrameworkUtilService(
+ mockProfileService as ProfileService,
+ mockSystemSettingsService as SystemSettingsService,
+ mockFrameworkUtilService as FrameworkUtilService,
+ mockFormService as FormService,
+ mockFrameworkService as FrameworkService,
+ mockSharedPreferences as SharedPreferences,
+ mockAppGlobalService as AppGlobalService,
+ mockAppVersion as AppVersion,
+ mockTranslateService as TranslateService,
+ mockEvents as Events
+ );
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('should create an instance of FormAndFrameworkUtilService', () => {
+ expect(formAndFrameworkUtilService).toBeTruthy();
+ });
+
+ describe('getWebviewConfig()', () => {
+
+ it('should return the webview version', () => {
+ // arrange
+ const formResponse = {
+ form: {
+ type: 'config',
+ subtype: 'webview_version',
+ action: 'get',
+ data: {
+ action: 'get',
+ fields: [
+ {
+ version: '54'
+ }
+ ]
+ }
+ }
+ };
+ mockFormService.getForm = jest.fn(() => of(formResponse));
+ // act
+ // assert
+ expect(formAndFrameworkUtilService.getWebviewConfig()).resolves.toBe(54);
+ });
+ });
+
+ it('should return the webview version if value is not set', () => {
+ // arrange
+ const formResponse = {
+ form: ''
+ };
+ mockFormService.getForm = jest.fn(() => of(formResponse));
+ // act
+ // assert
+ expect(formAndFrameworkUtilService.getWebviewConfig()).resolves.toBe(54);
+ });
+
+ it('should reject the error if API throws some error', () => {
+ // arrange
+ mockFormService.getForm = jest.fn(() => throwError({error: 'API_ERROR'}));
+ // act
+ // assert
+ expect(formAndFrameworkUtilService.getWebviewConfig()).rejects.toBe({error: 'API_ERROR'});
+ });
+
+});
diff --git a/src/services/formandframeworkutil.service.ts b/src/services/formandframeworkutil.service.ts
index 568f8caa42..138c525d97 100644
--- a/src/services/formandframeworkutil.service.ts
+++ b/src/services/formandframeworkutil.service.ts
@@ -601,4 +601,26 @@ export class FormAndFrameworkUtilService {
});
});
}
+
+ // get the required webview version
+ getWebviewConfig() {
+ return new Promise((resolve, reject) => {
+ const req: FormRequest = {
+ type: 'config',
+ subType: 'webview_version',
+ action: 'get',
+ };
+ // form api call
+ this.formService.getForm(req).toPromise()
+ .then((res: any) => {
+ if (res.form && res.form.data && res.form.data.fields[0].version) {
+ resolve(parseInt(res.form.data.fields[0].version, 10));
+ } else {
+ resolve(54);
+ }
+ }).catch((error: any) => {
+ reject(error);
+ });
+ });
+ }
}
diff --git a/src/services/handlers/tnc-update-handler.service.ts b/src/services/handlers/tnc-update-handler.service.ts
index 5c8b4f3d66..5e62f6ef82 100644
--- a/src/services/handlers/tnc-update-handler.service.ts
+++ b/src/services/handlers/tnc-update-handler.service.ts
@@ -49,6 +49,8 @@ export class TncUpdateHandlerService {
return;
}
this.presentTncPage({ profile });
+ }).catch(e => {
+ this.appGlobalService.closeSigninOnboardingLoader();
});
}
@@ -130,6 +132,7 @@ export class TncUpdateHandlerService {
}
})
.catch(() => {
+ this.appGlobalService.closeSigninOnboardingLoader();
this.externalIdVerificationService.showExternalIdVerificationPopup();
});
}
diff --git a/src/services/login-handler.service.ts b/src/services/login-handler.service.ts
index 7b4cce3033..fde867a685 100644
--- a/src/services/login-handler.service.ts
+++ b/src/services/login-handler.service.ts
@@ -30,6 +30,7 @@ import {
} from '@app/services/telemetry-constants';
import { ContainerService } from '@app/services/container.services';
import { Router } from '@angular/router';
+import { AppGlobalService } from './app-global-service.service';
@Injectable()
export class LoginHandlerService {
@@ -54,7 +55,8 @@ export class LoginHandlerService {
private formAndFrameworkUtilService: FormAndFrameworkUtilService,
private telemetryGeneratorService: TelemetryGeneratorService,
private router: Router,
- private events: Events
+ private events: Events,
+ private appGlobalService: AppGlobalService
) {
this.appVersion.getAppName()
@@ -112,6 +114,10 @@ export class LoginHandlerService {
})
.then(async () => {
await loader.dismiss();
+ if (!this.appGlobalService.signinOnboardingLoader) {
+ this.appGlobalService.signinOnboardingLoader = await this.commonUtilService.getLoader();
+ await this.appGlobalService.signinOnboardingLoader.present();
+ }
that.ngZone.run(() => {
that.preferences.putString('SHOW_WELCOME_TOAST', 'true').toPromise().then();
// this.events.publish('UPDATE_TABS');
diff --git a/src/services/telemetry-constants.ts b/src/services/telemetry-constants.ts
index 522335da81..92104c29f9 100644
--- a/src/services/telemetry-constants.ts
+++ b/src/services/telemetry-constants.ts
@@ -117,7 +117,8 @@ export enum PageId {
DISTRICT_MAPPING = 'district-mapping',
SIGNIN_POPUP = 'signin-popup',
EXTERNAL_USER_VERIFICATION_POPUP = 'user-verification-popup',
- FAQ_REPORT_ISSUE = 'faq-report-issue'
+ FAQ_REPORT_ISSUE = 'faq-report-issue',
+ UPDATE_WEBVIEW_POPUP = 'update-webview-popup',
}
export enum LogType {
NOTIFICATION = 'notification'
@@ -377,7 +378,9 @@ export enum InteractSubtype {
REPORT_ISSUE_CLICKED = 'report-issue-clicked',
STATE_DIST_CHANGED = 'state-dist-changed',
STATE_CHANGED = 'state-changed',
- DIST_CHANGED = 'dist-changed'
+ DIST_CHANGED = 'dist-changed',
+ UPDATE_WEBVIEW_CLICKED = 'update-webview-clicked',
+ CREDITS_CLICKED = 'credits-clicked'
}
export enum ID {
diff --git a/src/util/content-util.ts b/src/util/content-util.ts
index 56baf1960c..cd98fa8cd3 100644
--- a/src/util/content-util.ts
+++ b/src/util/content-util.ts
@@ -4,9 +4,9 @@ export class ContentUtil {
/**
* Returns values from ContentData in a comma-separated string
- * @param {ContentData} contentData
- * @param {string[]} properties
- * @returns {string}
+ * @param ContentData contentData
+ * @param string[] properties
+ * @returns string
*/
public static mergeProperties(contentData: ContentData, properties: string[]): string {
@@ -36,11 +36,11 @@ export class ContentUtil {
}
}
- /**
+ /**
* Returns rollup
- * @param {HierarchyInfo[]} hierarchyInfoList
- * @param {string} identifier
- * @returns {Rollup}
+ * @param HierarchyInfo[] hierarchyInfoList
+ * @param string identifier
+ * @returns Rollup
*/
public static generateRollUp(hierarchyInfoList, identifier): Rollup {
const rollUp = new Rollup();
@@ -55,12 +55,12 @@ export class ContentUtil {
return rollUp;
}
- /**
+ /**
* Returns apt app icon
- * @param {string} appIcon
- * @param {string} basePath
- * @param {boolean} isNetworkAvailable
- * @returns {string}
+ * @param string appIcon
+ * @param string basePath
+ * @param boolean isNetworkAvailable
+ * @returns string
*/
public static getAppIcon(appIcon: string, basePath: string, isNetworkAvailable: boolean): string {
if (appIcon) {
@@ -91,10 +91,10 @@ export class ContentUtil {
return pdf;
}
- /**
+ /**
* Returns TelemetryObject
- * @param {any} content
- * @returns {TelemetryObject}
+ * @param any content
+ * @returns TelemetryObject
*/
public static getTelemetryObject(content): TelemetryObject {
const identifier = content.identifier;