diff --git a/packages/browser/src/plugins/in-app-plugin/__tests__/inbox_messages.test.ts b/packages/browser/src/plugins/in-app-plugin/__tests__/inbox_messages.test.ts new file mode 100644 index 000000000..474ae81e0 --- /dev/null +++ b/packages/browser/src/plugins/in-app-plugin/__tests__/inbox_messages.test.ts @@ -0,0 +1,158 @@ +import { Analytics } from '../../../core/analytics' +import { createInboxAPI, GistInboxMessage } from '../inbox_messages' + +function makeMessage( + overrides: Partial = {} +): GistInboxMessage { + return { + messageType: 'inline', + expiry: '', + priority: 0, + type: 'test', + properties: {}, + queueId: `msg-${Math.random().toString(36).slice(2)}`, + userToken: 'user-1', + deliveryId: 'delivery-1', + sentAt: new Date().toISOString(), + opened: false, + ...overrides, + } +} + +function createMockGist(messages: GistInboxMessage[]) { + const listeners: Record = {} + return { + getInboxMessages: jest.fn().mockResolvedValue(messages), + updateInboxMessageOpenState: jest.fn(), + removeInboxMessage: jest.fn(), + events: { + on: jest.fn((event: string, cb: Function) => { + if (!listeners[event]) listeners[event] = [] + listeners[event].push(cb) + }), + off: jest.fn(), + emit: (event: string, data: any) => { + ;(listeners[event] || []).forEach((cb) => cb(data)) + }, + }, + } +} + +describe('Inbox _cio topic filtering', () => { + const analytics = { track: jest.fn() } as unknown as Analytics + + const regularMessage = makeMessage({ + queueId: 'regular', + topics: ['news'], + }) + const noTopicMessage = makeMessage({ + queueId: 'no-topic', + topics: [], + }) + const cioMessage = makeMessage({ + queueId: 'cio-msg', + topics: ['_cio_inbox_5'], + }) + const mixedMessage = makeMessage({ + queueId: 'mixed-msg', + topics: ['news', '_cio_feed'], + }) + + const allMessages = [regularMessage, noTopicMessage, cioMessage, mixedMessage] + + describe('no topics specified', () => { + it('should return messages without _cio topics', async () => { + const gist = createMockGist(allMessages) + const inbox = createInboxAPI(analytics, gist, []) + + const messages = await inbox.messages() + const ids = messages.map((m) => m.messageId) + + expect(ids).toContain('regular') + expect(ids).toContain('no-topic') + expect(ids).not.toContain('cio-msg') + expect(ids).not.toContain('mixed-msg') + }) + + it('should not count _cio messages in total', async () => { + const gist = createMockGist(allMessages) + const inbox = createInboxAPI(analytics, gist, []) + + expect(await inbox.total()).toBe(2) + }) + + it('should not count _cio messages in totalUnopened', async () => { + const gist = createMockGist(allMessages) + const inbox = createInboxAPI(analytics, gist, []) + + expect(await inbox.totalUnopened()).toBe(2) + }) + + it('should filter _cio messages in onUpdates', async () => { + const gist = createMockGist(allMessages) + const inbox = createInboxAPI(analytics, gist, []) + + const result = await new Promise((resolve) => { + inbox.onUpdates((messages) => resolve(messages)) + gist.events.emit('messageInboxUpdated', allMessages) + }) + + const ids = result.map((m) => m.messageId) + expect(ids).toContain('regular') + expect(ids).toContain('no-topic') + expect(ids).not.toContain('cio-msg') + expect(ids).not.toContain('mixed-msg') + }) + }) + + describe('non-_cio topic specified', () => { + it('should return matching messages but exclude _cio messages', async () => { + const gist = createMockGist(allMessages) + const inbox = createInboxAPI(analytics, gist, ['news']) + + const messages = await inbox.messages() + const ids = messages.map((m) => m.messageId) + + expect(ids).toContain('regular') + expect(ids).not.toContain('no-topic') + expect(ids).not.toContain('cio-msg') + // mixed-msg has 'news' but also has '_cio_feed', should be excluded + expect(ids).not.toContain('mixed-msg') + }) + + it('should not count _cio messages in total', async () => { + const gist = createMockGist(allMessages) + const inbox = createInboxAPI(analytics, gist, ['news']) + + expect(await inbox.total()).toBe(1) + }) + }) + + describe('_cio topic explicitly specified', () => { + it('should return _cio messages when explicitly requested', async () => { + const gist = createMockGist(allMessages) + const inbox = createInboxAPI(analytics, gist, ['_cio_inbox_5']) + + const messages = await inbox.messages() + const ids = messages.map((m) => m.messageId) + + expect(ids).toContain('cio-msg') + expect(ids).not.toContain('mixed-msg') + expect(ids).not.toContain('regular') + expect(ids).not.toContain('no-topic') + }) + + it('should return _cio messages when a _cio topic is requested alongside others', async () => { + const gist = createMockGist(allMessages) + const inbox = createInboxAPI(analytics, gist, ['news', '_cio_feed']) + + const messages = await inbox.messages() + const ids = messages.map((m) => m.messageId) + + expect(ids).toContain('regular') + expect(ids).toContain('mixed-msg') + expect(ids).not.toContain('no-topic') + expect(ids).not.toContain('cio-msg') + }) + }) +}) diff --git a/packages/browser/src/plugins/in-app-plugin/inbox_messages.ts b/packages/browser/src/plugins/in-app-plugin/inbox_messages.ts index d11fabd71..c4d464e86 100644 --- a/packages/browser/src/plugins/in-app-plugin/inbox_messages.ts +++ b/packages/browser/src/plugins/in-app-plugin/inbox_messages.ts @@ -1,6 +1,8 @@ import { Analytics } from '../../core/analytics' import { JourneysEvents } from './events' +const CIO_TOPIC_PREFIX = '_cio' + export interface GistInboxMessage { messageType: string expiry: string @@ -143,14 +145,25 @@ async function getFilteredMessages( }) if (topics.length === 0) { - return allMessages + return allMessages.filter((message) => { + const messageTopics = message.topics + if (!messageTopics || messageTopics.length === 0) { + return true + } + return !messageTopics.some((topic) => topic.startsWith(CIO_TOPIC_PREFIX)) + }) } + const hasCioTopic = topics.some((topic) => topic.startsWith(CIO_TOPIC_PREFIX)) + return allMessages.filter((message) => { const messageTopics = message.topics if (!messageTopics || messageTopics.length === 0) { return false } + if (!hasCioTopic && messageTopics.some((topic) => topic.startsWith(CIO_TOPIC_PREFIX))) { + return false + } return messageTopics.some((messageTopic) => topics.includes(messageTopic)) }) }