Repository navigation
test(SDK-615): inbox message display URL handling #940
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jferrao-itrbl
merged 6 commits into
master
from
feature/SDK-615-tests-for-IterableInboxMessageDisplay
Nov 11, 2026
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
6f210e8
test(SDK-615): add IterableInboxMessageDisplay link handling tests
jferrao-itrbl 90f6c02
test(SDK-615): rely on global WebView mock in inbox display tests
jferrao-itrbl 5caf58e
docs(SDK-615): note iterable click tracking matches native
jferrao-itrbl d22c650
Merge branch 'master' into feature/SDK-615-tests-for-IterableInboxMesβ¦
jferrao-itrbl ac57135
fix(SDK-615): close inbox on unknown iterable:// after track
jferrao-itrbl 5c42a46
docs(SDK-615): clarify inbox link changelog and comment wording
jferrao-itrbl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
258 changes: 258 additions & 0 deletions
258
src/inbox/components/IterableInboxMessageDisplay.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,258 @@ | ||
| import { View } from 'react-native'; | ||
| import { | ||
| act, | ||
| fireEvent, | ||
| render, | ||
| type RenderResult, | ||
| waitFor, | ||
| } from '@testing-library/react-native'; | ||
|
|
||
| import { MockLinking } from '../../__mocks__/MockLinking'; | ||
| import { MockRNIterableAPI } from '../../__mocks__/MockRNIterableAPI'; | ||
| import { Iterable } from '../../core/classes/Iterable'; | ||
| import { IterableActionContext } from '../../core/classes/IterableActionContext'; | ||
| import { IterableConfig } from '../../core/classes/IterableConfig'; | ||
| import { IterableEdgeInsets } from '../../core/classes/IterableEdgeInsets'; | ||
| import { IterableActionSource } from '../../core/enums/IterableActionSource'; | ||
| import { IterableHtmlInAppContent } from '../../inApp/classes/IterableHtmlInAppContent'; | ||
| import { IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage'; | ||
| import { IterableInAppTrigger } from '../../inApp/classes/IterableInAppTrigger'; | ||
| import { IterableInboxMetadata } from '../../inApp/classes/IterableInboxMetadata'; | ||
| import { IterableInAppTriggerType } from '../../inApp/enums/IterableInAppTriggerType'; | ||
| import { IterableInAppCloseSource } from '../../inApp/enums/IterableInAppCloseSource'; | ||
| import { IterableInAppLocation } from '../../inApp/enums/IterableInAppLocation'; | ||
| import type { IterableInboxRowViewModel } from '../types'; | ||
| import { IterableInboxMessageDisplay } from './IterableInboxMessageDisplay'; | ||
|
|
||
| describe('IterableInboxMessageDisplay', () => { | ||
| const messageId = 'msg-1'; | ||
|
|
||
| const rowViewModel: IterableInboxRowViewModel = { | ||
| title: 'T', | ||
| read: false, | ||
| inAppMessage: new IterableInAppMessage( | ||
| messageId, | ||
| 1, | ||
| new IterableInAppTrigger(IterableInAppTriggerType.never), | ||
| undefined, | ||
| undefined, | ||
| true, | ||
| new IterableInboxMetadata('Hello', undefined, undefined), | ||
| undefined, | ||
| false, | ||
| 0 | ||
| ), | ||
| }; | ||
|
|
||
| const htmlContent = new IterableHtmlInAppContent( | ||
| new IterableEdgeInsets(0, 0, 0, 0), | ||
| '<html><body></body></html>' | ||
| ); | ||
|
|
||
| let deleteRow: jest.Mock; | ||
| let returnToInbox: jest.Mock; | ||
| let urlHandler: jest.Mock; | ||
| let customActionHandler: jest.Mock; | ||
| let trackInAppClickSpy: jest.SpyInstance; | ||
| let trackInAppCloseSpy: jest.SpyInstance; | ||
|
|
||
| beforeEach(() => { | ||
| deleteRow = jest.fn(); | ||
| returnToInbox = jest.fn((callback?: () => void) => { | ||
| callback?.(); | ||
| }); | ||
| urlHandler = jest.fn(); | ||
| customActionHandler = jest.fn(); | ||
|
|
||
| const config = new IterableConfig(); | ||
| config.urlHandler = urlHandler; | ||
| config.customActionHandler = customActionHandler; | ||
| Iterable.savedConfig = config; | ||
|
|
||
| trackInAppClickSpy = jest | ||
| .spyOn(Iterable, 'trackInAppClick') | ||
| .mockImplementation(() => {}); | ||
| trackInAppCloseSpy = jest | ||
| .spyOn(Iterable, 'trackInAppClose') | ||
| .mockImplementation(() => {}); | ||
|
|
||
| MockRNIterableAPI.trackInAppClick.mockClear(); | ||
| MockRNIterableAPI.trackInAppClose.mockClear(); | ||
| MockLinking.openURL.mockClear(); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| trackInAppClickSpy.mockRestore(); | ||
| trackInAppCloseSpy.mockRestore(); | ||
| Iterable.savedConfig = new IterableConfig(); | ||
| }); | ||
|
|
||
| async function renderWithContent(): Promise<RenderResult> { | ||
| const utils = render( | ||
| <IterableInboxMessageDisplay | ||
| rowViewModel={rowViewModel} | ||
| inAppContentPromise={Promise.resolve(htmlContent)} | ||
| returnToInbox={returnToInbox} | ||
| deleteRow={deleteRow} | ||
| contentWidth={320} | ||
| isPortrait | ||
| /> | ||
| ); | ||
| await act(async () => { | ||
| await Promise.resolve(); | ||
| }); | ||
| return utils; | ||
| } | ||
|
|
||
| /** Global setup mocks `WebView` as `View`; pick the node wired with `onMessage`. */ | ||
| function postWebViewMessage(utils: RenderResult, url: string) { | ||
| const candidates = utils.UNSAFE_getAllByType(View); | ||
| const webView = candidates.find( | ||
| (node) => typeof node.props.onMessage === 'function' | ||
| ); | ||
| expect(webView).toBeDefined(); | ||
| const onMessage = webView!.props.onMessage as ( | ||
| e: { nativeEvent: { data: string } } | ||
| ) => void; | ||
| onMessage({ nativeEvent: { data: url } }); | ||
| } | ||
|
|
||
| it('should open http(s) in the system browser and return to the inbox', async () => { | ||
| const utils = await renderWithContent(); | ||
| const url = 'https://example.com/path'; | ||
|
|
||
| postWebViewMessage(utils, url); | ||
|
|
||
| expect(trackInAppClickSpy).toHaveBeenCalledWith( | ||
| rowViewModel.inAppMessage, | ||
| IterableInAppLocation.inbox, | ||
| url | ||
| ); | ||
| expect(trackInAppCloseSpy).toHaveBeenCalledWith( | ||
| rowViewModel.inAppMessage, | ||
| IterableInAppLocation.inbox, | ||
| IterableInAppCloseSource.link, | ||
| url | ||
| ); | ||
| expect(returnToInbox).toHaveBeenCalled(); | ||
| expect(MockLinking.openURL).toHaveBeenCalledWith(url); | ||
| expect(deleteRow).not.toHaveBeenCalled(); | ||
| expect(urlHandler).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should delete the row and return to the inbox for iterable://delete', async () => { | ||
| const utils = await renderWithContent(); | ||
|
|
||
| postWebViewMessage(utils, 'iterable://delete'); | ||
|
|
||
| expect(deleteRow).toHaveBeenCalledWith(messageId); | ||
| expect(returnToInbox).toHaveBeenCalled(); | ||
| expect(urlHandler).not.toHaveBeenCalled(); | ||
| expect(customActionHandler).not.toHaveBeenCalled(); | ||
| expect(MockLinking.openURL).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should return to the inbox for iterable://dismiss without deleting', async () => { | ||
| const utils = await renderWithContent(); | ||
|
|
||
| postWebViewMessage(utils, 'iterable://dismiss'); | ||
|
|
||
| expect(returnToInbox).toHaveBeenCalled(); | ||
| expect(deleteRow).not.toHaveBeenCalled(); | ||
| expect(urlHandler).not.toHaveBeenCalled(); | ||
| expect(customActionHandler).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should invoke customActionHandler with the action type for action:// URLs', async () => { | ||
| const utils = await renderWithContent(); | ||
| const url = 'action://foo'; | ||
|
|
||
| postWebViewMessage(utils, url); | ||
|
|
||
| await waitFor(() => { | ||
| expect(customActionHandler).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| expect(customActionHandler).toHaveBeenCalledWith( | ||
| expect.objectContaining({ type: 'foo' }), | ||
| expect.any(IterableActionContext) | ||
| ); | ||
| const [, context] = customActionHandler.mock.calls[0]; | ||
| expect(context.source).toBe(IterableActionSource.inApp); | ||
| expect(returnToInbox).toHaveBeenCalled(); | ||
| expect(urlHandler).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should close the message for unknown iterable:// URLs without calling any handler', async () => { | ||
| const utils = await renderWithContent(); | ||
| const url = 'iterable://foo'; | ||
|
|
||
| postWebViewMessage(utils, url); | ||
|
|
||
| expect(returnToInbox).toHaveBeenCalledTimes(1); | ||
| expect(trackInAppClickSpy).toHaveBeenCalledTimes(1); | ||
| expect(trackInAppCloseSpy).toHaveBeenCalledTimes(1); | ||
| expect(trackInAppCloseSpy).toHaveBeenCalledWith( | ||
| rowViewModel.inAppMessage, | ||
| IterableInAppLocation.inbox, | ||
| IterableInAppCloseSource.link, | ||
| url | ||
| ); | ||
| expect(urlHandler).not.toHaveBeenCalled(); | ||
| expect(customActionHandler).not.toHaveBeenCalled(); | ||
| expect(MockLinking.openURL).not.toHaveBeenCalled(); | ||
| expect(deleteRow).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should pass other URLs to urlHandler', async () => { | ||
| const utils = await renderWithContent(); | ||
| const url = 'myapp://screen'; | ||
|
|
||
| postWebViewMessage(utils, url); | ||
|
|
||
| await waitFor(() => { | ||
| expect(urlHandler).toHaveBeenCalledWith(url, expect.any(IterableActionContext)); | ||
| }); | ||
| expect(returnToInbox).toHaveBeenCalled(); | ||
| expect(MockLinking.openURL).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('should forward click tracking to the native bridge', async () => { | ||
| trackInAppClickSpy.mockRestore(); | ||
| trackInAppCloseSpy.mockRestore(); | ||
|
|
||
| const utils = await renderWithContent(); | ||
| const url = 'https://example.com/x'; | ||
|
|
||
| postWebViewMessage(utils, url); | ||
|
|
||
| expect(MockRNIterableAPI.trackInAppClick).toHaveBeenCalledWith( | ||
| messageId, | ||
| IterableInAppLocation.inbox, | ||
| url | ||
| ); | ||
| expect(MockRNIterableAPI.trackInAppClose).toHaveBeenCalledWith( | ||
| messageId, | ||
| IterableInAppLocation.inbox, | ||
| IterableInAppCloseSource.link, | ||
| url | ||
| ); | ||
| }); | ||
|
|
||
| it('should track close when the back button is pressed', async () => { | ||
| trackInAppClickSpy.mockRestore(); | ||
| trackInAppCloseSpy.mockRestore(); | ||
|
|
||
| const { getByText } = await renderWithContent(); | ||
|
|
||
| fireEvent.press(getByText('Inbox')); | ||
|
|
||
| expect(returnToInbox).toHaveBeenCalled(); | ||
| expect(MockRNIterableAPI.trackInAppClose).toHaveBeenCalledWith( | ||
| messageId, | ||
| IterableInAppLocation.inbox, | ||
| IterableInAppCloseSource.back, | ||
| undefined | ||
| ); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.