@luxexchange/notifications 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.depcheckrc +14 -0
- package/.eslintrc.js +20 -0
- package/README.md +548 -0
- package/package.json +42 -0
- package/project.json +30 -0
- package/src/getIsNotificationServiceLocalOverrideEnabled.ts +7 -0
- package/src/global.d.ts +2 -0
- package/src/index.ts +41 -0
- package/src/notification-data-source/NotificationDataSource.ts +8 -0
- package/src/notification-data-source/getNotificationQueryOptions.ts +85 -0
- package/src/notification-data-source/implementations/createIntervalNotificationDataSource.ts +73 -0
- package/src/notification-data-source/implementations/createLocalTriggerDataSource.test.ts +492 -0
- package/src/notification-data-source/implementations/createLocalTriggerDataSource.ts +177 -0
- package/src/notification-data-source/implementations/createNotificationDataSource.ts +19 -0
- package/src/notification-data-source/implementations/createPollingNotificationDataSource.test.ts +398 -0
- package/src/notification-data-source/implementations/createPollingNotificationDataSource.ts +74 -0
- package/src/notification-data-source/implementations/createReactiveDataSource.ts +113 -0
- package/src/notification-data-source/types/ReactiveCondition.ts +60 -0
- package/src/notification-processor/NotificationProcessor.ts +26 -0
- package/src/notification-processor/implementations/createBaseNotificationProcessor.test.ts +854 -0
- package/src/notification-processor/implementations/createBaseNotificationProcessor.ts +239 -0
- package/src/notification-processor/implementations/createNotificationProcessor.test.ts +130 -0
- package/src/notification-processor/implementations/createNotificationProcessor.ts +15 -0
- package/src/notification-renderer/NotificationRenderer.ts +8 -0
- package/src/notification-renderer/components/BannerTemplate.tsx +188 -0
- package/src/notification-renderer/components/InlineBannerNotification.tsx +123 -0
- package/src/notification-renderer/implementations/createNotificationRenderer.ts +16 -0
- package/src/notification-renderer/utils/iconUtils.ts +103 -0
- package/src/notification-service/NotificationService.ts +47 -0
- package/src/notification-service/implementations/createNotificationService.test.ts +1092 -0
- package/src/notification-service/implementations/createNotificationService.ts +364 -0
- package/src/notification-telemetry/NotificationTelemetry.ts +44 -0
- package/src/notification-telemetry/implementations/createNotificationTelemetry.test.ts +99 -0
- package/src/notification-telemetry/implementations/createNotificationTelemetry.ts +33 -0
- package/src/notification-tracker/NotificationTracker.ts +14 -0
- package/src/notification-tracker/implementations/createApiNotificationTracker.test.ts +465 -0
- package/src/notification-tracker/implementations/createApiNotificationTracker.ts +154 -0
- package/src/notification-tracker/implementations/createNoopNotificationTracker.ts +44 -0
- package/src/notification-tracker/implementations/createNotificationTracker.ts +31 -0
- package/src/utils/formatNotificationType.test.ts +25 -0
- package/src/utils/formatNotificationType.ts +25 -0
- package/tsconfig.json +24 -0
- package/tsconfig.lint.json +8 -0
- package/vitest-setup.ts +1 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
import { ContentStyle, type InAppNotification, OnClickAction } from '@luxfi/api'
|
|
2
|
+
import { createBaseNotificationProcessor } from '@luxfi/notifications/src/notification-processor/implementations/createBaseNotificationProcessor'
|
|
3
|
+
import type { NotificationTracker } from '@luxfi/notifications/src/notification-tracker/NotificationTracker'
|
|
4
|
+
import { describe, expect, it } from 'vitest'
|
|
5
|
+
|
|
6
|
+
describe('createBaseNotificationProcessor', () => {
|
|
7
|
+
const createMockNotification = (params: {
|
|
8
|
+
name: string
|
|
9
|
+
timestamp: number
|
|
10
|
+
style: ContentStyle
|
|
11
|
+
id?: string
|
|
12
|
+
includeDismiss?: boolean
|
|
13
|
+
}): InAppNotification =>
|
|
14
|
+
({
|
|
15
|
+
id: params.id ?? `${params.name}-id`,
|
|
16
|
+
notificationName: params.name,
|
|
17
|
+
timestamp: params.timestamp,
|
|
18
|
+
content: {
|
|
19
|
+
style: params.style,
|
|
20
|
+
title: `${params.name}-title`,
|
|
21
|
+
subtitle: '',
|
|
22
|
+
version: 0,
|
|
23
|
+
buttons:
|
|
24
|
+
params.includeDismiss !== false
|
|
25
|
+
? [
|
|
26
|
+
{
|
|
27
|
+
text: 'Dismiss',
|
|
28
|
+
isPrimary: false,
|
|
29
|
+
onClick: {
|
|
30
|
+
onClick: [OnClickAction.DISMISS],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
]
|
|
34
|
+
: [],
|
|
35
|
+
},
|
|
36
|
+
metaData: {},
|
|
37
|
+
userId: 'user-1',
|
|
38
|
+
}) as unknown as InAppNotification
|
|
39
|
+
|
|
40
|
+
const createMockTracker = (processedIds: Set<string> = new Set()): NotificationTracker => ({
|
|
41
|
+
getProcessedIds: async () => processedIds,
|
|
42
|
+
isProcessed: async (id: string) => processedIds.has(id),
|
|
43
|
+
track: async (): Promise<void> => {},
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('initialization', () => {
|
|
47
|
+
it('creates a notification processor with process method', () => {
|
|
48
|
+
const tracker = createMockTracker()
|
|
49
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
50
|
+
|
|
51
|
+
expect(processor).toBeDefined()
|
|
52
|
+
expect(typeof processor.process).toBe('function')
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('notification limiting by content style', () => {
|
|
57
|
+
it('limits MODAL notifications to 1', async () => {
|
|
58
|
+
const tracker = createMockTracker()
|
|
59
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
60
|
+
const notifications: InAppNotification[] = [
|
|
61
|
+
createMockNotification({ name: 'modal-1', timestamp: 1000, style: ContentStyle.MODAL }),
|
|
62
|
+
createMockNotification({ name: 'modal-2', timestamp: 2000, style: ContentStyle.MODAL }),
|
|
63
|
+
createMockNotification({ name: 'modal-3', timestamp: 3000, style: ContentStyle.MODAL }),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
const result = await processor.process(notifications)
|
|
67
|
+
|
|
68
|
+
expect(result.primary).toHaveLength(1)
|
|
69
|
+
expect(result.primary[0].id).toBe('modal-1-id')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('limits UNSPECIFIED notifications to 1', async () => {
|
|
73
|
+
const tracker = createMockTracker()
|
|
74
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
75
|
+
const notifications: InAppNotification[] = [
|
|
76
|
+
createMockNotification({ name: 'unspec-1', timestamp: 1000, style: ContentStyle.UNSPECIFIED }),
|
|
77
|
+
createMockNotification({ name: 'unspec-2', timestamp: 2000, style: ContentStyle.UNSPECIFIED }),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
const result = await processor.process(notifications)
|
|
81
|
+
|
|
82
|
+
expect(result.primary).toHaveLength(1)
|
|
83
|
+
expect(result.primary[0].id).toBe('unspec-1-id')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('allows up to 3 LOWER_LEFT_BANNER notifications', async () => {
|
|
87
|
+
const tracker = createMockTracker()
|
|
88
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
89
|
+
const notifications: InAppNotification[] = [
|
|
90
|
+
createMockNotification({ name: 'banner-1', timestamp: 1000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
91
|
+
createMockNotification({ name: 'banner-2', timestamp: 2000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
92
|
+
createMockNotification({ name: 'banner-3', timestamp: 3000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
93
|
+
createMockNotification({ name: 'banner-4', timestamp: 4000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
const result = await processor.process(notifications)
|
|
97
|
+
|
|
98
|
+
expect(result.primary).toHaveLength(3)
|
|
99
|
+
expect(result.primary[0].id).toBe('banner-1-id')
|
|
100
|
+
expect(result.primary[1].id).toBe('banner-2-id')
|
|
101
|
+
expect(result.primary[2].id).toBe('banner-3-id')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('limits each style independently', async () => {
|
|
105
|
+
const tracker = createMockTracker()
|
|
106
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
107
|
+
const notifications: InAppNotification[] = [
|
|
108
|
+
createMockNotification({ name: 'modal-1', timestamp: 1000, style: ContentStyle.MODAL }),
|
|
109
|
+
createMockNotification({ name: 'modal-2', timestamp: 2000, style: ContentStyle.MODAL }),
|
|
110
|
+
createMockNotification({ name: 'banner-1', timestamp: 3000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
111
|
+
createMockNotification({ name: 'banner-2', timestamp: 4000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
112
|
+
createMockNotification({ name: 'banner-3', timestamp: 5000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
const result = await processor.process(notifications)
|
|
116
|
+
|
|
117
|
+
// Should have 1 modal + 3 banners = 4 total
|
|
118
|
+
expect(result.primary).toHaveLength(4)
|
|
119
|
+
const modalResults = result.primary.filter((n) => n.content?.style === ContentStyle.MODAL)
|
|
120
|
+
const bannerResults = result.primary.filter((n) => n.content?.style === ContentStyle.LOWER_LEFT_BANNER)
|
|
121
|
+
expect(modalResults).toHaveLength(1)
|
|
122
|
+
expect(bannerResults).toHaveLength(3)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('handles notifications without content style as UNSPECIFIED and limits to 1', async () => {
|
|
126
|
+
const tracker = createMockTracker()
|
|
127
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
128
|
+
const notifWithoutStyle: InAppNotification = {
|
|
129
|
+
id: 'notif-no-style-1',
|
|
130
|
+
content: {
|
|
131
|
+
title: 'notif-no-style-title',
|
|
132
|
+
subtitle: '',
|
|
133
|
+
version: 0,
|
|
134
|
+
buttons: [
|
|
135
|
+
{
|
|
136
|
+
text: 'Dismiss',
|
|
137
|
+
onClick: { onClick: [OnClickAction.DISMISS] },
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
} as unknown as InAppNotification
|
|
142
|
+
const notifWithoutStyle2: InAppNotification = {
|
|
143
|
+
id: 'notif-no-style-2',
|
|
144
|
+
content: {
|
|
145
|
+
title: 'notif-no-style-title',
|
|
146
|
+
subtitle: '',
|
|
147
|
+
version: 0,
|
|
148
|
+
buttons: [
|
|
149
|
+
{
|
|
150
|
+
text: 'Dismiss',
|
|
151
|
+
onClick: { onClick: [OnClickAction.DISMISS] },
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
} as unknown as InAppNotification
|
|
156
|
+
const notifications: InAppNotification[] = [notifWithoutStyle, notifWithoutStyle2]
|
|
157
|
+
|
|
158
|
+
const result = await processor.process(notifications)
|
|
159
|
+
|
|
160
|
+
expect(result.primary).toHaveLength(1)
|
|
161
|
+
expect(result.primary[0].id).toBe('notif-no-style-1')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('handles empty notifications array', async () => {
|
|
165
|
+
const tracker = createMockTracker()
|
|
166
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
167
|
+
const result = await processor.process([])
|
|
168
|
+
|
|
169
|
+
expect(result.primary).toEqual([])
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('handles single notification', async () => {
|
|
173
|
+
const tracker = createMockTracker()
|
|
174
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
175
|
+
const notifications: InAppNotification[] = [
|
|
176
|
+
createMockNotification({ name: 'notif-1', timestamp: 1000, style: ContentStyle.MODAL }),
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
const result = await processor.process(notifications)
|
|
180
|
+
|
|
181
|
+
expect(result.primary).toHaveLength(1)
|
|
182
|
+
expect(result.primary[0].id).toBe('notif-1-id')
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe('filtering processed notifications', () => {
|
|
187
|
+
it('filters out notifications that are in processedIds', async () => {
|
|
188
|
+
const processedIds = new Set<string>(['id-2'])
|
|
189
|
+
const tracker = createMockTracker(processedIds)
|
|
190
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
191
|
+
const notifications: InAppNotification[] = [
|
|
192
|
+
createMockNotification({ name: 'notif-1', timestamp: 1000, style: ContentStyle.MODAL, id: 'id-1' }),
|
|
193
|
+
createMockNotification({ name: 'notif-2', timestamp: 2000, style: ContentStyle.LOWER_LEFT_BANNER, id: 'id-2' }),
|
|
194
|
+
createMockNotification({ name: 'notif-3', timestamp: 3000, style: ContentStyle.MODAL, id: 'id-3' }),
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
const result = await processor.process(notifications)
|
|
198
|
+
|
|
199
|
+
// Since both remaining notifications are MODAL style, only 1 should be returned
|
|
200
|
+
expect(result.primary).toHaveLength(1)
|
|
201
|
+
expect(result.primary[0].id).toBe('id-1')
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('filters out multiple processed notifications', async () => {
|
|
205
|
+
const processedIds = new Set<string>(['id-1', 'id-3'])
|
|
206
|
+
const tracker = createMockTracker(processedIds)
|
|
207
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
208
|
+
const notifications: InAppNotification[] = [
|
|
209
|
+
createMockNotification({ name: 'notif-1', timestamp: 1000, style: ContentStyle.MODAL, id: 'id-1' }),
|
|
210
|
+
createMockNotification({ name: 'notif-2', timestamp: 2000, style: ContentStyle.LOWER_LEFT_BANNER, id: 'id-2' }),
|
|
211
|
+
createMockNotification({ name: 'notif-3', timestamp: 3000, style: ContentStyle.MODAL, id: 'id-3' }),
|
|
212
|
+
createMockNotification({ name: 'notif-4', timestamp: 4000, style: ContentStyle.MODAL, id: 'id-4' }),
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
const result = await processor.process(notifications)
|
|
216
|
+
|
|
217
|
+
// Remaining: id-2 (banner), id-4 (modal) - both should be returned
|
|
218
|
+
expect(result.primary).toHaveLength(2)
|
|
219
|
+
const resultIds = result.primary.map((n) => n.id)
|
|
220
|
+
expect(resultIds).toContain('id-2')
|
|
221
|
+
expect(resultIds).toContain('id-4')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('returns notifications with limits when processedIds is empty', async () => {
|
|
225
|
+
const tracker = createMockTracker()
|
|
226
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
227
|
+
const notifications: InAppNotification[] = [
|
|
228
|
+
createMockNotification({ name: 'notif-1', timestamp: 1000, style: ContentStyle.MODAL }),
|
|
229
|
+
createMockNotification({ name: 'notif-2', timestamp: 2000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
230
|
+
]
|
|
231
|
+
|
|
232
|
+
const result = await processor.process(notifications)
|
|
233
|
+
|
|
234
|
+
expect(result.primary).toHaveLength(2)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('filters out notifications without DISMISS action', async () => {
|
|
238
|
+
const tracker = createMockTracker()
|
|
239
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
240
|
+
const notifications: InAppNotification[] = [
|
|
241
|
+
createMockNotification({ name: 'notif-1', timestamp: 1000, style: ContentStyle.MODAL }),
|
|
242
|
+
createMockNotification({ name: 'notif-2', timestamp: 2000, style: ContentStyle.MODAL, includeDismiss: false }),
|
|
243
|
+
createMockNotification({ name: 'notif-3', timestamp: 3000, style: ContentStyle.MODAL }),
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
const result = await processor.process(notifications)
|
|
247
|
+
|
|
248
|
+
// notif-2 should be filtered out because it has no DISMISS action
|
|
249
|
+
expect(result.primary).toHaveLength(1)
|
|
250
|
+
expect(result.primary[0].id).toBe('notif-1-id')
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('allows notifications with DISMISS in background click', async () => {
|
|
254
|
+
const tracker = createMockTracker()
|
|
255
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
256
|
+
const notificationWithBgDismiss: InAppNotification = {
|
|
257
|
+
id: 'bg-dismiss-id',
|
|
258
|
+
content: {
|
|
259
|
+
style: ContentStyle.MODAL,
|
|
260
|
+
title: 'notif-title',
|
|
261
|
+
subtitle: '',
|
|
262
|
+
version: 0,
|
|
263
|
+
buttons: [],
|
|
264
|
+
background: {
|
|
265
|
+
backgroundOnClick: {
|
|
266
|
+
onClick: [OnClickAction.DISMISS],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
} as unknown as InAppNotification
|
|
271
|
+
|
|
272
|
+
const result = await processor.process([notificationWithBgDismiss])
|
|
273
|
+
|
|
274
|
+
expect(result.primary).toHaveLength(1)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('allows notifications with DISMISS in onDismissClick', async () => {
|
|
278
|
+
const tracker = createMockTracker()
|
|
279
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
280
|
+
const notificationWithOnDismissClick: InAppNotification = {
|
|
281
|
+
id: 'dismiss-click-id',
|
|
282
|
+
content: {
|
|
283
|
+
style: ContentStyle.MODAL,
|
|
284
|
+
title: 'notif-title',
|
|
285
|
+
subtitle: '',
|
|
286
|
+
version: 0,
|
|
287
|
+
buttons: [],
|
|
288
|
+
onDismissClick: {
|
|
289
|
+
onClick: [OnClickAction.DISMISS],
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
} as unknown as InAppNotification
|
|
293
|
+
|
|
294
|
+
const result = await processor.process([notificationWithOnDismissClick])
|
|
295
|
+
|
|
296
|
+
expect(result.primary).toHaveLength(1)
|
|
297
|
+
expect(result.primary[0].id).toBe('dismiss-click-id')
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it('returns empty array when all notifications are processed', async () => {
|
|
301
|
+
const processedIds = new Set<string>(['id-1', 'id-2'])
|
|
302
|
+
const tracker = createMockTracker(processedIds)
|
|
303
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
304
|
+
const notifications: InAppNotification[] = [
|
|
305
|
+
createMockNotification({ name: 'notif-1', timestamp: 1000, style: ContentStyle.MODAL, id: 'id-1' }),
|
|
306
|
+
createMockNotification({ name: 'notif-2', timestamp: 2000, style: ContentStyle.LOWER_LEFT_BANNER, id: 'id-2' }),
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
const result = await processor.process(notifications)
|
|
310
|
+
|
|
311
|
+
expect(result.primary).toEqual([])
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('filters and limits remaining notifications', async () => {
|
|
315
|
+
const processedIds = new Set<string>(['id-2', 'id-4'])
|
|
316
|
+
const tracker = createMockTracker(processedIds)
|
|
317
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
318
|
+
const notifications: InAppNotification[] = [
|
|
319
|
+
createMockNotification({ name: 'notif-3', timestamp: 3000, style: ContentStyle.MODAL, id: 'id-3' }),
|
|
320
|
+
createMockNotification({ name: 'notif-1', timestamp: 1000, style: ContentStyle.LOWER_LEFT_BANNER, id: 'id-1' }),
|
|
321
|
+
createMockNotification({ name: 'notif-4', timestamp: 4000, style: ContentStyle.MODAL, id: 'id-4' }),
|
|
322
|
+
createMockNotification({ name: 'notif-2', timestamp: 2000, style: ContentStyle.MODAL, id: 'id-2' }),
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
const result = await processor.process(notifications)
|
|
326
|
+
|
|
327
|
+
// Should filter out id-2 and id-4, leaving notif-3 (modal) and notif-1 (banner)
|
|
328
|
+
expect(result.primary).toHaveLength(2)
|
|
329
|
+
const resultIds = result.primary.map((n) => n.id)
|
|
330
|
+
expect(resultIds).toContain('id-3')
|
|
331
|
+
expect(resultIds).toContain('id-1')
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
describe('edge cases', () => {
|
|
336
|
+
it('preserves original notification objects without mutation', async () => {
|
|
337
|
+
const tracker = createMockTracker()
|
|
338
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
339
|
+
const originalNotifications: InAppNotification[] = [
|
|
340
|
+
createMockNotification({ name: 'notif-1', timestamp: 1000, style: ContentStyle.MODAL }),
|
|
341
|
+
createMockNotification({ name: 'notif-2', timestamp: 2000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
342
|
+
]
|
|
343
|
+
const originalNotificationsCopy = JSON.parse(JSON.stringify(originalNotifications))
|
|
344
|
+
|
|
345
|
+
await processor.process(originalNotifications)
|
|
346
|
+
|
|
347
|
+
expect(originalNotifications).toEqual(originalNotificationsCopy)
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('applies limits correctly across multiple styles', async () => {
|
|
351
|
+
const tracker = createMockTracker()
|
|
352
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
353
|
+
const notifications: InAppNotification[] = [
|
|
354
|
+
createMockNotification({ name: 'modal-1', timestamp: 3000, style: ContentStyle.MODAL }),
|
|
355
|
+
createMockNotification({ name: 'banner-1', timestamp: 1000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
356
|
+
createMockNotification({ name: 'modal-2', timestamp: 4000, style: ContentStyle.MODAL }),
|
|
357
|
+
createMockNotification({ name: 'banner-2', timestamp: 2000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
358
|
+
]
|
|
359
|
+
|
|
360
|
+
const result = await processor.process(notifications)
|
|
361
|
+
|
|
362
|
+
// Should limit modals to 1 and keep both banners (limit is 3)
|
|
363
|
+
expect(result.primary).toHaveLength(3)
|
|
364
|
+
const modalResults = result.primary.filter((n) => n.content?.style === ContentStyle.MODAL)
|
|
365
|
+
const bannerResults = result.primary.filter((n) => n.content?.style === ContentStyle.LOWER_LEFT_BANNER)
|
|
366
|
+
expect(modalResults).toHaveLength(1)
|
|
367
|
+
expect(bannerResults).toHaveLength(2)
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it('handles notifications with undefined content gracefully', async () => {
|
|
371
|
+
const tracker = createMockTracker()
|
|
372
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
373
|
+
const notificationWithoutContent: InAppNotification = {
|
|
374
|
+
id: 'notif-null-id',
|
|
375
|
+
} as unknown as InAppNotification
|
|
376
|
+
|
|
377
|
+
const notifications: InAppNotification[] = [
|
|
378
|
+
notificationWithoutContent,
|
|
379
|
+
createMockNotification({ name: 'notif-valid', timestamp: 2000, style: ContentStyle.MODAL }),
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
// Should not throw, but notif-null-id should be filtered out (no DISMISS action)
|
|
383
|
+
const result = await processor.process(notifications)
|
|
384
|
+
|
|
385
|
+
expect(result.primary).toHaveLength(1)
|
|
386
|
+
expect(result.primary[0].id).toBe('notif-valid-id')
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
it('limits notifications correctly with large lists', async () => {
|
|
390
|
+
const tracker = createMockTracker()
|
|
391
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
392
|
+
const notifications: InAppNotification[] = []
|
|
393
|
+
|
|
394
|
+
// Create 50 modal and 50 banner notifications
|
|
395
|
+
for (let i = 0; i < 50; i++) {
|
|
396
|
+
notifications.push(
|
|
397
|
+
createMockNotification({ name: `modal-${i}`, timestamp: i * 1000, style: ContentStyle.MODAL }),
|
|
398
|
+
)
|
|
399
|
+
notifications.push(
|
|
400
|
+
createMockNotification({ name: `banner-${i}`, timestamp: i * 1000, style: ContentStyle.LOWER_LEFT_BANNER }),
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const result = await processor.process(notifications)
|
|
405
|
+
|
|
406
|
+
// Should limit to 1 modal and 3 banners = 4 total
|
|
407
|
+
expect(result.primary).toHaveLength(4)
|
|
408
|
+
const modalResults = result.primary.filter((n) => n.content?.style === ContentStyle.MODAL)
|
|
409
|
+
const bannerResults = result.primary.filter((n) => n.content?.style === ContentStyle.LOWER_LEFT_BANNER)
|
|
410
|
+
expect(modalResults).toHaveLength(1)
|
|
411
|
+
expect(bannerResults).toHaveLength(3)
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
describe('chained notifications', () => {
|
|
416
|
+
it('identifies simple chain: A → B', async () => {
|
|
417
|
+
const tracker = createMockTracker()
|
|
418
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
419
|
+
|
|
420
|
+
const notificationB = createMockNotification({
|
|
421
|
+
name: 'notif-B',
|
|
422
|
+
timestamp: 2000,
|
|
423
|
+
style: ContentStyle.MODAL,
|
|
424
|
+
id: 'notif-B',
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
const notificationA: InAppNotification = {
|
|
428
|
+
id: 'notif-A',
|
|
429
|
+
notificationName: 'notif-A',
|
|
430
|
+
timestamp: 1000,
|
|
431
|
+
content: {
|
|
432
|
+
style: ContentStyle.MODAL,
|
|
433
|
+
title: 'notif-A-title',
|
|
434
|
+
subtitle: '',
|
|
435
|
+
version: 0,
|
|
436
|
+
buttons: [
|
|
437
|
+
{
|
|
438
|
+
label: 'Show B',
|
|
439
|
+
onClick: {
|
|
440
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
441
|
+
onClickLink: 'notif-B',
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
},
|
|
446
|
+
metaData: {},
|
|
447
|
+
userId: 'user-1',
|
|
448
|
+
} as unknown as InAppNotification
|
|
449
|
+
|
|
450
|
+
const notifications = [notificationA, notificationB]
|
|
451
|
+
const result = await processor.process(notifications)
|
|
452
|
+
|
|
453
|
+
// A should be primary (no incoming edges)
|
|
454
|
+
expect(result.primary).toHaveLength(1)
|
|
455
|
+
expect(result.primary[0].id).toBe('notif-A')
|
|
456
|
+
|
|
457
|
+
// B should be chained (has incoming edge from A)
|
|
458
|
+
expect(result.chained.size).toBe(1)
|
|
459
|
+
expect(result.chained.has('notif-B')).toBe(true)
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
it('handles chain of length 3: A → B → C', async () => {
|
|
463
|
+
const tracker = createMockTracker()
|
|
464
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
465
|
+
|
|
466
|
+
const notificationC = createMockNotification({
|
|
467
|
+
name: 'notif-C',
|
|
468
|
+
timestamp: 3000,
|
|
469
|
+
style: ContentStyle.MODAL,
|
|
470
|
+
id: 'notif-C',
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
const notificationB: InAppNotification = {
|
|
474
|
+
id: 'notif-B',
|
|
475
|
+
notificationName: 'notif-B',
|
|
476
|
+
timestamp: 2000,
|
|
477
|
+
content: {
|
|
478
|
+
style: ContentStyle.MODAL,
|
|
479
|
+
title: 'notif-B-title',
|
|
480
|
+
subtitle: '',
|
|
481
|
+
version: 0,
|
|
482
|
+
buttons: [
|
|
483
|
+
{
|
|
484
|
+
label: 'Show C',
|
|
485
|
+
onClick: {
|
|
486
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
487
|
+
onClickLink: 'notif-C',
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
],
|
|
491
|
+
},
|
|
492
|
+
metaData: {},
|
|
493
|
+
userId: 'user-1',
|
|
494
|
+
} as unknown as InAppNotification
|
|
495
|
+
|
|
496
|
+
const notificationA: InAppNotification = {
|
|
497
|
+
id: 'notif-A',
|
|
498
|
+
notificationName: 'notif-A',
|
|
499
|
+
timestamp: 1000,
|
|
500
|
+
content: {
|
|
501
|
+
style: ContentStyle.MODAL,
|
|
502
|
+
title: 'notif-A-title',
|
|
503
|
+
subtitle: '',
|
|
504
|
+
version: 0,
|
|
505
|
+
buttons: [
|
|
506
|
+
{
|
|
507
|
+
label: 'Show B',
|
|
508
|
+
onClick: {
|
|
509
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
510
|
+
onClickLink: 'notif-B',
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
],
|
|
514
|
+
},
|
|
515
|
+
metaData: {},
|
|
516
|
+
userId: 'user-1',
|
|
517
|
+
} as unknown as InAppNotification
|
|
518
|
+
|
|
519
|
+
const notifications = [notificationA, notificationB, notificationC]
|
|
520
|
+
const result = await processor.process(notifications)
|
|
521
|
+
|
|
522
|
+
// Only A should be primary (no incoming edges)
|
|
523
|
+
expect(result.primary).toHaveLength(1)
|
|
524
|
+
expect(result.primary[0].id).toBe('notif-A')
|
|
525
|
+
|
|
526
|
+
// B and C should be chained (have incoming edges)
|
|
527
|
+
expect(result.chained.size).toBe(2)
|
|
528
|
+
expect(result.chained.has('notif-B')).toBe(true)
|
|
529
|
+
expect(result.chained.has('notif-C')).toBe(true)
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
it('handles chain of length 5: A → B → C → D → E', async () => {
|
|
533
|
+
const tracker = createMockTracker()
|
|
534
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
535
|
+
|
|
536
|
+
const createChainNotification = (id: string, nextId?: string): InAppNotification =>
|
|
537
|
+
({
|
|
538
|
+
id,
|
|
539
|
+
notificationName: id,
|
|
540
|
+
timestamp: 1000,
|
|
541
|
+
content: {
|
|
542
|
+
style: ContentStyle.MODAL,
|
|
543
|
+
title: `${id}-title`,
|
|
544
|
+
subtitle: '',
|
|
545
|
+
version: 0,
|
|
546
|
+
buttons: nextId
|
|
547
|
+
? [
|
|
548
|
+
{
|
|
549
|
+
label: `Show ${nextId}`,
|
|
550
|
+
onClick: {
|
|
551
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
552
|
+
onClickLink: nextId,
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
]
|
|
556
|
+
: [
|
|
557
|
+
{
|
|
558
|
+
label: 'Dismiss',
|
|
559
|
+
onClick: {
|
|
560
|
+
onClick: [OnClickAction.DISMISS],
|
|
561
|
+
},
|
|
562
|
+
},
|
|
563
|
+
],
|
|
564
|
+
},
|
|
565
|
+
metaData: {},
|
|
566
|
+
userId: 'user-1',
|
|
567
|
+
}) as unknown as InAppNotification
|
|
568
|
+
|
|
569
|
+
const notificationE = createChainNotification('notif-E')
|
|
570
|
+
const notificationD = createChainNotification('notif-D', 'notif-E')
|
|
571
|
+
const notificationC = createChainNotification('notif-C', 'notif-D')
|
|
572
|
+
const notificationB = createChainNotification('notif-B', 'notif-C')
|
|
573
|
+
const notificationA = createChainNotification('notif-A', 'notif-B')
|
|
574
|
+
|
|
575
|
+
const notifications = [notificationA, notificationB, notificationC, notificationD, notificationE]
|
|
576
|
+
const result = await processor.process(notifications)
|
|
577
|
+
|
|
578
|
+
// Only A should be primary (no incoming edges)
|
|
579
|
+
expect(result.primary).toHaveLength(1)
|
|
580
|
+
expect(result.primary[0].id).toBe('notif-A')
|
|
581
|
+
|
|
582
|
+
// B, C, D, E should all be chained
|
|
583
|
+
expect(result.chained.size).toBe(4)
|
|
584
|
+
expect(result.chained.has('notif-B')).toBe(true)
|
|
585
|
+
expect(result.chained.has('notif-C')).toBe(true)
|
|
586
|
+
expect(result.chained.has('notif-D')).toBe(true)
|
|
587
|
+
expect(result.chained.has('notif-E')).toBe(true)
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
it('handles multiple independent chains', async () => {
|
|
591
|
+
const tracker = createMockTracker()
|
|
592
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
593
|
+
|
|
594
|
+
const createChainNotification = ({
|
|
595
|
+
id,
|
|
596
|
+
style,
|
|
597
|
+
nextId,
|
|
598
|
+
}: {
|
|
599
|
+
id: string
|
|
600
|
+
style: ContentStyle
|
|
601
|
+
nextId?: string
|
|
602
|
+
}): InAppNotification =>
|
|
603
|
+
({
|
|
604
|
+
id,
|
|
605
|
+
notificationName: id,
|
|
606
|
+
timestamp: 1000,
|
|
607
|
+
content: {
|
|
608
|
+
style,
|
|
609
|
+
title: `${id}-title`,
|
|
610
|
+
subtitle: '',
|
|
611
|
+
version: 0,
|
|
612
|
+
buttons: nextId
|
|
613
|
+
? [
|
|
614
|
+
{
|
|
615
|
+
label: `Show ${nextId}`,
|
|
616
|
+
onClick: {
|
|
617
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
618
|
+
onClickLink: nextId,
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
]
|
|
622
|
+
: [
|
|
623
|
+
{
|
|
624
|
+
label: 'Dismiss',
|
|
625
|
+
onClick: {
|
|
626
|
+
onClick: [OnClickAction.DISMISS],
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
],
|
|
630
|
+
},
|
|
631
|
+
metaData: {},
|
|
632
|
+
userId: 'user-1',
|
|
633
|
+
}) as unknown as InAppNotification
|
|
634
|
+
|
|
635
|
+
// Chain 1: A → B → C (use MODAL style)
|
|
636
|
+
const notificationC = createChainNotification({ id: 'notif-C', style: ContentStyle.MODAL })
|
|
637
|
+
const notificationB = createChainNotification({ id: 'notif-B', style: ContentStyle.MODAL, nextId: 'notif-C' })
|
|
638
|
+
const notificationA = createChainNotification({ id: 'notif-A', style: ContentStyle.MODAL, nextId: 'notif-B' })
|
|
639
|
+
|
|
640
|
+
// Chain 2: X → Y (use LOWER_LEFT_BANNER style so it doesn't conflict with the MODAL limit)
|
|
641
|
+
const notificationY = createChainNotification({ id: 'notif-Y', style: ContentStyle.LOWER_LEFT_BANNER })
|
|
642
|
+
const notificationX = createChainNotification({
|
|
643
|
+
id: 'notif-X',
|
|
644
|
+
style: ContentStyle.LOWER_LEFT_BANNER,
|
|
645
|
+
nextId: 'notif-Y',
|
|
646
|
+
})
|
|
647
|
+
|
|
648
|
+
const notifications = [notificationA, notificationB, notificationC, notificationX, notificationY]
|
|
649
|
+
const result = await processor.process(notifications)
|
|
650
|
+
|
|
651
|
+
// A and X should be primary (roots of their chains)
|
|
652
|
+
expect(result.primary).toHaveLength(2)
|
|
653
|
+
const primaryIds = result.primary.map((n) => n.id)
|
|
654
|
+
expect(primaryIds).toContain('notif-A')
|
|
655
|
+
expect(primaryIds).toContain('notif-X')
|
|
656
|
+
|
|
657
|
+
// B, C, Y should be chained
|
|
658
|
+
expect(result.chained.size).toBe(3)
|
|
659
|
+
expect(result.chained.has('notif-B')).toBe(true)
|
|
660
|
+
expect(result.chained.has('notif-C')).toBe(true)
|
|
661
|
+
expect(result.chained.has('notif-Y')).toBe(true)
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
it('handles notification with multiple parents (diamond pattern)', async () => {
|
|
665
|
+
const tracker = createMockTracker()
|
|
666
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
667
|
+
|
|
668
|
+
const notificationD = createMockNotification({
|
|
669
|
+
name: 'notif-D',
|
|
670
|
+
timestamp: 4000,
|
|
671
|
+
style: ContentStyle.MODAL,
|
|
672
|
+
id: 'notif-D',
|
|
673
|
+
})
|
|
674
|
+
|
|
675
|
+
// B and C both point to D
|
|
676
|
+
const notificationC: InAppNotification = {
|
|
677
|
+
id: 'notif-C',
|
|
678
|
+
notificationName: 'notif-C',
|
|
679
|
+
timestamp: 3000,
|
|
680
|
+
content: {
|
|
681
|
+
style: ContentStyle.MODAL,
|
|
682
|
+
title: 'notif-C-title',
|
|
683
|
+
subtitle: '',
|
|
684
|
+
version: 0,
|
|
685
|
+
buttons: [
|
|
686
|
+
{
|
|
687
|
+
label: 'Show D',
|
|
688
|
+
onClick: {
|
|
689
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
690
|
+
onClickLink: 'notif-D',
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
],
|
|
694
|
+
},
|
|
695
|
+
metaData: {},
|
|
696
|
+
userId: 'user-1',
|
|
697
|
+
} as unknown as InAppNotification
|
|
698
|
+
|
|
699
|
+
const notificationB: InAppNotification = {
|
|
700
|
+
id: 'notif-B',
|
|
701
|
+
notificationName: 'notif-B',
|
|
702
|
+
timestamp: 2000,
|
|
703
|
+
content: {
|
|
704
|
+
style: ContentStyle.MODAL,
|
|
705
|
+
title: 'notif-B-title',
|
|
706
|
+
subtitle: '',
|
|
707
|
+
version: 0,
|
|
708
|
+
buttons: [
|
|
709
|
+
{
|
|
710
|
+
label: 'Show D',
|
|
711
|
+
onClick: {
|
|
712
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
713
|
+
onClickLink: 'notif-D',
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
],
|
|
717
|
+
},
|
|
718
|
+
metaData: {},
|
|
719
|
+
userId: 'user-1',
|
|
720
|
+
} as unknown as InAppNotification
|
|
721
|
+
|
|
722
|
+
// A points to both B and C
|
|
723
|
+
const notificationA: InAppNotification = {
|
|
724
|
+
id: 'notif-A',
|
|
725
|
+
notificationName: 'notif-A',
|
|
726
|
+
timestamp: 1000,
|
|
727
|
+
content: {
|
|
728
|
+
style: ContentStyle.MODAL,
|
|
729
|
+
title: 'notif-A-title',
|
|
730
|
+
subtitle: '',
|
|
731
|
+
version: 0,
|
|
732
|
+
buttons: [
|
|
733
|
+
{
|
|
734
|
+
label: 'Show B',
|
|
735
|
+
onClick: {
|
|
736
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
737
|
+
onClickLink: 'notif-B',
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
label: 'Show C',
|
|
742
|
+
onClick: {
|
|
743
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
744
|
+
onClickLink: 'notif-C',
|
|
745
|
+
},
|
|
746
|
+
},
|
|
747
|
+
],
|
|
748
|
+
},
|
|
749
|
+
metaData: {},
|
|
750
|
+
userId: 'user-1',
|
|
751
|
+
} as unknown as InAppNotification
|
|
752
|
+
|
|
753
|
+
const notifications = [notificationA, notificationB, notificationC, notificationD]
|
|
754
|
+
const result = await processor.process(notifications)
|
|
755
|
+
|
|
756
|
+
// Only A should be primary (has no incoming edges)
|
|
757
|
+
expect(result.primary).toHaveLength(1)
|
|
758
|
+
expect(result.primary[0].id).toBe('notif-A')
|
|
759
|
+
|
|
760
|
+
// B, C, D should all be chained (all have incoming edges)
|
|
761
|
+
expect(result.chained.size).toBe(3)
|
|
762
|
+
expect(result.chained.has('notif-B')).toBe(true)
|
|
763
|
+
expect(result.chained.has('notif-C')).toBe(true)
|
|
764
|
+
expect(result.chained.has('notif-D')).toBe(true)
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
it('handles background onClick with POPUP action', async () => {
|
|
768
|
+
const tracker = createMockTracker()
|
|
769
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
770
|
+
|
|
771
|
+
const notificationB = createMockNotification({
|
|
772
|
+
name: 'notif-B',
|
|
773
|
+
timestamp: 2000,
|
|
774
|
+
style: ContentStyle.MODAL,
|
|
775
|
+
id: 'notif-B',
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
const notificationA: InAppNotification = {
|
|
779
|
+
id: 'notif-A',
|
|
780
|
+
notificationName: 'notif-A',
|
|
781
|
+
timestamp: 1000,
|
|
782
|
+
content: {
|
|
783
|
+
style: ContentStyle.MODAL,
|
|
784
|
+
title: 'notif-A-title',
|
|
785
|
+
subtitle: '',
|
|
786
|
+
version: 0,
|
|
787
|
+
buttons: [
|
|
788
|
+
{
|
|
789
|
+
text: 'Dismiss',
|
|
790
|
+
onClick: { onClick: [OnClickAction.DISMISS] },
|
|
791
|
+
},
|
|
792
|
+
],
|
|
793
|
+
background: {
|
|
794
|
+
backgroundOnClick: {
|
|
795
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
796
|
+
onClickLink: 'notif-B',
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
},
|
|
800
|
+
metaData: {},
|
|
801
|
+
userId: 'user-1',
|
|
802
|
+
} as unknown as InAppNotification
|
|
803
|
+
|
|
804
|
+
const notifications = [notificationA, notificationB]
|
|
805
|
+
const result = await processor.process(notifications)
|
|
806
|
+
|
|
807
|
+
// A should be primary
|
|
808
|
+
expect(result.primary).toHaveLength(1)
|
|
809
|
+
expect(result.primary[0].id).toBe('notif-A')
|
|
810
|
+
|
|
811
|
+
// B should be chained (referenced by A's background onClick)
|
|
812
|
+
expect(result.chained.size).toBe(1)
|
|
813
|
+
expect(result.chained.has('notif-B')).toBe(true)
|
|
814
|
+
})
|
|
815
|
+
|
|
816
|
+
it('ignores POPUP references to notifications not in the batch', async () => {
|
|
817
|
+
const tracker = createMockTracker()
|
|
818
|
+
const processor = createBaseNotificationProcessor(tracker)
|
|
819
|
+
|
|
820
|
+
const notificationA: InAppNotification = {
|
|
821
|
+
id: 'notif-A',
|
|
822
|
+
notificationName: 'notif-A',
|
|
823
|
+
timestamp: 1000,
|
|
824
|
+
content: {
|
|
825
|
+
style: ContentStyle.MODAL,
|
|
826
|
+
title: 'notif-A-title',
|
|
827
|
+
subtitle: '',
|
|
828
|
+
version: 0,
|
|
829
|
+
buttons: [
|
|
830
|
+
{
|
|
831
|
+
label: 'Show B',
|
|
832
|
+
onClick: {
|
|
833
|
+
onClick: [OnClickAction.POPUP, OnClickAction.DISMISS],
|
|
834
|
+
onClickLink: 'notif-B-not-in-batch',
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
],
|
|
838
|
+
},
|
|
839
|
+
metaData: {},
|
|
840
|
+
userId: 'user-1',
|
|
841
|
+
} as unknown as InAppNotification
|
|
842
|
+
|
|
843
|
+
const notifications = [notificationA]
|
|
844
|
+
const result = await processor.process(notifications)
|
|
845
|
+
|
|
846
|
+
// A should still be primary (references notification not in batch)
|
|
847
|
+
expect(result.primary).toHaveLength(1)
|
|
848
|
+
expect(result.primary[0].id).toBe('notif-A')
|
|
849
|
+
|
|
850
|
+
// No chained notifications
|
|
851
|
+
expect(result.chained.size).toBe(0)
|
|
852
|
+
})
|
|
853
|
+
})
|
|
854
|
+
})
|