diff --git a/app/actions/local/thread.ts b/app/actions/local/thread.ts index 6fcfe01a0..f256b9966 100644 --- a/app/actions/local/thread.ts +++ b/app/actions/local/thread.ts @@ -13,7 +13,7 @@ import {getCurrentTeamId, getCurrentUserId, prepareCommonSystemValues, type Prep import {addChannelToTeamHistory, addTeamToTeamHistory} from '@queries/servers/team'; import {getThreadById, prepareThreadsFromReceivedPosts, queryThreadsInTeam} from '@queries/servers/thread'; import {getCurrentUser} from '@queries/servers/user'; -import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllOverlays, goToScreen} from '@screens/navigation'; +import {dismissAllModals, dismissAllModalsAndPopToRoot, dismissAllOverlaysWithExceptions, goToScreen} from '@screens/navigation'; import EphemeralStore from '@store/ephemeral_store'; import NavigationStore from '@store/navigation_store'; import {isTablet} from '@utils/helpers'; @@ -95,7 +95,7 @@ export const switchToThread = async (serverUrl: string, rootId: string, isFromNo if (isFromNotification) { if (currentThreadId && currentThreadId === rootId && NavigationStore.getScreensInStack().includes(Screens.THREAD)) { await dismissAllModals(); - await dismissAllOverlays(); + await dismissAllOverlaysWithExceptions(); return {}; } diff --git a/app/screens/navigation.test.ts b/app/screens/navigation.test.ts index 1cf57486c..1f128f108 100644 --- a/app/screens/navigation.test.ts +++ b/app/screens/navigation.test.ts @@ -6,11 +6,16 @@ import {Navigation} from 'react-native-navigation'; import {Events, Preferences, Screens} from '@constants'; import NavigationStore from '@store/navigation_store'; -import {openAsBottomSheet} from './navigation'; +jest.unmock('@screens/navigation'); + +import * as navigationModule from './navigation'; +import {dismissAllOverlaysWithExceptions} from './navigation'; import type {FirstArgument} from '@typings/utils/utils'; import type {IntlShape} from 'react-intl'; +const {registerNavigationListeners} = navigationModule; + function expectShowModalCalledWith(screen: string, title: string, props?: Record) { expect(Navigation.showModal).toHaveBeenCalledWith({ stack: { @@ -33,7 +38,7 @@ function expectShowModalOverCurrentContext(screen: string, props?: Record, isTablet: boolean) { +function expectOpenAsBottomSheetCalledWith(props: FirstArgument, isTablet: boolean) { if (isTablet) { expectShowModalCalledWith(props.screen, props.title, {closeButtonId: props.closeButtonId, ...props.props}); } else { @@ -107,3 +112,373 @@ describe('openUserProfileModal', () => { }, false); }); }); + +describe('overlay command listeners', () => { + let mockCommandListener: jest.Mock; + + beforeEach(() => { + jest.clearAllMocks(); + NavigationStore.reset(); + + mockCommandListener = jest.fn(); + const mockEvents = { + registerScreenPoppedListener: jest.fn(() => ({remove: jest.fn()})), + registerCommandListener: jest.fn((listener) => { + mockCommandListener = listener; + return {remove: jest.fn()}; + }), + registerComponentWillAppearListener: jest.fn(() => ({remove: jest.fn()})), + }; + (Navigation.events as jest.Mock).mockReturnValue(mockEvents); + + registerNavigationListeners(); + }); + + it('should call NavigationStore.addOverlayToStack when showOverlay command is received', () => { + const overlayId = 'test-overlay-id'; + const params = {layout: {id: overlayId}}; + + expect(NavigationStore.getOverlaysInStack()).toEqual([]); + + mockCommandListener('showOverlay', params); + + expect(NavigationStore.getOverlaysInStack()).toEqual([overlayId]); + }); + + it('should call NavigationStore.removeOverlayFromStack when dismissOverlay command is received', () => { + const overlayId = 'test-overlay-id'; + NavigationStore.addOverlayToStack(overlayId); + NavigationStore.addOverlayToStack('another-overlay'); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['another-overlay', overlayId]); + + mockCommandListener('dismissOverlay', {componentId: overlayId}); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['another-overlay']); + }); + + it('should call NavigationStore.removeAllOverlaysFromStack when dismissAllOverlays command is received', () => { + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addOverlayToStack('floating-banner-overlay'); + NavigationStore.addOverlayToStack('overlay2'); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['overlay2', 'floating-banner-overlay', 'overlay1']); + + mockCommandListener('dismissAllOverlays', {}); + + expect(NavigationStore.getOverlaysInStack()).toEqual([]); + }); + + it('should not affect NavigationStore when unrecognized commands are received', () => { + NavigationStore.addOverlayToStack('test-overlay'); + const initialOverlays = NavigationStore.getOverlaysInStack(); + + mockCommandListener('someOtherCommand', {}); + + expect(NavigationStore.getOverlaysInStack()).toEqual(initialOverlays); + }); +}); + +describe('showOverlay', () => { + beforeEach(() => { + jest.clearAllMocks(); + NavigationStore.reset(); + }); + + it('should call Navigation.showOverlay with correct parameters', () => { + const screenName = Screens.USER_PROFILE; + const passProps = {userId: 'user123'}; + const options = {overlay: {interceptTouchOutside: true}}; + const id = 'custom-overlay-id'; + + navigationModule.showOverlay(screenName, passProps, options, id); + + expect(Navigation.showOverlay).toHaveBeenCalledWith({ + component: { + id: 'custom-overlay-id', + name: screenName, + passProps, + options: expect.objectContaining({ + layout: { + backgroundColor: 'transparent', + componentBackgroundColor: 'transparent', + }, + overlay: { + interceptTouchOutside: true, + }, + }), + }, + }); + }); + + it('should use default options when none provided', () => { + const screenName = Screens.USER_PROFILE; + + navigationModule.showOverlay(screenName); + + expect(Navigation.showOverlay).toHaveBeenCalledWith({ + component: { + id: undefined, + name: screenName, + passProps: {}, + options: { + layout: { + backgroundColor: 'transparent', + componentBackgroundColor: 'transparent', + }, + overlay: { + interceptTouchOutside: false, + }, + }, + }, + }); + }); + + it('should merge custom options with default options correctly', () => { + const screenName = Screens.USER_PROFILE; + const customOptions = { + layout: {backgroundColor: 'red'}, + overlay: {interceptTouchOutside: true}, + customProperty: 'test', + }; + + navigationModule.showOverlay(screenName, {}, customOptions); + + expect(Navigation.showOverlay).toHaveBeenCalledWith({ + component: { + id: undefined, + name: screenName, + passProps: {}, + options: expect.objectContaining({ + layout: { + backgroundColor: 'red', + componentBackgroundColor: 'transparent', + }, + overlay: { + interceptTouchOutside: true, + }, + customProperty: 'test', + }), + }, + }); + }); +}); + +describe('dismissOverlay', () => { + beforeEach(() => { + jest.clearAllMocks(); + NavigationStore.reset(); + }); + + it('should call Navigation.dismissOverlay with correct componentId', async () => { + const componentId = 'overlay-123'; + + await navigationModule.dismissOverlay(componentId); + + expect(Navigation.dismissOverlay).toHaveBeenCalledWith(componentId); + }); + + it('should handle Navigation.dismissOverlay rejection gracefully', async () => { + const componentId = 'non-existent-overlay'; + + (Navigation.dismissOverlay as jest.Mock).mockRejectedValueOnce(new Error('Overlay not found')); + + await expect(navigationModule.dismissOverlay(componentId)).resolves.not.toThrow(); + + expect(Navigation.dismissOverlay).toHaveBeenCalledWith(componentId); + }); +}); + +describe('dismissAllOverlays', () => { + beforeEach(() => { + jest.clearAllMocks(); + NavigationStore.reset(); + }); + + it('should call Navigation.dismissAllOverlays', async () => { + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addOverlayToStack('floating-banner-overlay'); + NavigationStore.addOverlayToStack('overlay2'); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['overlay2', 'floating-banner-overlay', 'overlay1']); + + await navigationModule.dismissAllOverlays(); + + expect(Navigation.dismissAllOverlays).toHaveBeenCalledTimes(1); + expect(Navigation.dismissOverlay).not.toHaveBeenCalled(); + }); + + it('should handle empty overlay stack gracefully', async () => { + expect(NavigationStore.getOverlaysInStack()).toEqual([]); + + await navigationModule.dismissAllOverlays(); + + expect(Navigation.dismissAllOverlays).toHaveBeenCalledTimes(1); + }); + + it('should handle only exception overlays in stack', async () => { + NavigationStore.addOverlayToStack('floating-banner-overlay'); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['floating-banner-overlay']); + + await navigationModule.dismissAllOverlays(); + + expect(Navigation.dismissAllOverlays).toHaveBeenCalledTimes(1); + }); + + it('should handle Navigation.dismissAllOverlays rejection gracefully', async () => { + (Navigation.dismissAllOverlays as jest.Mock).mockRejectedValueOnce(new Error('Dismiss failed')); + + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addOverlayToStack('overlay2'); + + await expect(navigationModule.dismissAllOverlays()).resolves.not.toThrow(); + + expect(Navigation.dismissAllOverlays).toHaveBeenCalledTimes(1); + }); + + it('should call Navigation.dismissAllOverlays once regardless of overlay count', async () => { + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addOverlayToStack('overlay2'); + NavigationStore.addOverlayToStack('overlay3'); + + await navigationModule.dismissAllOverlays(); + + expect(Navigation.dismissAllOverlays).toHaveBeenCalledTimes(1); + expect(Navigation.dismissOverlay).not.toHaveBeenCalled(); + }); +}); + +describe('dismissAllModalsAndPopToRoot', () => { + beforeEach(() => { + jest.clearAllMocks(); + NavigationStore.reset(); + }); + + it('should call dismissAllModals, dismissAllOverlaysWithExceptions, and popToRoot in sequence', async () => { + NavigationStore.addModalToStack('modal1'); + NavigationStore.addScreenToStack('screen1'); + + await navigationModule.dismissAllModalsAndPopToRoot(); + + expect(Navigation.dismissModal).toHaveBeenCalledTimes(1); + expect(Navigation.dismissModal).toHaveBeenCalledWith('modal1', {animations: {dismissModal: {enabled: false}}}); + expect(Navigation.popToRoot).toHaveBeenCalledTimes(1); + expect(Navigation.popToRoot).toHaveBeenCalledWith('screen1'); + }); + + it('should dismiss non-exception overlays via dismissAllOverlaysWithExceptions', async () => { + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addOverlayToStack('floating-banner-overlay'); + NavigationStore.addOverlayToStack('overlay2'); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['overlay2', 'floating-banner-overlay', 'overlay1']); + + await navigationModule.dismissAllModalsAndPopToRoot(); + + expect(Navigation.dismissOverlay).toHaveBeenCalledTimes(2); + expect(Navigation.dismissOverlay).toHaveBeenCalledWith('overlay2'); + expect(Navigation.dismissOverlay).toHaveBeenCalledWith('overlay1'); + expect(Navigation.dismissOverlay).not.toHaveBeenCalledWith('floating-banner-overlay'); + }); + + it('should handle empty overlay stack gracefully', async () => { + expect(NavigationStore.getOverlaysInStack()).toEqual([]); + NavigationStore.addScreenToStack('screen1'); + + await navigationModule.dismissAllModalsAndPopToRoot(); + + expect(Navigation.dismissModal).not.toHaveBeenCalled(); + expect(Navigation.dismissOverlay).not.toHaveBeenCalled(); + expect(Navigation.popToRoot).toHaveBeenCalledTimes(1); + expect(Navigation.popToRoot).toHaveBeenCalledWith('screen1'); + }); + + it('should continue with popToRoot even if overlay dismissal fails', async () => { + (Navigation.dismissOverlay as jest.Mock).mockRejectedValueOnce(new Error('Dismiss failed')); + + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addScreenToStack('screen1'); + + await expect(navigationModule.dismissAllModalsAndPopToRoot()).resolves.not.toThrow(); + + expect(Navigation.dismissModal).not.toHaveBeenCalled(); + expect(Navigation.dismissOverlay).toHaveBeenCalledWith('overlay1'); + expect(Navigation.popToRoot).toHaveBeenCalledTimes(1); + expect(Navigation.popToRoot).toHaveBeenCalledWith('screen1'); + }); +}); + +describe('dismissAllOverlaysWithExceptions', () => { + beforeEach(() => { + jest.clearAllMocks(); + NavigationStore.reset(); + }); + + it('should dismiss non-exception overlays individually', async () => { + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addOverlayToStack('floating-banner-overlay'); + NavigationStore.addOverlayToStack('overlay2'); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['overlay2', 'floating-banner-overlay', 'overlay1']); + + await dismissAllOverlaysWithExceptions(); + + expect(Navigation.dismissOverlay).toHaveBeenCalledTimes(2); + expect(Navigation.dismissOverlay).toHaveBeenCalledWith('overlay2'); + expect(Navigation.dismissOverlay).toHaveBeenCalledWith('overlay1'); + expect(Navigation.dismissOverlay).not.toHaveBeenCalledWith('floating-banner-overlay'); + }); + + it('should handle empty overlay stack gracefully', async () => { + expect(NavigationStore.getOverlaysInStack()).toEqual([]); + + await dismissAllOverlaysWithExceptions(); + + expect(Navigation.dismissOverlay).not.toHaveBeenCalled(); + }); + + it('should handle only exception overlays in stack', async () => { + NavigationStore.addOverlayToStack('floating-banner-overlay'); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['floating-banner-overlay']); + + await dismissAllOverlaysWithExceptions(); + + expect(Navigation.dismissOverlay).not.toHaveBeenCalled(); + }); + + it('should use Promise.all for concurrent dismissals', async () => { + const dismissalOrder: string[] = []; + (Navigation.dismissOverlay as jest.Mock).mockImplementation((overlayId: string) => { + dismissalOrder.push(overlayId); + return Promise.resolve(); + }); + + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addOverlayToStack('overlay2'); + NavigationStore.addOverlayToStack('overlay3'); + + await dismissAllOverlaysWithExceptions(); + + expect(Navigation.dismissOverlay).toHaveBeenCalledTimes(3); + expect(dismissalOrder).toContain('overlay1'); + expect(dismissalOrder).toContain('overlay2'); + expect(dismissalOrder).toContain('overlay3'); + }); + + it('should continue dismissing even if one overlay dismissal fails', async () => { + (Navigation.dismissOverlay as jest.Mock). + mockImplementationOnce(() => Promise.reject(new Error('Dismiss failed'))). + mockImplementationOnce(() => Promise.resolve()); + + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addOverlayToStack('overlay2'); + + await expect(dismissAllOverlaysWithExceptions()).resolves.not.toThrow(); + + expect(Navigation.dismissOverlay).toHaveBeenCalledTimes(2); + expect(Navigation.dismissOverlay).toHaveBeenCalledWith('overlay2'); + expect(Navigation.dismissOverlay).toHaveBeenCalledWith('overlay1'); + }); +}); diff --git a/app/screens/navigation.ts b/app/screens/navigation.ts index 2fd6de665..5311d8918 100644 --- a/app/screens/navigation.ts +++ b/app/screens/navigation.ts @@ -76,6 +76,16 @@ function onCommandListener(name: string, params: any) { case 'dismissModal': NavigationStore.removeModalFromStack(params.componentId); break; + case 'showOverlay': + NavigationStore.addOverlayToStack(params?.layout?.id); + break; + case 'dismissOverlay': + NavigationStore.removeOverlayFromStack(params?.componentId); + break; + case 'dismissAllOverlays': { + NavigationStore.removeAllOverlaysFromStack(); + break; + } } if (NavigationStore.getVisibleScreen() === Screens.HOME) { @@ -553,7 +563,7 @@ export async function popToRoot() { export async function dismissAllModalsAndPopToRoot() { await dismissAllModals(); - await dismissAllOverlays(); + await dismissAllOverlaysWithExceptions(); await popToRoot(); } @@ -567,7 +577,7 @@ export async function dismissAllModalsAndPopToRoot() { */ export async function dismissAllModalsAndPopToScreen(screenId: AvailableScreens, title: string, passProps = {}, options = {}) { await dismissAllModals(); - await dismissAllOverlays(); + await dismissAllOverlaysWithExceptions(); if (NavigationStore.getScreensInStack().includes(screenId)) { let mergeOptions = options; if (title) { @@ -791,6 +801,28 @@ export async function dismissOverlay(componentId: string) { } } +/** + * Instead of using native dismissAllOverlays, we're looping through the overlays + * and dismissing them individually. Native dismissAllOverlays is causing the app to + * dismiss 700+ overlays. Even though those overlays doesn't exist in the stack. Since we're + * tracking the overlays in the store, we can dismiss them individually. + * @returns + */ +export async function dismissAllOverlaysWithExceptions() { + try { + const overlaysToRemove = NavigationStore.getAllOverlaysOtherThanExceptions(); + if (!overlaysToRemove.length) { + return; + } + + await Promise.all(overlaysToRemove.map((overlayId) => + Navigation.dismissOverlay(overlayId).catch(() => undefined), + )); + } catch { + // do nothing + } +} + export async function dismissAllOverlays() { try { await Navigation.dismissAllOverlays(); @@ -912,3 +944,4 @@ export async function openUserProfileModal( Keyboard.dismiss(); openAsBottomSheet({screen, title, theme, closeButtonId, props: {...props}}); } + diff --git a/app/store/navigation_store.test.ts b/app/store/navigation_store.test.ts index 4a043ce9a..b69f71f33 100644 --- a/app/store/navigation_store.test.ts +++ b/app/store/navigation_store.test.ts @@ -113,6 +113,77 @@ describe('NavigationStore', () => { }); }); + describe('overlay management', () => { + it('should add and remove overlays correctly', () => { + NavigationStore.addOverlayToStack('test-overlay'); + NavigationStore.addOverlayToStack('another-overlay'); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['another-overlay', 'test-overlay']); + }); + + it('should handle duplicate overlay additions', () => { + NavigationStore.addOverlayToStack('test-overlay'); + NavigationStore.addOverlayToStack('test-overlay'); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['test-overlay']); + }); + + it('should remove specific overlays from stack', () => { + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addOverlayToStack('overlay2'); + NavigationStore.addOverlayToStack('overlay3'); + + NavigationStore.removeOverlayFromStack('overlay2'); + + expect(NavigationStore.getOverlaysInStack()).toEqual(['overlay3', 'overlay1']); + }); + + it('should return overlays other than exceptions', () => { + NavigationStore.addOverlayToStack('regular-overlay'); + NavigationStore.addOverlayToStack('floating-banner-overlay'); + NavigationStore.addOverlayToStack('another-overlay'); + + const overlaysToRemove = NavigationStore.getAllOverlaysOtherThanExceptions(); + + expect(overlaysToRemove).toEqual(['another-overlay', 'regular-overlay']); + expect(overlaysToRemove).not.toContain('floating-banner-overlay'); + }); + + it('should return empty array when only exception overlays exist', () => { + NavigationStore.addOverlayToStack('floating-banner-overlay'); + + const overlaysToRemove = NavigationStore.getAllOverlaysOtherThanExceptions(); + + expect(overlaysToRemove).toEqual([]); + }); + + it('should return all overlays when no exceptions exist in stack', () => { + NavigationStore.addOverlayToStack('overlay1'); + NavigationStore.addOverlayToStack('overlay2'); + + const overlaysToRemove = NavigationStore.getAllOverlaysOtherThanExceptions(); + + expect(overlaysToRemove).toEqual(['overlay2', 'overlay1']); + }); + + it('should handle empty overlay stack gracefully when getting overlays to remove', () => { + const overlaysToRemove = NavigationStore.getAllOverlaysOtherThanExceptions(); + expect(overlaysToRemove).toEqual([]); + expect(NavigationStore.getOverlaysInStack()).toEqual([]); + }); + + it('should maintain overlay order when filtering', () => { + NavigationStore.addOverlayToStack('first-overlay'); + NavigationStore.addOverlayToStack('floating-banner-overlay'); + NavigationStore.addOverlayToStack('second-overlay'); + NavigationStore.addOverlayToStack('third-overlay'); + + const overlaysToRemove = NavigationStore.getAllOverlaysOtherThanExceptions(); + + expect(overlaysToRemove).toEqual(['third-overlay', 'second-overlay', 'first-overlay']); + }); + }); + describe('screen loading and visibility', () => { it('should handle waitUntilScreenHasLoaded', async () => { const promise = NavigationStore.waitUntilScreenHasLoaded(Screens.ABOUT); diff --git a/app/store/navigation_store.ts b/app/store/navigation_store.ts index 60b15f7fe..cb7733378 100644 --- a/app/store/navigation_store.ts +++ b/app/store/navigation_store.ts @@ -5,9 +5,12 @@ import {BehaviorSubject} from 'rxjs'; import type {AvailableScreens} from '@typings/screens/navigation'; +const OVERLAY_EXCEPTIONS = new Set(['floating-banner-overlay']); + class NavigationStoreSingleton { private screensInStack: AvailableScreens[] = []; private modalsInStack: AvailableScreens[] = []; + private overlaysInStack: string[] = []; private visibleTab = 'Home'; private tosOpen = false; @@ -20,6 +23,7 @@ class NavigationStoreSingleton { reset = () => { this.screensInStack = []; this.modalsInStack = []; + this.overlaysInStack = []; this.visibleTab = 'Home'; this.tosOpen = false; this.subject.next(undefined); @@ -85,6 +89,28 @@ class NavigationStoreSingleton { } }; + addOverlayToStack = (overlayId: string) => { + this.removeOverlayFromStack(overlayId); + this.overlaysInStack.unshift(overlayId); + }; + + removeOverlayFromStack = (overlayId: string) => { + const index = this.overlaysInStack.indexOf(overlayId); + if (index > -1) { + this.overlaysInStack.splice(index, 1); + } + }; + + getOverlaysInStack = () => this.overlaysInStack; + + removeAllOverlaysFromStack = () => { + this.overlaysInStack = []; + }; + + getAllOverlaysOtherThanExceptions = () => { + return this.overlaysInStack.filter((overlayId) => !OVERLAY_EXCEPTIONS.has(overlayId)); + }; + setToSOpen = (open: boolean) => { this.tosOpen = open; }; diff --git a/test/setup.ts b/test/setup.ts index 2f73f49f9..a92e216eb 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -1,8 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -/* eslint-disable react/no-multi-comp */ - import {setGenerator} from '@nozbe/watermelondb/utils/common/randomId'; import * as ReactNative from 'react-native'; import mockSafeAreaContext from 'react-native-safe-area-context/jest/mock'; @@ -342,22 +340,23 @@ jest.mock('react-native-navigation', () => { RNN.Navigation.setLazyComponentRegistrator = jest.fn(); RNN.Navigation.setDefaultOptions = jest.fn(); RNN.Navigation.registerComponent = jest.fn(); + + const mockSubscription = {remove: jest.fn()}; + const createMockEvents = () => ({ + registerAppLaunchedListener: jest.fn(() => mockSubscription), + registerComponentListener: jest.fn(() => mockSubscription), + bindComponent: jest.fn(() => mockSubscription), + registerNavigationButtonPressedListener: jest.fn(() => mockSubscription), + registerScreenPoppedListener: jest.fn(() => mockSubscription), + registerCommandListener: jest.fn(() => mockSubscription), + registerComponentWillAppearListener: jest.fn(() => mockSubscription), + }); + return { ...RNN, Navigation: { ...RNN.Navigation, - events: () => ({ - registerAppLaunchedListener: jest.fn(), - registerComponentListener: jest.fn(() => { - return {remove: jest.fn()}; - }), - bindComponent: jest.fn(() => { - return {remove: jest.fn()}; - }), - registerNavigationButtonPressedListener: jest.fn(() => { - return {remove: jest.fn()}; - }), - }), + events: jest.fn(createMockEvents), setRoot: jest.fn(), pop: jest.fn(), push: jest.fn(), @@ -367,7 +366,8 @@ jest.mock('react-native-navigation', () => { popToRoot: jest.fn(), mergeOptions: jest.fn(), showOverlay: jest.fn(), - dismissOverlay: jest.fn(), + dismissOverlay: jest.fn(() => Promise.resolve()), + dismissAllOverlays: jest.fn(() => Promise.resolve()), updateProps: jest.fn(), }, }; @@ -395,7 +395,7 @@ jest.mock('react-native-notifications', () => { ios: { getDeliveredNotifications: jest.fn().mockImplementation(() => Promise.resolve(deliveredNotifications)), removeDeliveredNotifications: jest.fn((ids) => { - // eslint-disable-next-line + // @ts-ignore deliveredNotifications = deliveredNotifications.filter((n) => !ids.includes(n.identifier)); }),