Repository navigation
test(SDK-617): add unit tests for IterableInboxDataModel #938
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 4 commits into
master
from
feature/SDK-617-tests-for-IterableInboxDataModel
Nov 8, 2026
+283
β0
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
01a6d09
test(SDK-617): add unit tests for IterableInboxDataModel
jferrao-itrbl 2969780
test(SDK-617): exercise default inbox sort in refresh tests
jferrao-itrbl 2edb7fd
Merge branch 'master' into feature/SDK-617-tests-for-IterableInboxDatβ¦
jferrao-itrbl e9f8536
test(SDK-617): address review on changelog, mocks, and assertions
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,283 @@ | ||
| import { MockRNIterableAPI } from '../../__mocks__/MockRNIterableAPI'; | ||
| import { IterableApi } from '../../core/classes/IterableApi'; | ||
| import { IterableHtmlInAppContent } from '../../inApp/classes/IterableHtmlInAppContent'; | ||
| import { IterableInAppMessage } from '../../inApp/classes/IterableInAppMessage'; | ||
| import { IterableInAppTrigger } from '../../inApp/classes/IterableInAppTrigger'; | ||
| import { IterableInAppTriggerType } from '../../inApp/enums/IterableInAppTriggerType'; | ||
| import { IterableInAppDeleteSource } from '../../inApp/enums/IterableInAppDeleteSource'; | ||
| import { IterableInAppLocation } from '../../inApp/enums/IterableInAppLocation'; | ||
| import { IterableInboxMetadata } from '../../inApp/classes/IterableInboxMetadata'; | ||
| import { type IterableInboxImpressionRowInfo } from '../types/IterableInboxImpressionRowInfo'; | ||
|
|
||
| import { IterableInboxDataModel } from './IterableInboxDataModel'; | ||
|
|
||
| jest.mock('../../api', () => ({ | ||
| __esModule: true, | ||
| default: MockRNIterableAPI, | ||
| })); | ||
|
|
||
| describe('IterableInboxDataModel', () => { | ||
| const makeMessage = ( | ||
| messageId: string, | ||
| createdAt: Date, | ||
| overrides?: Partial<{ | ||
| saveToInbox: boolean; | ||
| read: boolean; | ||
| title: string; | ||
| }> | ||
| ): IterableInAppMessage => | ||
| new IterableInAppMessage( | ||
| messageId, | ||
| 1, | ||
| new IterableInAppTrigger(IterableInAppTriggerType.immediate), | ||
| createdAt, | ||
| undefined, | ||
| overrides?.saveToInbox ?? true, | ||
| new IterableInboxMetadata( | ||
| overrides?.title ?? `title-${messageId}`, | ||
| 'sub', | ||
| 'https://example.com/icon.png' | ||
| ), | ||
| undefined, | ||
| overrides?.read ?? false, | ||
| 0 | ||
| ); | ||
|
|
||
| beforeEach(() => { | ||
| jest.clearAllMocks(); | ||
| jest | ||
| .spyOn(Date.prototype, 'toLocaleDateString') | ||
| .mockImplementation(function (this: Date) { | ||
| return `date-${this.toISOString()}`; | ||
| }); | ||
| jest | ||
| .spyOn(Date.prototype, 'toLocaleTimeString') | ||
| .mockImplementation(function (this: Date) { | ||
| return `time-${this.toISOString()}`; | ||
| }); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| jest.restoreAllMocks(); | ||
| }); | ||
|
|
||
| describe('refresh', () => { | ||
| it('should sort by most recent first when no comparator is set', async () => { | ||
| // GIVEN two messages with different createdAt values, returned older-first | ||
| const older = makeMessage('older', new Date('2024-01-01T10:00:00.000Z')); | ||
| const newer = makeMessage('newer', new Date('2024-02-01T10:00:00.000Z')); | ||
|
|
||
| const getInboxSpy = jest | ||
| .spyOn(IterableApi, 'getInboxMessages') | ||
| .mockResolvedValue([older, newer]); | ||
|
|
||
| const model = new IterableInboxDataModel(); | ||
|
|
||
| // WHEN refresh runs without set() (default sortByMostRecent) | ||
| const rows = await model.refresh(); | ||
|
|
||
| // THEN newer messages appear first regardless of API order | ||
| expect(getInboxSpy).toHaveBeenCalledTimes(1); | ||
| expect(rows.map((r) => r.inAppMessage.messageId)).toEqual([ | ||
| 'newer', | ||
| 'older', | ||
| ]); | ||
| expect(rows[0]?.inAppMessage).toBe(newer); | ||
| }); | ||
|
|
||
| it('should apply a filter with default most-recent sort when only a filter is set', async () => { | ||
| // GIVEN three messages and a filter; no custom comparator (default sort) | ||
| const oldest = makeMessage('a', new Date('2024-01-01T10:00:00.000Z')); | ||
| const mid = makeMessage('b', new Date('2024-02-01T10:00:00.000Z')); | ||
| const newest = makeMessage('c', new Date('2024-03-01T10:00:00.000Z')); | ||
| const hidden = makeMessage('hidden', new Date('2024-04-01T10:00:00.000Z')); | ||
|
|
||
| jest.spyOn(IterableApi, 'getInboxMessages').mockResolvedValue([ | ||
| oldest, | ||
| mid, | ||
| newest, | ||
| hidden, | ||
| ]); | ||
|
|
||
| const model = new IterableInboxDataModel(); | ||
| model.set((m) => m.messageId !== 'hidden'); | ||
|
|
||
| const rows = await model.refresh(); | ||
|
|
||
| expect(rows.map((r) => r.inAppMessage.messageId)).toEqual([ | ||
| 'c', | ||
| 'b', | ||
| 'a', | ||
| ]); | ||
| }); | ||
|
|
||
| it('should map inbox messages through filter and a custom comparator', async () => { | ||
| const older = makeMessage('older', new Date('2024-01-01T10:00:00.000Z'), { | ||
| read: true, | ||
| }); | ||
| const newer = makeMessage('newer', new Date('2024-02-01T10:00:00.000Z')); | ||
| const hidden = makeMessage('hidden', new Date('2024-03-01T10:00:00.000Z')); | ||
|
|
||
| const getInboxSpy = jest | ||
| .spyOn(IterableApi, 'getInboxMessages') | ||
| .mockResolvedValue([older, newer, hidden]); | ||
|
|
||
| const model = new IterableInboxDataModel(); | ||
| model.set( | ||
| (m) => m.messageId !== 'hidden', | ||
| // Reverse id order: the opposite of the default most-recent-first sort | ||
| (a, b) => b.messageId.localeCompare(a.messageId) | ||
| ); | ||
|
|
||
| // WHEN refresh runs | ||
| const rows = await model.refresh(); | ||
|
|
||
| // THEN the filter drops 'hidden' and the comparator replaces the default sort | ||
| expect(getInboxSpy).toHaveBeenCalledTimes(1); | ||
| expect(rows.map((r) => r.inAppMessage.messageId)).toEqual([ | ||
| 'older', | ||
| 'newer', | ||
| ]); | ||
| expect(rows[0]).toEqual({ | ||
| title: 'title-older', | ||
| subtitle: 'sub', | ||
| imageUrl: 'https://example.com/icon.png', | ||
| createdAt: older.createdAt, | ||
| read: true, | ||
| inAppMessage: older, | ||
| }); | ||
| expect(rows[0]?.inAppMessage).toBe(older); | ||
| }); | ||
|
|
||
| it('should resolve to an empty array when getInboxMessages rejects', async () => { | ||
| // GIVEN the inbox fetch fails | ||
| jest | ||
| .spyOn(IterableApi, 'getInboxMessages') | ||
| .mockRejectedValue(new Error('network error')); | ||
|
|
||
| const model = new IterableInboxDataModel(); | ||
|
|
||
| // WHEN refresh runs | ||
| const rows = await model.refresh(); | ||
|
|
||
| // THEN the model returns an empty list | ||
| expect(rows).toEqual([]); | ||
| }); | ||
| }); | ||
|
|
||
| describe('getFormattedDate', () => { | ||
| it('should use the default locale date and time string when no date mapper is set', () => { | ||
| // GIVEN a message with a creation time | ||
| const model = new IterableInboxDataModel(); | ||
| const message = makeMessage('m1', new Date('2024-06-15T12:00:00.000Z')); | ||
|
|
||
| // WHEN the date is formatted | ||
| const formatted = model.getFormattedDate(message); | ||
|
|
||
| // THEN the default mapper combines locale date and time for that instant | ||
| expect(formatted).toBe( | ||
| 'date-2024-06-15T12:00:00.000Z at time-2024-06-15T12:00:00.000Z' | ||
| ); | ||
| }); | ||
|
|
||
| it('should use the custom date mapper when set', () => { | ||
| // GIVEN a custom date mapper | ||
| const model = new IterableInboxDataModel(); | ||
| model.set(undefined, undefined, (m) => `custom-${m.messageId}`); | ||
| const message = makeMessage('m1', new Date('2024-06-15T12:00:00.000Z')); | ||
|
|
||
| // WHEN the date is formatted | ||
| const formatted = model.getFormattedDate(message); | ||
|
|
||
| // THEN the custom mapper output is returned | ||
| expect(formatted).toBe('custom-m1'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('IterableApi delegation', () => { | ||
| it('should call IterableApi.setReadForMessage when marking a message read', () => { | ||
| const spy = jest.spyOn(IterableApi, 'setReadForMessage').mockImplementation(); | ||
| const model = new IterableInboxDataModel(); | ||
|
|
||
| model.setMessageAsRead('msg-1'); | ||
|
|
||
| expect(spy).toHaveBeenCalledWith('msg-1', true); | ||
| }); | ||
|
|
||
| it('should call IterableApi.removeMessage when deleting an inbox item', () => { | ||
| const spy = jest.spyOn(IterableApi, 'removeMessage').mockImplementation(); | ||
| const model = new IterableInboxDataModel(); | ||
| const source = IterableInAppDeleteSource.inboxSwipe; | ||
|
|
||
| model.deleteItemById('msg-2', source); | ||
|
|
||
| expect(spy).toHaveBeenCalledWith( | ||
| 'msg-2', | ||
| IterableInAppLocation.inbox, | ||
| source | ||
| ); | ||
| }); | ||
|
|
||
| it('should call IterableApi.getHtmlInAppContentForMessage and wrap the result', async () => { | ||
| const raw = { | ||
| edgeInsets: { top: 1, left: 2, bottom: 3, right: 4 }, | ||
| html: '<p>hello</p>', | ||
| }; | ||
| const spy = jest | ||
| .spyOn(IterableApi, 'getHtmlInAppContentForMessage') | ||
| .mockResolvedValue(raw as never); | ||
|
|
||
| const model = new IterableInboxDataModel(); | ||
| const content = await model.getHtmlContentForMessageId('html-msg'); | ||
|
|
||
| expect(spy).toHaveBeenCalledWith('html-msg'); | ||
| expect(content).toBeInstanceOf(IterableHtmlInAppContent); | ||
| expect(content.html).toBe('<p>hello</p>'); | ||
| }); | ||
|
|
||
| it('should call IterableApi.startSession with visible rows', () => { | ||
| const spy = jest.spyOn(IterableApi, 'startSession').mockImplementation(); | ||
| const model = new IterableInboxDataModel(); | ||
| const rows: IterableInboxImpressionRowInfo[] = [ | ||
| { messageId: 'a', silentInbox: false }, | ||
| ]; | ||
|
|
||
| model.startSession(rows); | ||
|
|
||
| expect(spy).toHaveBeenCalledWith(rows); | ||
| }); | ||
|
|
||
| it('should call IterableApi.updateVisibleRows before IterableApi.endSession', async () => { | ||
| const updateSpy = jest | ||
| .spyOn(IterableApi, 'updateVisibleRows') | ||
| .mockImplementation(); | ||
| const endSpy = jest.spyOn(IterableApi, 'endSession').mockImplementation(); | ||
| const model = new IterableInboxDataModel(); | ||
| const rows: IterableInboxImpressionRowInfo[] = [ | ||
| { messageId: 'b', silentInbox: true }, | ||
| ]; | ||
|
|
||
| await model.endSession(rows); | ||
|
|
||
| expect(updateSpy).toHaveBeenCalledWith(rows); | ||
| expect(endSpy).toHaveBeenCalled(); | ||
| expect(updateSpy.mock.invocationCallOrder[0]).toBeLessThan( | ||
| endSpy.mock.invocationCallOrder[0]! | ||
| ); | ||
| }); | ||
|
|
||
| it('should call IterableApi.updateVisibleRows when updateVisibleRows is invoked', () => { | ||
| const spy = jest | ||
| .spyOn(IterableApi, 'updateVisibleRows') | ||
| .mockImplementation(); | ||
| const model = new IterableInboxDataModel(); | ||
| const rows: IterableInboxImpressionRowInfo[] = [ | ||
| { messageId: 'c', silentInbox: false }, | ||
| ]; | ||
|
|
||
| model.updateVisibleRows(rows); | ||
|
|
||
| expect(spy).toHaveBeenCalledWith(rows); | ||
| }); | ||
| }); | ||
| }); | ||
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.