diff --git a/src/index.ts b/src/index.ts index c269450..1184dde 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,7 +16,7 @@ import { isValidVersionFormat } from './utils'; type Theme = 'light' | 'dark'; /** - * Этот класс - абстракция для связи веб приложения с нативом и предназначен ТОЛЬКО + * Этот класс — абстракция для связи веб приложения с нативом и предназначен ТОЛЬКО * для использования в вебвью окружении. */ export class BridgeToNative { @@ -41,14 +41,21 @@ export class BridgeToNative { private _theme: Theme; - private _handleRedirect: HandleRedirect; + private readonly _blankPagePath: string; - constructor(handleRedirect: HandleRedirect, nativeParams?: NativeParams) { + private readonly _handleRedirect: HandleRedirect; + + constructor( + handleRedirect: HandleRedirect, + blankPagePath: string, + nativeParams?: NativeParams, + ) { const previousState = !!sessionStorage.getItem(PREVIOUS_B2N_STATE_STORAGE_KEY); if (previousState) { this.restorePreviousState(); this.nativeFallbacks = new NativeFallbacks(this); + this._blankPagePath = blankPagePath; return; } @@ -70,6 +77,7 @@ export class BridgeToNative { this.nextPageId = nativeParams ? nativeParams.nextPageId : null; this.nativeFallbacks = new NativeFallbacks(this); + this._blankPagePath = blankPagePath; } get theme() { diff --git a/src/native-navigation-and-title.ts b/src/native-navigation-and-title.ts index 1f96511..a7adead 100644 --- a/src/native-navigation-and-title.ts +++ b/src/native-navigation-and-title.ts @@ -24,7 +24,7 @@ export class NativeNavigationAndTitle { // Просто, чтобы не слать одинаковые сигналы в приложение. private lastSetPageSettingsParams = ''; - private handleWindowRedirect: HandleRedirect; + private readonly handleWindowRedirect: HandleRedirect; constructor( private b2n: BridgeToNative, @@ -57,15 +57,15 @@ export class NativeNavigationAndTitle { /** * Метод, вызывающий history.go(-колл. шагов назад) и модифицирует внутреннее - * состояние, чтобы в дальнейшем зарегистририровать этот переход в приложении. + * состояние, чтобы в дальнейшем зарегистрировать этот переход в приложении. * * @param stepsNumber Количество шагов назад. * Возможно передача как положительного, так и отрицательного числа. * 0 будет проигнорирован. - * @param autocloseWebview Флаг – закрывать ли вебвью автоматически, - * если переданное кол-во шагов будет больше, чем записей в истории. + * @param autoCloseWebview Флаг – закрывать ли вебвью автоматически, + * если переданное кол-во шагов будет больше чем записей в истории. */ - public goBackAFewSteps(stepsNumber: number, autocloseWebview = false) { + public goBackAFewSteps(stepsNumber: number, autoCloseWebview = false) { if (!stepsNumber) { return; } @@ -74,7 +74,7 @@ export class NativeNavigationAndTitle { const maxStepsToBack = this.nativeHistoryStack.length - 1; if (stepsToBack > maxStepsToBack) { - if (autocloseWebview) { + if (autoCloseWebview) { this.b2n.closeWebview(); return; @@ -137,7 +137,7 @@ export class NativeNavigationAndTitle { /** * Информирует натив, что веб находится на первом экране (сбрасывает историю переходов, не влияя на браузерную - * историю), а значит следующее нажатие на кнопку "Назад" в нативне закроет вебвью. + * историю), а значит следующее нажатие на кнопку "Назад" в нативе закроет вебвью. * * @param pageTitle Заголовок, который нужно отрисовать в нативе. */ @@ -159,12 +159,12 @@ export class NativeNavigationAndTitle { } /** - * Метод для открытия второго web приложения в рамках - * одной вебвью сессии - * сохраняет все текущее состояние текущего экземпляра bridgeToAm и AmNavigationAndTitle в sessionStorage, а + * Метод для открытия второго web приложения в рамках одной вебвью сессии. + * Сохраняет все текущее состояние текущего экземпляра bridgeToAm и AmNavigationAndTitle в sessionStorage, а * так же наполняет url необходимыми query параметрами. Работает только в Android окружении. * В IOS окружении будет открыто новое webview поверх текущего. - * @param url адрес второго web приложения к которому перед переходом на него будут добавлены + * + * @param url адрес второго web приложения, к которому перед переходом на него будут добавлены * все initial query параметры от натива и параметр nextPageId (Android) */ public navigateInsideASharedSession(url: string) { @@ -183,20 +183,15 @@ export class NativeNavigationAndTitle { } /** - * ПОКА НЕ ИСПОЛЬЗОВАТЬ В ПРОДЕ (МЕТОД НЕ РАБОТАЕТ КОРРЕКТНО НА ANDROID) - * НА ANDROID ПРИ ПЕРЕЗАГРУЗКЕ СТРАНИЦЫ В ИСТРИЮ ЛОЖИТСЯ + ЕЩЕ ОДИН ЛИШНИЙ ЭЛЕМЕНТ - * Данный метод будет дорабатываться отдельной задачей * Безопасный способ для перезагрузки страницы. - * Производит предварительное сохранение текущего состояния - * BridgeToNative и NativeNavigationAndTitle перед вызовом location.reload() */ - public reloadPage() { + public pseudoReloadPage() { // В b2n этот метод отмечен модификатором доступа private, но тут его нужно вызвать // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - this.b2n.saveCurrentState(); + this.handleRedirect(this.b2n._blankPagePath); - window.location.reload(); + this.goBack(); } /** @@ -269,7 +264,7 @@ export class NativeNavigationAndTitle { const stackSize = this.nativeHistoryStack.length; // Нажимая на кнопку назад, можно дойти до "первой" страницы, - // в iOS для "первой" страницы не нужно слать `pageId` + // в iOS для "первой" страницы не нужно слать `pageId`. return this.b2n.environment === 'ios' && stackSize <= 1 ? null : stackSize; } @@ -401,7 +396,7 @@ export class NativeNavigationAndTitle { * рамках одной вебвью сессии * @param url - url иного веб приложения * @return подготовленная согласно контракту ссылка на иное веб приложение с initial query - * параметрами от нативе, а так же nextPageId + * параметрами от натива, а так же nextPageId. */ private prepareExternalLinkBeforeOpen(url: string) { const currentPageId = this.nativeHistoryStack.length; diff --git a/test/index.test.ts b/test/index.test.ts index 8647597..bb912bb 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -49,7 +49,7 @@ describe('BridgeToNative', () => { nextPageId: null, }; - const { getItem, setItem } = mockSessionStorage( + const { getItem, setItem, removeItem } = mockSessionStorage( PREVIOUS_B2N_STATE_STORAGE_KEY, savedBridgeToAmState, ); @@ -65,7 +65,7 @@ describe('BridgeToNative', () => { BridgeToNative.prototype['restorePreviousState'] = jest.fn(); - const inst = new BridgeToNative(mockedHandleRedirect, { + const inst = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, title: 'Initial Title', }); @@ -80,7 +80,7 @@ describe('BridgeToNative', () => { describe('method `saveCurrentState`', () => { it('should save current state into sessionStorage and call `AmNavigationAndTitle.saveCurrentState`', () => { - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); const currentB2amState = { appVersion: inst['appVersion'], @@ -101,23 +101,22 @@ describe('BridgeToNative', () => { describe('method `restorePreviousState`', () => { it('should get previous state from sessionStorage and restore it and cleared storage', () => { - // JSON.parse = jest.fn().mockImplementationOnce(() => savedBridgeToAmState); + JSON.parse = jest.fn(() => savedBridgeToAmState); - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); inst['restorePreviousState'](); expect(getItem).toBeCalledWith(PREVIOUS_B2N_STATE_STORAGE_KEY); - // expect(inst['appVersion']).toBe(savedBridgeToAmState.appVersion); - // expect(inst['iosAppId']).toBe(savedBridgeToAmState.iosAppId); - // expect(inst['theme']).toBe(savedBridgeToAmState.theme); - // expect(inst['originalWebviewParams']).toBe( - // savedBridgeToAmState.originalWebviewParams, - // ); - // expect(inst['nextPageId']).toBe(savedBridgeToAmState.nextPageId); - // - // expect(removeItem).toBeCalledWith(PREVIOUS_B2N_STATE_STORAGE_KEY); + expect(inst['appVersion']).toBe(savedBridgeToAmState.appVersion); + expect(inst['iosAppId']).toBe(savedBridgeToAmState.iosAppId); + expect(inst['theme']).toBe(savedBridgeToAmState.theme); + expect(inst['originalWebviewParams']).toBe( + savedBridgeToAmState.originalWebviewParams, + ); + expect(inst['nextPageId']).toBe(savedBridgeToAmState.nextPageId); + expect(removeItem).toBeCalledWith(PREVIOUS_B2N_STATE_STORAGE_KEY); }); }); }); @@ -141,7 +140,7 @@ describe('BridgeToNative', () => { describe('constructor', () => { it('should pass `initialAmTitle` to `AmNavigationAndTitle` constructor', () => { - const inst = new BridgeToNative(mockedHandleRedirect, { + const inst = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, title: 'Initial Title', }); @@ -157,8 +156,8 @@ describe('BridgeToNative', () => { describe('public props', () => { it('should save theme used by AM in `theme` property', () => { - const inst1 = new BridgeToNative(mockedHandleRedirect, defaultAmParams); - const inst2 = new BridgeToNative(mockedHandleRedirect, { + const inst1 = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); + const inst2 = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, theme: 'dark', }); @@ -170,7 +169,7 @@ describe('BridgeToNative', () => { it('should save original AM query params in `originalWebviewParams` property', () => { const originalWebviewParamsExample = 'device_uuid=8441576F-A09F-41E9-89A7-EE1FA486C20A&device_id=2E32AFD5-F50B-4B2F-B758-CAE59DF2BF6C&applicationId=1842D0AA-0008-4941-93E0-4FD80E087841&device_os_version=com.aconcierge.app&device_app_version=iOS 16.1&scope=12.26.0&device_boot_time=openid mobile-bank'; - const inst = new BridgeToNative(mockedHandleRedirect, { + const inst = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, originalWebviewParams: originalWebviewParamsExample, }); @@ -179,13 +178,12 @@ describe('BridgeToNative', () => { }); it('should save nextPageId in `nextPageId` property and send it into `AmNavigationAndTitle` constructor', () => { - const inst = new BridgeToNative(mockedHandleRedirect, { + const inst = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, title: 'Test', nextPageId: 7, }); - // removeItem expect(inst['nextPageId']).toBe(7); expect(MockedNativeNavigationAndTitleConstructor).toBeCalledWith( inst, @@ -196,13 +194,13 @@ describe('BridgeToNative', () => { }); it('should provide `NativeFallbacks` instance', () => { - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); expect(inst.nativeFallbacks).toEqual(mockedNativeFallbacksInstance); }); it('should provide `AmNavigationAndTitle` instance', () => { - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); expect(inst.nativeNavigationAndTitle).toEqual( mockedNativeNavigationAndTitleInstance, @@ -215,19 +213,19 @@ describe('BridgeToNative', () => { }); it('should provide `AndroidBridge` property', () => { - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); expect(inst.AndroidBridge).toEqual((window as WebViewWindow).Android); }); it('should set `environment` property correctly', () => { - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); expect(inst.environment).toBe('android'); }); it('should not provide application type using `iosAppId` property', () => { - const ins = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const ins = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); expect(ins.iosAppId).not.toBeDefined(); }); @@ -235,13 +233,13 @@ describe('BridgeToNative', () => { describe('iOS environment', () => { it('should not provide `AndroidBridge` property', () => { - const ins = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const ins = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); expect(ins.AndroidBridge).not.toBeDefined(); }); it('should set `environment` property correctly', () => { - const ins = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const ins = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); expect(ins.environment).toBe('ios'); }); @@ -259,7 +257,7 @@ describe('BridgeToNative', () => { ])( 'should detect app scheme for version %s correctly and save it in `iosAppId` property', (appVersion, expected) => { - const ins = new BridgeToNative(mockedHandleRedirect, { + const ins = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, appVersion, }); @@ -269,12 +267,12 @@ describe('BridgeToNative', () => { ); it('should use `iosAppId` parameter as value for `iosApplicationId` while parameter exists', () => { - const inst1 = new BridgeToNative(mockedHandleRedirect, { + const inst1 = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, appVersion: '0.0.0', iosAppId: 'kittycash', }); - const inst2 = new BridgeToNative(mockedHandleRedirect, { + const inst2 = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, appVersion: '12.22.0', iosAppId: 'kittycash', @@ -301,7 +299,7 @@ describe('BridgeToNative', () => { androidEnvFlag = true; } - const inst = new BridgeToNative(mockedHandleRedirect, { + const inst = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, appVersion, }); @@ -322,7 +320,7 @@ describe('BridgeToNative', () => { writable: true, }); - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); inst.closeWebview(); expect(window.location.href).toBe( @@ -350,7 +348,7 @@ describe('BridgeToNative', () => { ])( 'should compare current version `%s` with `%s` and return `%s`', (currentVersion, versionToCompare, result) => { - const inst = new BridgeToNative(mockedHandleRedirect, { + const inst = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, appVersion: currentVersion, @@ -365,7 +363,7 @@ describe('BridgeToNative', () => { it('should return `undefined` in Android environment', () => { androidEnvFlag = true; - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); expect(inst['getIosAppId']()).toBeUndefined(); expect(inst['getIosAppId']('aconcierge')).toBeUndefined(); @@ -387,7 +385,7 @@ describe('BridgeToNative', () => { ])( 'should detect app scheme for version `%s` as `%s` while parameter is not passed', (version, appId) => { - const inst = new BridgeToNative(mockedHandleRedirect, { + const inst = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, appVersion: version, }); @@ -397,7 +395,7 @@ describe('BridgeToNative', () => { ); it('should use app scheme from parameter', () => { - const inst = new BridgeToNative(mockedHandleRedirect, { + const inst = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, appVersion: '1.0.0', }); diff --git a/test/integration.test.ts b/test/integration.test.ts index 218f562..fe150b1 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -52,7 +52,7 @@ describe('BridgeToNative integration testing', () => { }); it('should use AM interface correctly when moving forward and then backward', async () => { - const inst = new BridgeToNative(mockedHandleRedirect, { + const inst = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, title: 'Initial Title', }); @@ -110,7 +110,7 @@ describe('BridgeToNative integration testing', () => { }); it('should act and use AM interface correctly when using `goBackAFewSteps`', async () => { - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); const mockedCloseWebview = jest.fn(); inst.closeWebview = mockedCloseWebview; @@ -140,7 +140,7 @@ describe('BridgeToNative integration testing', () => { }); it('should act and use AM interface correctly when using `setInitialView`', async () => { - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); const mockedCloseWebview = jest.fn(); inst.closeWebview = mockedCloseWebview; @@ -166,7 +166,7 @@ describe('BridgeToNative integration testing', () => { describe('iOS environment', () => { it('should use AM interface correctly when moving forward and then backward', async () => { - const inst = new BridgeToNative(mockedHandleRedirect, { + const inst = new BridgeToNative(mockedHandleRedirect, '/', { ...defaultAmParams, title: 'Initial Title', }); @@ -232,7 +232,7 @@ describe('BridgeToNative integration testing', () => { }); it('should act and use AM interface correctly when using `goBackAFewSteps`', async () => { - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); const mockedCloseWebview = jest.fn(); inst.closeWebview = mockedCloseWebview; @@ -264,7 +264,7 @@ describe('BridgeToNative integration testing', () => { }); it('should act and use AM interface correctly when using `setInitialView`', async () => { - const inst = new BridgeToNative(mockedHandleRedirect, defaultAmParams); + const inst = new BridgeToNative(mockedHandleRedirect, '/', defaultAmParams); const mockedCloseWebview = jest.fn(); inst.closeWebview = mockedCloseWebview; diff --git a/test/native-navigation-and-title.test.ts b/test/native-navigation-and-title.test.ts index d7b19ae..19678da 100644 --- a/test/native-navigation-and-title.test.ts +++ b/test/native-navigation-and-title.test.ts @@ -16,6 +16,9 @@ const mockedBridgeToAmInstance = { get environment() { return androidEnvFlag ? 'android' : 'ios'; }, + get _blankPagePath() { + return '/blank?reload=true'; + }, get originalWebviewParams() { return 'title=superTitle'; }, @@ -916,8 +919,10 @@ describe('AmNavigationAndTitle', () => { }); }); - describe('method `reloadPage`', () => { - it('should call `b2n.saveCurrentState` and location.reload', () => { + describe('method `pseudoReloadPage`', () => { + it('should call `handleRedirect` and `goBack`', () => { + const mockedGoBack = jest.fn(); + const inst = new NativeNavigationAndTitle( mockedBridgeToAmInstance, 44, @@ -925,10 +930,12 @@ describe('AmNavigationAndTitle', () => { mockedHandleRedirect, ); - inst.reloadPage(); + inst['goBack'] = mockedGoBack; + + inst.pseudoReloadPage(); - expect(inst['b2n']['saveCurrentState']).toBeCalled(); - expect(mockedLocationReload).toBeCalled(); + expect(mockedHandleRedirect).toBeCalledWith('blank', '', { reload: 'true' }); + expect(mockedGoBack).toBeCalled(); }); }); });